🌐

Pythonだけどweb開発したい! ~99%Pythonだけでwebアプリを作る~

2021/12/16に公開

この記事はPython Advent Calender 2021 16日目の記事です。(タイトルまちがったので上げ直しました。)

今年のアドカレももう折り返しですね。今年は調子に乗って参加しすぎてぶっ倒れそうですが、今年の頑張って作ったアプリのうち1つを公開します。

GitHub

全体

https://github.com/bluepost59/full_python_todo

フロントエンドだけGitHub Pagesでデプロイした版(動作を見れます)

https://bluepost59.github.io/brython_todo/

リポジトリ: https://github.com/bluepost59/brython_todo

はじめに

「Pythonしか書けないけどイケてるwebアプリ開発したい!」そう思ったことはありませんか?

webアプリ開発には、webサーバのコードとブラウザサイドのコードを開発する必要があります。特に後者はJavaScriptが必須であり、Pythonと違う記法やcallbackのようなJavaScript独特の概念を理解する必要があり、なかなか難しそうです。

そんなPythonistaの皆さんの夢をかなえるべく、今回はタイトルの通り99% Pythonだけで、webアプリ入門の定番であるToDoリストを作ってみます。

バックエンド: Flask

バックエンドにはPython使いにはおなじみのFlaskを使います。

せっかくなのでwebアプリらしくサーバサイドともAjax通信してサーバにデータを蓄積するようにします。初期画面はFlaskでロードしてタスクが登録されたらDBに保存するようにします。DBはサーバ立てるのが面倒なのでSQLiteを使います。

htmlなどをホストするAPIのほかにajax通信を受けるAPIを開けておきます。今回はSQLiteにタスクを格納するAPIを開けておき、登録時だけDBに保存するようにします。DBのタスクはビューを描画するときにテンプレートに埋め込む形にします。SQLiteはサーバを立てなくてよいので便利です。

server.py
import json
from flask import Flask,Request

app = Flask(__name__)

# ビューの作成
@app.route("/")
def serve_route():
    # データベースからデータを引っ張ってくる
    conn = sqlite3.connect("todo.db")
    cursor = conn.cursor()

    cursor.execute("select title from todo")
    raw_tasks = cursor.fetchall()

    tasks = [mytask[0] for mytask in raw_tasks]

    # HTMLにtasksの内容を埋め込み、レスポンスを返す
    return render_template("main.html", tasks=tasks)

jinja2のtemplate

FlaskではJinja2というテンプレートエンジンでレンダリングします。今回のToDoリストのように可変長なものでも、forループをテンプレートの中に埋め込むことでうまく表現することができます。

main.html
<div id="tasks_area" class="tasks_container">
{% for mytask in tasks%}
<div class="task">
    <input type="checkbox"></input>
    <div>{{ mytask }}</div>
</div>
{% endfor %}
</div>

フロントエンド: Brython

フロントエンドはBrythonを使います。あまり聞きなじみのないものかも知れませんが、実はこのライブラリは結構イケていて、JavaScriptだとjqueryなどのライブラリを使って実装するajax通信[1]もPythonの標準的な記法で記述できます。

Brythonは文法はPythonですが、.bryという拡張子で保存することが推奨されています。特にFlaskの場合だとPythonコードは全部サーバコードと認識するので、拡張子が.pyだとブラウザ側のコードを編集してもサーバが止まってしまいます。

ただし、はまりどころがあります。JSONをサーバに送るときjson.dumpsして文字列として送らないとgetのクエリのような形式で送られてしまい、サーバ側でパースエラーになります。

main.bry
def add_task(task_name: str):
    # POSTする
    ajax.post(
        url="http://localhost:3000/register/",
        headers={"Content-Type": "application/json", },
        data=json.dumps({"title": task_name, "detail": "detail"})
    )

    # DOM操作
    new_task_checkbox = browser.document.createElement("input")
    new_task_checkbox.type = "checkbox"

    new_task_name = browser.document.createElement("div")
    new_task_name.text = task_name

    new_task = browser.document.createElement("div")
    new_task.class_name = "task"

    new_task.appendChild(new_task_checkbox)
    new_task.appendChild(new_task_name)

    browser.document["tasks_area"].appendChild(new_task)

# 登録ボタン
@browser.bind(browser.document["new_task_register"], "click")
def register_new_task(ev):
    add_task(browser.document["new_task_input"].value)

BrythonはPythonコードをJSにトランスパイルして実行しています。そういう意味で「99%」とつけました。というかPython以外にもhtmlとCSSは手書きしないといけません。ここはブラウザで動かす以上仕方がないです...。

最後に

JavaScriptもできるエンジニア青ポス「JavaScriptでええやん」

とはいえ、JavaScriptは初学者にはとても難しい言語だと思います。言語仕様に加えて非同期実行やwebの通信の仕組みも同時並行で学ばないといけないので、DOMを読ませるにしても工夫が必要だったりします(head部分に書いたscriptでdocument.getElementById()するとNullが返るとか...)。

その点Brythonでは非同期実行が必須ではないので、ajaxによる非同期通信も普通のPythonコードのように書くことができます。実用としてはともかく、Pythonしかできない初心者プログラマがwebに入門するにはとてもわかりやすいのではないでしょうか。

ちなみに(宣伝)

次回はWEBやデータ分析に関する投稿をみんなでしてみよう Advent Calendar 2021で、もっとヤバいPython実行環境を解説します[2]。お楽しみに。

脚注
  1. もっとも、最近のJavaScriptではfetch APIという標準の言語機能でajax通信ができるようになっています。 ↩︎

  2. ブラウザ上でNumPyやScikit-learnを走らせたりします。ヤバイ。 ↩︎

Discussion