TL;NR;
Reflex(旧称はPynecone)っていうフレームワークを使うと、Pure PythonでかなりモダンなWebアプリが作れる。
2022年12月から開発が始まったばかりのようで日本語の記事が全然見つからないからサンプルコード組んでみた。
個人の雑感だけど、公式に"Build anything, faster."を謳ってる通り、コントロールのデザインが柔軟な上にレイアウトの種類がかなり豊富で、けっこう無理のきくページデザインができそう。
フルスタックなわりに初期化作業が簡単で取っ付きやすいし、データベースやコンポーネントも標準でしっかり用意されてて"Battery Included"な感じを受けた。
ただし新しすぎて検索しても情報は少ないから、公式ドキュメントがガチの生命線になる。
ともあれ新進気鋭でポテンシャルは高いから、ブレイクしたら類似フレームワークよりも人気が出るかも!?
インストール
Python 3.10.4 をインストール済みの Windows 11 とかで下記のコマンドを入力してインストールしたよ。
pip install reflex
コーディング
ReflexのトップページからChat GPTにアクセスしたり、DALL-Eで画像生成したりするサンプルコードにアクセスできるよ。
サンプルコードからして現代風ビュービュー吹かしてるよね!(ほめ言葉)
Hello world
私は技術力が低すぎてノーコードというかNo高度なので、そういうキラキラなコードについていけない。
なので古式ゆかしきHello worldを書くアプローチで攻めるとか言ってみるテスト(古語)。
今回はプロジェクト名をhello_reflex
にしてみる。
- まず適当な場所に
hello_reflex
フォルダを作る
※フォルダ名は大体何でもよいが、reflex
って名前のフォルダだと動かないので注意 - Powershellなどのターミナルを開いて
hello_reflex
フォルダにcd
する -
reflex init
を実行する - フォルダの中に入れ子で
hello_reflex
フォルダが作られて、その中にhello_reflex.py
があるのでテキストエディタで開く -
hello_reflex.py
の中身を全部消して、下記のように書き直して保存するimport reflex as rx def index(): return rx.heading("Hello Reflex!") app = rx.App(state=rx.State) app.add_page(index) app.compile()
-
reflex run
を実行する - ブラウザで http://localhost:3000/ を開く
以降はreflex init
は不要で、reflex run
するだけでOK。
こんな感じの画面が出る。
rx.App
でインスタンスを作って、そこにadd_page
でコンポーネントを返す関数を足すだけでWebページが出せる!
ちなみにreflex init
の後の書き換え作業を飛ばしてreflex run
すると、もう少し盛った下記の画面が出る。
なんでわざわざソースコード書き換えたのかは疑問の余地が残るものの、そこそこシンプルなコードでWebページが作れることに納得してもらえただろうか。
簡単なInput
Reflexのデザインは、Stackなどのレイアウトコンポーネントの中に入力コンポーネントを詰め込んでいく方式。
PySimpleGUIとかでレイアウト組んでる人には親和性高めだと思う。
複雑な画面だと入れ子が増えるしプロパティの設定をしっかり定義すると記述量が増えるから、Exampleを見てると少し癖があると感じる人もいるかもしれない。
今回の簡単な画面でも、rx.vstack
>rx.form
>rx.vstack
>rx.form_control
>入力コンポーネントでそこそこ深い入れ子になってる。
import reflex as rx
class State(rx.State):
number: float
password: str
number_text: str
password_text: str
def put_texts(self):
self.number_text = f"1.2 + 0.5 = {self.number}" if self.number else ""
self.password_text = f"パスワードは{self.password}" if self.password else ""
def index():
return rx.vstack(
rx.form(
rx.vstack(
rx.form_control(
rx.form_label("1.2 + 0.5 ="),
rx.input(on_change=State.set_number,placeholder="計算結果を入力",type_="float",is_required=True),
),
rx.form_control(
rx.form_label("パスワード入れてね"),
rx.password(on_change=State.set_password),
rx.form_helper_text("助言は役に立たない"),
),
rx.button("Submit", type_="submit"),
),
on_submit=State.put_texts,
),
rx.divider(),
rx.text(State.number_text),
rx.text(State.password_text),
)
# Add state and page to the app.
app = rx.App(state=State)
app.add_page(index)
app.compile()
こんな感じに動く。
入れ子が増える分、Streamlitよりは少しコード量が多くなるかな?
パラメータ保持用のクラス(今回はState
)を作って、そこでデータを管理するのがReflexの特徴として目を引く。
このState
クラスは公式Examplesで必ず出てくるし、データを一か所に集めるのは良い設計だと思う。
それにしてもState
クラスにnumber
変数作っただけで、set_number
関数なんてないのにrx.number_input(on_change=State.set_number,
が通るのはドキドキした。
(継承元のreflex.State
クラスにSettersが用意されてる)
最初は驚いたけど、コントロールと変数が簡単にデータバインディングできるのもいいよね。
複数コンポーネント
複数行のテーブルを表示する時もState
クラスを作って、rx.vstack
の中に各種コンポーネント群を入れていく特徴は変わらない。
下記はちょっと長いコード例。
みどころはindex
関数の中でrx.table
を定義(88行目)して、その中身はrx.foreach(State.rows, render_row)
みたいに各行の処理を自作のrender_row
関数に移譲してるところ。
それとrx.upload
(92行目)以下で公式ドキュメントのcolor
とかbg
とかborder
とかのデザイン関連のプロパティをふんだんに使ってるところ。
import reflex as rx
from typing import Any
from typing import List
class State(rx.State):
war: str
agree: str
answer: str = "2"
text: str
rows:List[List[str]] = []
img: list[str]
# 公式ドキュメントを流用 https://reflex.dev/docs/library/forms/upload
async def handle_upload(
self, files: list[rx.UploadFile]
):
"""Handle the upload of file(s).
Args:
files: The uploaded files.
"""
for file in files:
upload_data = await file.read()
outfile = rx.get_asset_path(file.filename)
# Save the file.
with open(outfile, "wb") as file_object:
file_object.write(upload_data)
# Update the img var.
self.img.append(file.filename)
def put_table(self):
self.rows = [["好きなもの", self.war],
["あなたは", "正直者" if self.agree else "嘘つき"],
["1 + 1 =", self.answer],
["Text Area", self.text],
]
def render_row(row):
return rx.tr(
rx.td(row[0]),
rx.td(rx.markdown(row[1])),
)
def index():
color = "rgb(107,99,246)"
return rx.vstack(
rx.form(
rx.vstack(
rx.form_control(
rx.form_label("どっちが好き"),
rx.select(["きのこ","たけのこ","干し芋"], placeholder="究極vs至高",on_change=State.set_war),
),
rx.form_control(
rx.form_label("チェックボックス"),
rx.checkbox("私は今Qiitaを読んでいます", on_change=State.set_agree),
),
rx.form_control(
rx.form_label("1 + 1 ="),
rx.radio_group(["2","11","田"], default_value="2", on_change=State.set_answer),
),
rx.form_control(
rx.form_label("Markdown"),
rx.text_area(on_blur=State.set_text),
),
rx.button("Submit", on_click=lambda: State.put_table()),
#rx.button("Submit", type_="submit"), # Submitにするならこちら
),
#on_submit=lambda: State.put_table(rx.upload_files()), # Submitにするならこちら
),
rx.divider(),
rx.table(
rx.thead(
rx.tr(
rx.th("項目"),
rx.th("値"),
)
),
rx.tbody(
rx.foreach(State.rows, render_row),
),
),
# 公式ドキュメントを流用 https://reflex.dev/docs/library/forms/upload
rx.upload(
rx.vstack(
rx.button(
"Select File",
color=color,
bg="white",
border=f"1px solid {color}",
),
rx.text(
"Drag and drop files here or click to select files"
),
),
border=f"1px dotted {color}",
padding="5em",
),
rx.button(
"Upload",
on_click=lambda: State.handle_upload(
rx.upload_files()
),
),
rx.foreach(
State.img, lambda img: rx.image(src=img)
),
padding="5em",
)
# Add state and page to the app.
app = rx.App(state=State)
app.add_page(index)
app.compile()
こんな感じに動く。
実はこのコードを書いてみたらかなり苦戦した。
Submit時に画像を表示しようとしたところ、画像がない時に表示が変わらなかったり、そもそもsubmit
じゃ画像が表示できなくてon_click
に切り替えたりした。
さらにクリック時に呼び出す処理の呼び出しタイミングが独特らしく、単純なif文で処理を分岐させようとしてもうまくいかなかったり、Condを駆使してもやっぱりうまくいかなかったりもした。
前述のとおり「python reflex」で検索しても公式ドキュメント以外の情報がほとんどなかったり、フレームワークが新しすぎてかゆいところにプロパティが届かなかったりするので、まだそこは覚悟が必要だと思う。
画面遷移
画面遷移について、わりと簡単に書けた。
複数ページのプログラムなら、他の類似フレームワークより取り扱いやすいかもしれない。
ちなみに画面表示の関数名に_
をつけるとURLは/
に変換されてしまうので注意。
import reflex as rx
def index():
return rx.vstack(
rx.text("問おう、あなたが私のマスタード?"),
rx.divider(),
rx.link("はい(1へ行け)", href="/task/1"),
rx.link("いいえ(14へ行け)", href="/task/14"),
)
def task_1():
return rx.vstack(
rx.text("お腹がすきました!"),
rx.divider(),
rx.link("ごはんをつくる(Endingへ行け)", href="/ending"),
rx.link("つくらない(14へ行け)", href="/task/14"),
)
def task_14():
return rx.vstack(
rx.text("ざんねん!"),
rx.text("あなたのぼうけんはここでおわってしまった!"),
rx.divider(),
rx.link(rx.button("コンティニュー"), href="/"),
)
def ending():
return rx.vstack(
rx.text("おめでとうエプロンボーイ!"),
rx.text("今日のごはんはごちそうだ!"),
rx.divider(),
rx.link(rx.button("戻る"), href="/"),
)
# Add state and page to the app.
app = rx.App(state=rx.State)
app.add_page(index)
app.add_page(task_1, route="/task/1")
app.add_page(task_14, route="/task/14")
app.add_page(ending, route="/ending")
app.compile()
- スタート画面(http://localhost:3000/)
- いいえ
総括
python以外は1行も書かない縛りでかなり楽にリッチなWebアプリケーションを作ることができるフレームワークがまた一つ増えた。
開発中の機能も多いと思うけど、デザインの自由度が高くフルスタックなフレームワークに育ちそうだから、絶対にHTML書きたくないマンの救世主になるポテンシャルを秘めている可能性もそこはかとなくありそう。ってコンピューターおばあちゃんが言ってた気がする。
なんやかんやでRoRのアップデートに苦しんだり「React
+TypeScript
って何だよ、pipすれば一発で動かせるくらい直感的になれよ!」って八つ当たりする我々のようなPython民は、戯れに試してどうぞ。
Reflexの公式サイトでホスティングサービスの応募をしてるから、興味のある方は申し込んでおくと吉。かもしれない。