FastAPI with Django ORM and Admin

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

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

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

## Djangoのデメリット
しかし、Djangoにはいくつかのデメリットがあります。
- API開発にあまり向いていない
  - DRFは必須
  - DRFを使用しても複雑・冗長になりがちである
- **遅い**
  - cf. https://qiita.com/bee2/items/0ad260ab9835a2087dae

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

## FastAPIのメリット
FastAPIを触ってみると革命が起きました。
- Pythonに**型定義**を含められる
  - インターン先のDjango開発などで、型がないことによりDXが下がりつらい思いをしていたため、この機能は非常に嬉しいです
- デフォルトで**APIドキュメント**を出力できる
  - 天才です。
  - フロントとの分業において効果を発揮しまくります。
- **速い**
  - cf. [PythonのWeb frameworkのパフォーマンス比較](https://qiita.com/bee2/items/0ad260ab9835a2087dae)

cf. 

https://zenn.dev/nameless_sn/articles/why_fastapi_development

## FastAPIのデメリット
上記のようなメリットがたくさんあるのですが、**Djangoの恋しさ**も残ります。
- **Adminページ**
  - Djangoの素晴らしさはやはりここでしょう
  - どうしても欲しいんです
- ORM
  - DjangoのORMに手が馴染んでしまいました

## だったら、
FastAPIとDjangoを融合してしまおう！！

# コード
https://github.com/kathmandu777/fastapi-django-template

## ディレクトリ構造
```sh
├── 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

## マウント
```py
"""
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で作成するときと同様に作成してください。

```py
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)

    ...
```

## スキーマ
```py
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で作成するときと同様に作成してください。

```py
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
```py
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
https://www.stavros.io/posts/fastapi-with-django

https://qiita.com/Ningensei848/items/ac72ff6edf4d887cdcc1

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

## The integration of FastAPI and Django ORM
https://kigawas.me/posts/integrate-fastapi-and-django-orm/

https://github.com/kigawas/fastapi-django

最も参考にしました

## FastAPIとDjango ORMを組み合わせる - Qiita
https://qiita.com/kigawas/items/80e48ccce98a35f65fff

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

## Learn to Use Django with FastAPI Frameworks
https://nsikakimoh.com/learn/django-and-fastapi-combo-tutorials

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