転職黙示録 (7) FastAPIのソースを読む 第1回 RouterにRouteを登録する

# FastAPIから学ぶWebフレームワークの内部

## 事前準備

### 前提条件

　作業環境は以下です.

* Python (3.7.4)
* Poetry 0.12.17
* VSCode (1.38.1)
* macOS Mojava (10.14.6)

### プロジェクトの作成

```bash
poetry new --src fast-001
cd fast-001
poetry add uvicorn fastapi
```

### 依存パッケージ

#### Uvicorn

　軽量なASGIサーバーらしいです. つまりサーバーです. Webフレームワークで作ったWebアプリを動かすソフトウェアということですね.

### WSGI

　ASGIはWSGIを非同期な処理にも拡張した仕様らしいです. WSGIはサーバーとWebアプリケーションの標準インターフェースを定めたもののようです.

> The Web Server Gateway Interface (WSGI) is a standard interface between web server software and web applications written in Python. [^1]

　こうやって共通のインタフェースを決めておくと開発が別々にできますねということでしょうか.

#### Starlette

　ASGIに対応したWebフレームワーク(ツールキット)らしいです. UvicornとStarletteは両方ASGIに対応しているので一緒に使えますねということです. WebsocketやGraphQLにも対応しているようです.

### 対象コード

```python:fast-001
import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
```

## コード・リーディング

### Q. FastAPIクラスとは?
### A. Starletteを継承したクラス

　つまりWebフレームワークということになります. Starletteがツールキットとも呼ばれるのはこのような拡張を意識した作りになっているからのようです.

```python:application.py
# ...
class FastAPI(Starlette):
# ...
```

　アプリのインスタンスでルーティングの機能などを提供します. ルートの設定はデコレータを使います. よく見るとコンストラクタの引数にroutesというのがありここに実際のルートが格納されるようです. 意外にもリスト型です.[^2]

```python:application.py
class FastAPI(Starlette):
    def __init__(
        ...
        routes: List[BaseRoute] = None,
        ...
    ) -> None:
```

　BaseRouteはStarletteで定義されている抽象クラスです. 

```python:starlette/routing.py
class BaseRoute:
    def matches(self, scope: Scope) -> typing.Tuple[Match, Scope]:
        raise NotImplementedError()  # pragma: no cover

    def url_path_for(self, name: str, **path_params: str) -> URLPath:
        raise NotImplementedError()  # pragma: no cover

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        raise NotImplementedError()  # pragma: no cover
```

　またroutingモジュールのAPIRouterインスタンスをプロパティに持ちます. インスタンス化するときにroutesを渡します. このAPIRouterインスタンスが実際のルーティングの処理担うようです.

```python:fastapi/application.py
self.router: routing.APIRouter = routing.APIRouter(
    routes, dependency_overrides_provider=self
)
```

### Q. getは何をしているの?

### A. 関数の再定義

 実際にadd_api_routeはAPIRouterクラスのメソッドです. ルーターにルートを追加するという処理をしているわけです.

```python:routing.py
def api_route(
        # ...
    ) -> Callable:
        def decorator(func: Callable) -> Callable:
            self.add_api_route(
                path,
                func,
                # ...
            )
            return func

        return decorator
```

　add_api_routeの引数を前二つ見るとpath, endPointという聞き慣れた名前になっています. 両者はほぼ同じ意味です.

```python:add_api_route
def add_api_route(self, path: str, endPoint: Callable, ...):
# ...
```

　getの処理の流れをまとめておきましょう

* パス文字列をapp.getに渡す
* routerインスタンスのgetに引数を引き継ぐ
* routerインスタンスのapi_route関数に引数を引き継ぐ
* api_route関数からdecorator関数の定義を返す

```python:routing.py
def api_route(
    self,
    path: str,
    ...
) -> Callable:
    def decorator(func: Callable) -> Callable:
        self.add_api_route(
            path,
            func,
            ...
        )
        return func

    return decorator
```


### Q. デコレータとは?

### A. 高階関数です.

　高階関数は引数として関数をとったり戻り値として関数を返す関数のことです. 関数をとり関数を返しますが, デコレータは渡した関数プラスアルファ何かします. 例を見るのが早いでしょう.


```python
def higher_order_function(func):
    def wrapped(*arg, **kwarg):
        print('🐶')
        func()
        print('🦜')
    return wrapped

@higher_order_function
def saru():
    print("🐵")

saru()
```

　デコレータは高階関数の一種ですが, デコレータがやっているのは関数の定義を返すことです. つまり関数の再定義がデコレータが実際にしていることです. api_route関数は関数を再定義して返しています. しかしこの再定義した関数はadd_api_route関数でエンドポイントを登録する以外はもらった関数をそのまま返しています.[^3]

　getデコレータによって, @app.get("/")以下の行がapi_routeの内部で定義されたdecoratorで置き換えられていると思えばいいでしょう.

### Q. APIRouterはどのように複数のルートを管理しているの？

### A. リストに入れてる

　つまりAPIRouterはコレクション型のラッパーです. add_api_routeでpathとendPointを登録しているのはすでに示した通りです.

```python:routing.py
def add_api_route(self, path: str, endpoint: Callable, ...) -> None:
    route = self.route_class(path, endpoint=endpoint, ...)
    self.routes.append(route)
```

## まとめ

* FastAPIはStarletteを継承したクラス
* APIRouterはAPIRouteクラスのインスタンスを管理するコレクション型のラッパー

[^1]: [wsgiref](https://docs.python.org/3/library/wsgiref.html)
[^2]: てっきり辞書型かなと思っていました.
[^3]: 面白いけどなぜ?