3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonフレームワークの「litestar」とは

3
Posted at

概要

Pythonのことを調べていたところフレームワークでlitestarというものがあることを知りました
Pythonでのフレームワークというと、FastAPIやDjangoなどが主流かと思っていましたが、まだ聞いたことがないものが存在しておりました

軽く調べた感じですと何やらFastAPIよりも速い可能性を秘めているそうなので、まとめてみたいと思います

本題

「litestar」とは

特徴をざっくり書いていきたいと思います

  1. コントローラー(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では以下のように書くことができます
      リクエストメソッドのデコレータを記述する必要はあるので残っていますがパス自体の記述はなくなっています
      関連して認証などのエンドポイント全体に一貫した処理を加えたい場合はクラスにguardsafter_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)
      
  2. エンドポイントのレスポンス時にモデルの変換自体を型システムに任せられる

    • 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が勝手に削ってくれる
      
  3. Msgspecをサポートしているため高速である

    高速かつ型安全にデータのシリアライズ/デシリアライズとバリデーションを行えるライブラリです

    とのことです
    (JSON エンコードが最大 40 % 速くなったという記事も見つかったりしました)

    ↓↓↓詳しく気になる方は公式のベンチマークをのぞいてみてください↓↓↓

導入方法(Mac前提)

シンプルに以下でpipからインストールするのみとなります

pip install 'litestar[standard]'

サンプルコード

ここからはサンプルコードを実際に記載していきます
チュートリアル通りにTODOリストのところまで進めていくので、読める方・知っている方は飛ばしていただいても大丈夫です

  1. HelloWorldを表示する
    定番のHelloWorldを表示するだけのコードになります

    コードと所感
    app.py
    from 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
    
  2. TODOリストの一覧を返す
    この時点では単一のTODOリストをコード内でベタ書きして、その内容をjsonで取得するというものになります

    チュートリアル内ではdataclassの部分をdataclass使わずに実装していましたが、やはりdataclassを使うことでわかりやすく・見やすくなっていることがわかります

    また、クエリパラメータでdone=1だった場合の分岐を実装していますが、これも関数に未使用のパラメータを追加するだけで勝手に認識してくれます、便利です(FastAPIでも同じですが)

    コードと所感
    app.py
    from 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])
    
  3. TODOリストにTODOを追加・更新できるようにする
    チュートリアルとは逸脱して一覧の処理とTODOの追加と更新の処理を一緒にしています
    ここら辺はあまりFastAPIとは変わらないような気がしますね

    details
    app.py
    from 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のエンコードが高速になるということで、しっかりと利点を見極めつつ開発に適したものを選んでいければと思います

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?