はじめに
Pythonが大好きな豊田工業高等専門学校の加藤愛斗です。
DjangoのAdmin, ORM機能を用いつつ、API周りはFastAPIを用いてサーバーを実装出来るようにしましたので紹介します。
はじまり
プログラミングは手段
私がプログラミングをする理由は、何か作りたいものがあってそれを形にしたいからです。そのため、プログラミングはただの手段と捉えています。
最初に身に着けた技術
私が初めて触ったプログラミング言語はPythonで、それ以来ずっとPythonでコンテストやインターンを行っていまいます。そして、サーバーサイドを書く際はよくDjangoを使用しています。これは、初めて触ったフレームワークがDjangoであり、インターン先でもDjnago+DRFで開発をしていたからです。
Djangoのデメリット
しかし、Djangoにはいくつかのデメリットがあります。
- API開発にあまり向いていない
- DRFは必須
- DRFを使用しても複雑・冗長になりがちである
- 遅い
新しいフレームワーク探し
そこで、新しいフレームワークを触ることにしました。
上記のように私のやりたいことはものを作ることで、プログラミングは単なる手段のため、新しい言語を習得したくありませんでした。(一時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対応させていない点が本プロジェクトと異なる気がします