概要
Pythonのことを調べていたところフレームワークでlitestarというものがあることを知りました
Pythonでのフレームワークというと、FastAPIやDjangoなどが主流かと思っていましたが、まだ聞いたことがないものが存在しておりました
軽く調べた感じですと何やらFastAPIよりも速い可能性を秘めているそうなので、まとめてみたいと思います
本題
「litestar」とは
特徴をざっくり書いていきたいと思います
-
コントローラー(Controller)でエンドポイントをグループ化することができる
-
FastAPIではメソッドごとにエンドポイントの設定をパスを記載しつつデコレータで記述していく感じになります
@app.get("/items") def list_items(db: Session = Depends(get_db)): ... @app.post("/items") def create_item(db: Session = Depends(get_db)): ... @app.get("/items/{id}") def get_item(id: int, db: Session = Depends(get_db)): ... -
litestarでは以下のように書くことができます
リクエストメソッドのデコレータを記述する必要はあるので残っていますがパス自体の記述はなくなっています
関連して認証などのエンドポイント全体に一貫した処理を加えたい場合はクラスにguardsやafter_requestを追記するだけでクラスに含まれるエンドポイント全てに反映させることができます(反映漏れがなくなるというのは良いことです)from litestar import Controller, get, post from litestar.di import Provide class ItemController(Controller): path = "/items" # 共通のパス dependencies = {"db": Provide(get_db)} guards = [admin_only_guard] # 大元の関数は省略 after_request = log_after_request # 大元の関数は省略 @get() async def list_items(self, db: Session) -> list[Item]: return db.query(Item).all() @post() async def create_item(self, db: Session, data: Item) -> Item: return db.add(data) @get("/{id:int}") async def get_item(self, id: int, db: Session) -> Item: return db.get(id)
-
-
エンドポイントのレスポンス時にモデルの変換自体を型システムに任せられる
-
FastAPIだとPydanticを用いて、実際にコードを記述していく必要がある部分ですが、litestarには「DTO」という仕組みがあります
エンドポイントが増えていくことで
response_modelのようなものも増えていくと思いますが、そうするとそのためのクラスを追加していくことになります以下の例だと次にUserの新規作成をしようとした場合にクラスの追加が必要になります
# Userモデルのベース class UserBase(BaseModel): email: str username: str # IDを出力する用 class UserRead(UserBase): id: int # ここではレスポンス返すためのモデルを参照している @app.get("/user/{id}", response_model=UserRead) def get_user(id: int): user_db = db.get(id) return user_read -
litestarにある「DTO(Data Transfer Object)」という考え方だとベースになるデータ構造を1つ用意してそのデータ構造に対して「ユーザー作成APIのレスポンスではパスワードを除外してレスポンスを返そう」ということをconfigという形でクラスに記載するだけで済むようになります
from litestar.dto import DataclassDTO, DTOConfig # 1. ベースとなるデータ構造(これ1つでOK) @dataclass class User: id: int username: str password: str email: str # 2. 出力用のDTO設定(パスワードを隠す) class UserOutDTO(DataclassDTO[User]): config = DTOConfig(exclude={"password"}) # 3. 更新用のDTO設定(IDとパスワードは変更不可にする) class UserUpdateDTO(DataclassDTO[User]): config = DTOConfig(exclude={"id", "password"}) @get("/user/{id:int}", return_dto=UserOutDTO) async def get_user(id: int) -> User: return db.get(id) # Userモデルをそのまま返せば、DTOが勝手に削ってくれる
-
-
Msgspecをサポートしているため高速である
高速かつ型安全にデータのシリアライズ/デシリアライズとバリデーションを行えるライブラリです
とのことです
(JSON エンコードが最大 40 % 速くなったという記事も見つかったりしました)↓↓↓詳しく気になる方は公式のベンチマークをのぞいてみてください↓↓↓
導入方法(Mac前提)
シンプルに以下でpipからインストールするのみとなります
pip install 'litestar[standard]'
サンプルコード
ここからはサンプルコードを実際に記載していきます
チュートリアル通りにTODOリストのところまで進めていくので、読める方・知っている方は飛ばしていただいても大丈夫です
-
HelloWorldを表示する
定番のHelloWorldを表示するだけのコードになりますコードと所感
app.pyfrom litestar import Litestar, get @get("/") async def hello_world() -> str: return "Hello, world!" app = Litestar([hello_world])以下のコマンドを実行してアプリケーションを起動しましょう
litestar runファイル名を「app.py」にしないと自動で認識してくれないので注意です
任意のファイル名で行きたい場合は以下になりますlitestar --app <任意のファイル名(.pyは抜いてください)>:app run -
TODOリストの一覧を返す
この時点では単一のTODOリストをコード内でベタ書きして、その内容をjsonで取得するというものになりますチュートリアル内ではdataclassの部分をdataclass使わずに実装していましたが、やはりdataclassを使うことでわかりやすく・見やすくなっていることがわかります
また、クエリパラメータでdone=1だった場合の分岐を実装していますが、これも関数に未使用のパラメータを追加するだけで勝手に認識してくれます、便利です(FastAPIでも同じですが)
コードと所感
app.pyfrom dataclasses import dataclass from litestar import Litestar, get @dataclass class TodoItem: title: str done: bool TODO_LIST: list[TodoItem] = [ TodoItem(title="Start writing TODO list", done=True), TodoItem(title="???", done=False), TodoItem(title="Profit", done=False), ] @get("/") async def get_list(done: bool | None = None) -> list[TodoItem]: if done is None: return TODO_LIST return [item for item in TODO_LIST if item.done == done] app = Litestar([get_list]) -
TODOリストにTODOを追加・更新できるようにする
チュートリアルとは逸脱して一覧の処理とTODOの追加と更新の処理を一緒にしています
ここら辺はあまりFastAPIとは変わらないような気がしますねdetails
app.pyfrom dataclasses import dataclass from litestar import Litestar, get, post, put from litestar.exceptions import NotFoundException @dataclass class TodoItem: title: str done: bool TODO_LIST: list[TodoItem] = [ TodoItem(title="Start writing TODO list", done=True), TodoItem(title="???", done=False), TodoItem(title="Profit", done=False), ] def get_todo_by_title(todo_name) -> TodoItem: for item in TODO_LIST: if item.title == todo_name: return item raise NotFoundException(detail=f"TODO {todo_name!r} not found") @get("/") async def get_list(done: bool | None = None) -> list[TodoItem]: if done is None: return TODO_LIST return [item for item in TODO_LIST if item.done == done] @post("/") async def add_item(data: TodoItem) -> list[TodoItem]: TODO_LIST.append(data) return TODO_LIST @put("/{item_title:str}") async def update_item(item_title: str, data: TodoItem) -> list[TodoItem]: todo_item = get_todo_by_title(item_title) todo_item.title = data.title todo_item.done = data.done return TODO_LIST app = Litestar([get_list, add_item, update_item])
おわりに
ということでPythonのフレームワークである「litestar」の紹介でした
まだまだFastAPIの人気は根深いものかと思いましたが、こちらもjsonのエンコードが高速になるということで、しっかりと利点を見極めつつ開発に適したものを選んでいければと思います