2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

YounGeekAdvent Calendar 2022

Day 3

FastAPI with Django ORM and Admin

Last updated at Posted at 2022-12-02

はじめに

Pythonが大好きな豊田工業高等専門学校の加藤愛斗です。
DjangoのAdmin, ORM機能を用いつつ、API周りはFastAPIを用いてサーバーを実装出来るようにしましたので紹介します。

はじまり

プログラミングは手段

私がプログラミングをする理由は、何か作りたいものがあってそれを形にしたいからです。そのため、プログラミングはただの手段と捉えています。

最初に身に着けた技術

私が初めて触ったプログラミング言語はPythonで、それ以来ずっとPythonでコンテストやインターンを行っていまいます。そして、サーバーサイドを書く際はよくDjangoを使用しています。これは、初めて触ったフレームワークがDjangoであり、インターン先でもDjnago+DRFで開発をしていたからです。

Djangoのデメリット

しかし、Djangoにはいくつかのデメリットがあります。

新しいフレームワーク探し

そこで、新しいフレームワークを触ることにしました。
上記のように私のやりたいことはものを作ることで、プログラミングは単なる手段のため、新しい言語を習得したくありませんでした。(一時Go言語に入門しようとしていたが、時間がなく断念)
いろいろ調べた結果、FastAPIというフレームワークがかなり来ているということを知りました。

FastAPIのメリット

FastAPIを触ってみると革命が起きました。

  • Pythonに型定義を含められる
    • インターン先のDjango開発などで、型がないことによりDXが下がりつらい思いをしていたため、この機能は非常に嬉しいです
  • デフォルトでAPIドキュメントを出力できる
    • 天才です。
    • フロントとの分業において効果を発揮しまくります。
  • 速い

cf.

FastAPIのデメリット

上記のようなメリットがたくさんあるのですが、Djangoの恋しさも残ります。

  • Adminページ
    • Djangoの素晴らしさはやはりここでしょう
    • どうしても欲しいんです
  • ORM
    • DjangoのORMに手が馴染んでしまいました

だったら、

FastAPIとDjangoを融合してしまおう!!

コード

ディレクトリ構造

├── README.md
├── docker-compose.yml
├── fastapi
│   ├── Dockerfile
│   ├── app
│   │   ├── __init__.py
│   │   ├── admin
│   │   │   ├── __init__.py
│   │   │   └── user.py
│   │   ├── api
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   └── user.py
│   │   ├── apps.py
│   │   ├── dependencies
│   │   │   ├── __init__.py
│   │   │   └── auth.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   └── user.py
│   │   ├── routers
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   ├── health.py
│   │   │   └── user.py
│   │   └── schemas
│   │       ├── __init__.py
│   │       ├── auth.py
│   │       └── user.py
│   ├── config
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── exceptions.py
│   │   ├── jwt.py
│   │   ├── log.py
│   │   ├── password.py
│   │   ├── settings
│   │   │   ├── base.py
│   │   │   ├── local.py
│   │   │   └── production.py
│   │   └── urls.py
│   ├── fastapi.env
│   ├── manage.py
│   ├── media
│   ├── poetry.lock
│   ├── pyproject.toml
│   ├── scripts
│   │   ├── runlocalserver.sh
│   │   └── startserver.sh
│   └── static
│       └── admin ()
├── poetry.lock
└── pyproject.toml

Djangoを参考に、アプリごとに分けるスタイルをとっています。

  • models: Django ORM
  • routers: FastAPI routers
  • schemas: FastAPI Pydantic models
  • api: FastAPI view

マウント

"""
Django settings
"""
django_app = get_asgi_application()


"""
FastAPI settings
"""
from app.routers import auth_router, health_router, user_router

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

fastapi_app = FastAPI()

# routers
fastapi_app.include_router(health_router, tags=["health"], prefix="/health")

# to mount Django
fastapi_app.mount("/django", django_app)
fastapi_app.mount("/static", StaticFiles(directory="static"), name="static")
fastapi_app.mount("/media", StaticFiles(directory="media"), name="media")

DjangoとFastAPIのasgi applicationを作成し、FastAPIのapplicationから、Djangoのapplicationをmountします。

モデル

Djangoで作成するときと同様に作成してください。

class User(AbstractBaseUser, PermissionsMixin, BaseModelMixin):
    objects = UserManager()

    email = models.EmailField(_("email address"), unique=True)

    MIN_LENGTH_USERNAME = 1
    MAX_LENGTH_USERNAME = 20
    username = models.CharField(
        _("username"),
        max_length=MAX_LENGTH_USERNAME,
    )

    # permissions
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    ...

スキーマ

from uuid import UUID

from pydantic import BaseModel


class ReadUserSchema(BaseModel):
    uuid: UUID
    username: str
    email: str

    class Config:
        orm_mode = True


class CreateUserSchema(BaseModel):
    username: str
    email: str
    password: str

orm_mode = True が重要です。

ルーター

FastAPIで作成するときと同様に作成してください。

user_router = APIRouter()


@user_router.get("/", response_model=ReadUserSchema)
async def get(request: Request, current_user: User = Depends(get_current_user)) -> User:
    return UserAPI.get(request, current_user)


@user_router.post(
    "/",
    response_model=ReadUserSchema,
)
async def create(request: Request, schema: CreateUserSchema) -> User:
    return await UserAPI.create(request, schema)

API

class UserAPI:
    @classmethod
    def get(cls, request: Request, current_user: User) -> User:
        return current_user

    @classmethod
    async def create(cls, request: Request, schema: CreateUserSchema) -> User:
        user = await User.objects.filter(email=schema.email).afirst()
        if user:
            raise HTTPException(status_code=400, detail="Email already registered")
        schema.password = hash_password(schema.password)
        return await sync_to_async(User.objects.create)(**schema.dict())

sync_to_async, afirstなどを用いて、DBに非同期処理をかけることが重要です。同期処理をしてしまうととても遅くなります。

Django4.1での非同期処理

Django4.1からORMにて非同期処理をすることが出来るようにアップデートされたため、このようにFastAPIとDjangoを組み合わせて開発することが出来るようになりました。以下に紹介する参考記事は全て非同期処理をしていないので、かなり遅いかと思われます。

まとめ

自分の理想のスタイルが構築できて嬉しいです。テンプレートリポジトリとしてあるため、ぜひ使っていただけたらと思います。
FastAPIは初心者であるため、変なことをしている部分があるかと思います。ぜひ、Issue, PRをお願いします。

参考

Using FastAPI with Django - Stavros' Stuff

この記事との出会いがこのプロジェクトの始まりでした

The integration of FastAPI and Django ORM

最も参考にしました

FastAPIとDjango ORMを組み合わせる - Qiita

Django ORMがasync対応する前の日本語記事

Learn to Use Django with FastAPI Frameworks

この記事を執筆するに当たり見つけました
djangoをwsgiで動かしている点と、Django ORMをasync対応させていない点が本プロジェクトと異なる気がします

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?