開発の経緯



ギャグの人数がいるサークル用に、昨年から部内コンペ用のプラットフォームを運営しています。
今は昨年作った Go製のものが稼働しているのですが、いくつかの課題を抱えていました。


というわけで以前から気になっていた streamlit を使って作り直すことにしました。

作ったもの

完成したもの

コンペ機能


まずメインのコンペの機能ですが、画面を見てもらうとわかるように

とかが実装されていて、割とちゃんとしたプラットフォームな建て付けをしています。
submit 画面
ベストスコアが出るとお祝いしてくれます。
ベストスコアの表示
ルールの表示
スコアの表示。各チームの public scoreのprogress が見れます。
チーム設定画面

デザイン力が皆無な自分ですが、streamlit を使うとかなり綺麗なものが出来上がってしまってマジですごい!

ディスカッション機能


コンペを盛り上げるにはディスカッションが必須です。
なのでディスカッションのページも作りました。
ディスカッション画面

ディスカッションでは、 Jupyter notebook をそのままアップロードすることでディスカッションを作ることができます。

投稿画面
Jupyter Notebookがそのままディスカッションに

Jupyter Notebookの実体は jsonで、各セルの属性が書いてあるのでそれを丁寧丁寧丁寧に場合わけすると簡単に streamlit で表示できるようになります。
実装するとこんな感じです。
def render_notebook(notebook: bytes):
    notebook = nbformat.reads(notebook, as_version=4)
    for cell in notebook.cells:
        if cell.cell_type == "code":
            st.code(cell.source)
            for output in cell.outputs:
                if output.output_type == "display_data":
                    st.image(read_nbimage(output.data["image/png"]))
                elif output.output_type == "execute_result":
                    if "text/html" in output.data:
                        render_html_out(output.data["text/html"])
                    else:
                        st.write(output.data["text/plain"])
                elif output.output_type == "stream":
                    st.write(output.text)
                elif output.output_type == "error":
                    st.error(output.ename)
                    st.error(output.evalue)
                elif output.output_type == "dataframe":
                    st.dataframe(output.data["text/plain"])
        elif cell.cell_type == "markdown":
            st.markdown(cell.source)

便利ですね〜

開発記録


こういうのはガッと作るのがいいので、土曜の深夜から作り始めて、一旦寝て日曜に集中して作業して終わらせました。
wakatime で見てみると大体9時間くらいの作業時間でした。

streamlit 使ったらすぐ終わるやろ! という方向性の想定よりは少しかかりましたが、とはいえこれくらい綺麗な見た目のものがこの短時間でできるのはだいぶ驚きです。
streamlit ほんとにすごいと思いました。

YAMLを1時間も触ってる謎の時間がありますが、mariadb の healthcheck をするときに mysqladmin ping する方法がなぜか一向にうまくいかずにガチャガチャやっていたせいです。

結局、 mariadb のイメージに最近つくようになった healthcheck.sh を使う方法でうまくいきました。

version: '3'

services:
  mariadb:
    image: mariadb
    ports:
      - "3306:3306"
    environment:
      MARIADB_ROOT_PASSWORD: password
      MARIADB_DATABASE: app_db
      TZ: Asia/Tokyo
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
      - mariadb_data:/var/lib/mysql
    healthcheck:
      test:
        [
          "CMD",
          "healthcheck.sh",
          "--su-mysql",
          "--connect",
          "--innodb_initialized"
        ]
      interval: 3s
      timeout: 10s
      retries: 3

  backend:
    build: ./backend
    ports:
      - "5001:5001"
    depends_on:
      mariadb:
        condition: service_healthy

volumes:
  mariadb_data:

それと、SQL はそんなに得意ではないので消耗しそうだな〜と思っていたのですが、GitHub Copilot を使ってみると ほとんど一発で所望のクエリを書いてくれてめちゃくちゃ効率がアップしました。
(一方で streamlit は更新が早いのでろくなコードをサジェストしてくれませんでしたが、まぁしゃーなしですね)

全てを Pythonで書いているのと、大した量のデータを扱っていないので、結構な部分でデータを全て DataFrame にしてしまって、pandas 芸人力を遺憾無く発揮して手早く実装を済ませました。

感想


綺麗な見た目の Webサービスが作れてうれしい 😊

これでコンペが盛り上がるといいな〜

今日の一曲