前回、Fletの機能を実装を交えて、確認しました。今回はその続きです。
- Flet使うとPythonでWeb/Desktop/Mobileアプリ作れちゃうんだ ←前回の内容
- Flet Docs (controls編) + cloudflareのWorkersデプロイ(D1を添えて) ←今回ここです!
- 1と2の学習を経て、実践的なサイト構築予定(詳細未定です)
Fletの以下ドキュメントで気になる内容に対して、手を動かしてみました。しかし、レイアウトだけでは・・・ってところです。今回もcloudflareと連携させます。workersとD1との連携を含めた形で進めようと思いました。
どちらかというと、今回はcloudflareの割合が高いと思います!!
cloudflare WorkersとD1
前回の記事ではcloudflareのpagesを用いて、Fletで作成した簡単なアプリケーションのデプロイまで行いました。冒頭の話と重なりますが、今回はAPI(Workers)とデータベース(D1)に登場していただきます。
今回のゴールは、Workers(Build serverless applications)とD1(serverless SQL databases)の連携方法を理解し、Fletで作成した画面上でそれらを利用できるようにすることです。
※最初にD1のページに記載された注意書きについて共有します。
Open Alpha While in the Open Alpha period, there is a possibility of breaking changes. The Alpha is meant for testing purposes and using it for production traffic is not recommended.
D1は現在オープン アルファ版であり、本番データおよびトラフィックには推奨されません。
実装準備
以下ページの手順通りで、デプロイまで完了でいます。
コマンド自体を覚える必要は特にないので、都度コピペすればいいです。
今回は内容を整理するために、各コマンド実行後に、ローカル環境とクラウド環境上でそれぞれどういった状態になっているのか、図を載せることにしました。
Workersのプロジェクト作成
wrangler init d1-tutorial -y
ローカル環境(PCのイラスト)にWorkersの土台を作成します。Workersの画像を少し薄くしていますが、ローカル≒ダミー、という私の感覚でこのようにしました。
DB作成
wrangler d1 create <DATABASE_NAME>
リモート環境に、データベースだけを作成します。(テーブルなどはないです)
この段階でフォルダは以下のようになっています。
スキーマ定義と初期データ投入
プロジェクトのルートディレクトリにschema.sql
を作成して、必要なテーブルの作成や初期データ投入などを実施します。--local
をつけると、ローカル環境だけにスキーマを適用します。schema.sql
の内容は、チュートリアル内に記載されている内容をそのまま記載してます。
npx wrangler d1 execute sample-d1 --local --file=./schema.sql
データが投入されたか確認するために、コマンドパレットを開きます。(VsCodeで作業してます)
選択肢に出てきますので、選択します。(直前に記載したコマンドを実行で、自動的に生成されます。)
Show Table
をクリックすれば、データを確認できます。(初期データは4件あります)
ここまでのイメージが以下の通りです。これでローカル環境で動作確認できる状態が整いました。
D1構築時のファイル確認
wrangler.tomlの修正
fly.io
を使うようになってからtoml
ファイルを触るようになりましたが、XXX.ini
やXXX.yaml
などの拡張子でもある設定ファイルのようなもの、という認識で作業してます。
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "<DATABASE_NAME>"
database_id = "<unique-ID-for-your-database>"
database_id
はcloudflareのマネジメントコンソールから取得します。
以下画像の<unique-ID-for-your-database>
部分にIDが記載されてます。コピーして貼り付けます。
src/index.ts
を確認
特定のPathへのアクセス時はXXXを、それ以外のPathの場合はYYYをレスポンスに設定する構造になってます。
実装以外の話ですが、Workers以外の何と連携できるのかわかるコメントは助かります。公式ドキュメントを辿れば分かりますが、試しに触ってみた段階で分かるのはうれしいです。
export interface Env {
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
// MY_KV_NAMESPACE: KVNamespace;
//
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
// MY_DURABLE_OBJECT: DurableObjectNamespace;
//
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
// MY_BUCKET: R2Bucket;
//
// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
// MY_SERVICE: Fetcher;
MY_SAMPLE_DB: D1Database;
}
実装開始
以下の機能があると、WorkersとD1を利用したアプリケーションかな~といったところです。
- ルートパスは読み込み時にデータ取得(GETリクエスト)を行い、全件表示
- データ登録のためのフォームを用意して、POSTリクエスト
- データ登録時、必要項目に値が設定されていない場合は、送信時ボタンを非活性
- DBへの接続に失敗した場合は、画面にメッセージ表示
Layout検討
サンプルとしてTrolliが用意されていたので、それをベースにしました。
PythonでPostリクエストの実装
Fletのアプリケーションからリクエストを投げるための実装方法確認です。
Pythonのrequestsを用いたPOST通信の実装方法(Bobyの設定)
CloudflareのWorkersでD1からデータ取得・操作
こちらは、WorkersからD1に対する指示をする方法を確認しました。
ローカル開発
workersとD1がローカルだけで完全再現できることが、本当に開発を行いやすくしてくれます。
Fletのコードをローカルで実装後、ローカルで挙動を確認して、問題がなさそう!となったら、デプロイすればOKです。
以下コマンドでWorkersをローカル環境で起動します。(wrangler
2.19.0`の場合)
npx wrangler dev --local --persist
真ん中あたりの枠で囲まれた部分にある通り、キーボードの入力値によって、挙動が変わるようです。動かすだけであれば、[b] open a browser
になるかと。。。
127.0.0.1
なので、自身のPC(ローカル)です!
wrangler 3.1.1
による動作確認
※wrangler 2.19.0
使用時に、wrangler 3.1.1
の使用を推奨されたためバージョンを変えてみました。以下コマンドでローカルDBの起動ができました。
npx wrangler dev --local
また、コンソールを見ると・・・
▲ [WARNING] --local is no longer required and will be removed in a future version.
--local
はもういらない、とのことです。以下コマンドで起動できました。
npx wrangler dev
1日のリクエスト制限を大切に
実際のWorkersのURLに対してリクエストを実施すると、バンドルリクエスト数が加算されます。
本日の
ということなので、10万リクエスト/日は、かなりの太っ腹な数ですが、節約できるならしたい、はずです!!Workersをローカルで起動して、http://127.0.0.1:8787
にアクセスする場合は、このバンドルリクエスト数がカウントされません。
実装のワンシーン(Fletアプリからcloudflare Workersへの疎通確認)
画像のinsert_data_to_customer
をクリックで、処理が走ります
上記画像はあくまでも挙動確認のための簡単なアプリケーションです。Fletの挙動理解のためでもありますが、いきなり本格的に作り上げるとミスったときに、戻すのが大変でやる気が削がれるので・・・この方法をとりました。
Fletのアプリケーション
側のコードでPOSTリクエストを行うメソッド定義
cloudflare Workers
側では、Fletアプリケーション側からのリクエストのBodyが受け取れているか確認
Flet側のコンソール
200(HTTPレスポンス)なので、正常にリクエストができたことが分かります。
cloudflare Workers側のコンソール
データ受け取れてますね。
ローカルDBとリモートDB
ローカルで開発が完結していることを、データ数で確認してみました。以下画像はWorkersのコード側でデータ追加の実装が完成した直後のテーブルの状態です。(初期データが4件あったので、2件追加)
このときに、リモートの状態(cloudflareのマネジメントコンソール)のデータ数を確認すると、4件で初期の状態から変わっていません。きちんと分かれてて、開発体験良しです~~
完成したアプリケーションの画面と実装
各画面と挙動
トップ画面
ユーザ追加画面
作成が成功したメッセージが表示されてから1秒経過すると、トップ画面(一覧画面)に戻る処理を入れています。一覧画面で、先ほどの入力値が追加されてますね!
Workers側のサーバを起動しない・間違ったURLに対するリクエストを実行したときの画面
実装を交えて(最後にリポジトリ情報を載せていますので、そちらを参照いただく形でもOK!です)
cloudflareの機能が素晴らしいことに感動して、Fletの話を忘れそうになります。
上記完成物の中で、以下に関する実装を取り上げます。
アクセスしたとき、以下の画面です。
submit
を非活性にして、送信処理が実行されないようにしています。
disabled=True
としています。この記述によって、画面表示時はボタンを非活性状態にできます。
# 追加処理のボタン
submit_button = ft.ElevatedButton(
disabled=True,
text="Submit", on_click=button_clicked)
次に、テキストフィールドに値を入力したときの挙動を見てみます。
各テキストフィールド(Company Name,Contract Name)それぞれに対して、on_change
を定義しています。
テキストの内容が更新されると、on_change=XXX
のXXXが実行されます。今回はtextfield_change
メソッドを定義しています。Company NameまたはContract Nameが空文字であれば、ボタンを非活性とする実装です。
両方の値が空文字でなければ、非活性状態が解除されます。
またdisabled
のステータスを更新するため、最後には、self.page.update()
を実行します。このupdate処理は実装全体でも登場することがしばしばあります。
# テキストフィールド内容更新時の処理
def textfield_change(e):
if company_name_textfield.value == "" or contract_name_textfield.value == "":
submit_button.disabled = True
else:
submit_button.disabled = False
self.page.update()
# テキストフィールド
company_name_textfield = ft.TextField(
label="Company Name", on_change=textfield_change)
contract_name_textfield = ft.TextField(
label="Contract Name", on_change=textfield_change)
両方入力できていれば、Submit
が活性状態になります。
送信しました。メッセージ表示がされたと同時に、テキストフィールドが空っぽにあり、ボタンも非活性状態に戻りました。
登録成功・失敗時のメッセージの要素について、visible=False
としています。画面表示したタイミングでは非表示とするためですね。try-exceptまではPOSTリクエスト情報を作成しています。
リクエストを実行時には・・以下を満たす必要がありそうです。
- 登録成功
- 登録できたことを伝える
- 画面をアクセスしたときの状態に戻す
- 登録失敗
- 登録できなかったことを伝える
登録の結果を伝えるためのメッセージ表示については、XXXX.visible = True
を使用します。
コードのコメントでも記載していますが、アクセスしたときの状態に戻すために、以下の処理が必要です。
- テキストフィールド初期化:
XXX.value = ""
- ボタンを日活性に:
submit_button.disabled = True
そして忘れずにpage.update()
を実行します。
# customer登録失敗時のメッセージ表示
msg_failed_add_customer = Row(
[
ft.Text("Sorry Failed to send new customer, Retry Access few minutes Later",
size=24, color=ft.colors.RED_500
)
],
visible=False
)
# customer登録成功時のメッセージ表示
msg_success_add_customer = Row(
[
ft.Text("Success to create new customer!",
size=24, color=ft.colors.BLUE_900,
)
],
visible=False
)
# 送信(Customer登録処理)
def button_clicked(e):
if (len(company_name_textfield.value) == 0 or len(contract_name_textfield.value) == 0):
page.update()
return
# POSTリクエスト
url = os.getenv('WORKERS_URL')
data = {
'CompanyName': company_name_textfield.value,
'ContactName': contract_name_textfield.value,
}
data_encode = json.dumps(data)
try:
response = requests.post(url, data=data_encode)
# テキストフィールド初期化
company_name_textfield.value = ""
contract_name_textfield.value = ""
# 画面の状態更新
submit_button.disabled = True
msg_failed_add_customer.visible = False
msg_success_add_customer.visible = True
page.update()
except:
msg_failed_add_customer.visible = True
page.update()
# 追加処理のボタン(再掲)
submit_button = ft.ElevatedButton(
disabled=True,
text="Submit", on_click=button_clicked)
最後は余談ですが、FletがそもそもFlutterを意識されているので、Rowの実装を見ていると、Flutterに近いものがあると感じました。
WorkersとD1のデプロイ
テーブル定義のデプロイ
これまでローカルだけで定義していたスキーマを、リモートに対して適用します。
これまでローカルで使用していたテーブルのデータは影響を受けず、schema.sql
に記述した定義を今度はリモート環境に適用します。
npx wrangler d1 execute sample-d1 --file=./schema.sql
Workersの処理を記述したtsファイル自体のデプロイ
GET/POSTリクエストを実装できたので、こちらもデプロイします。
npx wrangler publish
コンソールの出力
Your worker has access to the following bindings:
- D1 Databases:
- MY_SAMPLE_DB: {projectname} (b827dba5-47aa-427f-886e-d815287ca699)
Total Upload: 7.18 KiB / gzip: 2.27 KiB
Uploaded {projectname} (0.89 sec)
Published {projectname} (0.28 sec)
https://{projectname}.{github-username}.workers.dev
Current Deployment ID: 7bd71725-ac94-401c-bd37-274b697b83f7
fletのアプリケーションをpages上にデプロイ(githubのリポジトリ経由)
cloudflare上にpagesが構築されました。
よし最終確認だ!と思って、デプロイしたアプリケーションの動作確認を始めると・・・
Can't connect to HTTPS URL because the SSL module is not available.
読んでみると、どうやら・・・Pyodide
が関係している???
前回の記事でcloudflare pages
にデプロイしたアプリケーションは大した機能がないので、何も起きませんでした。ですが今回のように、いくつか機能を加えてライブラリも追加するようになると、不具合が発生するケースもあることが確認できました。
Fletの公式ドキュメントにも、以下のようにFlet static cons
として以下の記述があります。
Limited Python compatibility - not every program that works with native Python can be run with Pyodide.
Pyodide
で動かした場合に、Pythonで動いても機能しない部分がある、といった訳でしょうか。
ということで・・・
fletのアプリケーションをcloudflare pagesfly.io
上にデプロイに変更します!!
fly launch
コマンド実行
自動生成されるProcfile
というファイルがあります。そこに、起動時のコマンドを設定します。
デフォルトは以下のようになっていました。(1行目はコメントです。)
# Modify this Procfile to fit your needs
web: gunicorn server:app
今回は、通常にPythonコマンドで動作確認もしていたので、それを使います。
# Modify this Procfile to fit your needs
web: python main.py
fly deploy
コマンドでデプロイして、画面から確認してみます。
きちんとデータが表示できました・・・
Fletのデザインについて学習した
cloudflareとそのためのアプリケーションの方が内容盛りだくさんで、かつ私自身としても共有したい内容だったので、Fletの内容確認は後ろに回しました。
前回と同様ですが全ては触っていません。実装を重ねる中で適宜調べるので、ざっくり見て気になった項目を確認しました。詳しい情報は公式ドキュメントをみていただきたいと思います。
また公式からデモページが用意されているので、こちらで動作確認がすぐに行えます。
Appbar
参考にしている、このドキュメントにもおそらく使われているパーツだと思います。
Appbarが画面上部ですが、横・下部に同様の実装を、navigationrail,navigationbarで説明していると思います。(省略)
ResponsiveRow
Responsive
という単語で想像つく部分はありましたが、実装方法を見てみました。
bootstrapやtailwindcsを経験された方ですと、レイアウトの話で12
という数字はなじみがあるかと思います。画面幅がどれくらいであれば、どのように表示する、といった制御を行います。
Image
画像が表示できないのは困るので調べました。CORSの問題があったりして、画像が表示できないケースもある。
サンプルコードにあったhttps://picsum.photos/200/200
の画像を表示する場合に、ブラウザで起動するとCORSエラーになるので、ft.appの引数にweb_renderer="html"
を設定したりして、対応する必要もありそうです。
progressbar
メモ:処理に時間を要して、ユーザに待ってもらうときに、使うと思った。
これはFletに限った話ではないが、N秒でプログレスの最後まで到達するのか制御できるので、使いやすい。
IconButton
メモ:各商品ページやアカウントページなどでは、使いそうかな~
AlertDialog
メモ:ボタンをクリックしたとき、何か警告などを表示するときに使いそうかな~、
GestureDetector
Flutterでもお世話になる機会が多かったので、こちらでも触れておきます。
何か画面でイベント(入力、ボタンのクリック)が発生したときに、どんな処理を
Footer
アプリケーションでフッターはよく見ますが、そのサンプルをみてみました。
実装について
今後
Fletの基礎部分と、cloudflareのWorkdersとD1の基礎について、理解することができました。
次回以降は少し作成まで間が空いてしまうかもしれませんが、もう少し実践的な内容に挑戦してみようと考えています。