본문 바로가기
파이썬/FastAPI

[Python] FastAPI의 APIRouter

by 책 읽는 개발자_테드 2022. 5. 1.
반응형

목차

· APIRouter

· import APIRouter

· 경로 연산(path operations)

· Dependencies

· APIRouter 속성 일괄 처리하기

· 커스텀 tags, responses, dependencies 더하기

· The main FastAPI

 


APIRouter

· 애플리케이션이나 웹 API를 빌드하는 경우 모든 코드를 하나의 파일에 넣는 경우는 드물다. APIRouter는 FastAPI에서 여러 개의 파일을 통해 프로젝트 구조를 구성하여 더 큰 애플리케이션을 만들 수 있도록 하는 도구다.

 

· 예제를 통해 APIRouter에 대하여 알아보자. 예제의 파일 구성은 다음과 같다.

├── app                  # "app" is a Python package
│   ├── __init__.py      # this file makes "app" a "Python package"
│   ├── main.py          # "main" module, e.g. import app.main
│   ├── dependencies.py  # "dependencies" module, e.g. import app.dependencies
│   └── routers          # "routers" is a "Python subpackage"
│   │   ├── __init__.py  # makes "routers" a "Python subpackage"
│   │   ├── items.py     # "items" submodule, e.g. import app.routers.items
│   │   └── users.py     # "users" submodule, e.g. import app.routers.users
│   └── internal         # "internal" is a "Python subpackage"
│       ├── __init__.py  # makes "internal" a "Python subpackage"
│       └── admin.py     # "admin" submodule, e.g. import app.internal.admin

 

import APIRouter

· routers/users.py 모듈을 만들고, FastaPI 클래스와 같은 방식으로 가져와서 인스턴스를 생성한다.

from fastapi import APIRouter

router = APIRouter()

 

경로 연산(path operations)

· 위에서 import한 APIRouter를 통해 경로 연산을 수행한다.  

from fastapi import APIRouter

router = APIRouter()


@router.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "Ted"}, {"username": "Robin"}]


@router.get("/users/me", tags=["users"])
async def read_user_me():
    return {"username": "fakecurrentuser"}

- APIRouter를 "작은 FastAPI" 클래스로 생각할 수 있다. FastAPI 클래스와 모든 동일한 작업을 지원한다. ex) parameters, responses, dependencies, tags, etc

 

Dependencies

· 애플리케이션에서 종속성(dependencies)이 필요할 수 있다. 이러한 종속성을 app/dependencies.py 모듈에 입력한다.

- 예시: 간단한 종속성을 사용하여 사용자 지정 X-Token 헤더를 읽는다.

from fastapi import Header, HTTPException


async def get_token_header(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def get_query_token(token: str):
    if token != "barney":
        raise HTTPException(status_code=400, detail="No Barney token provided")

 

APIRouter 속성 일괄 처리하기

· app/routers/items.py 모듈에 "items"를 다루는 전용 엔드포인트가 있다고 가정하자. 

- 다음과 같은 path 연산을 갖는다.

/items/

/items/{item_id}

 

- 이는 app/routers/users.py와 같은 구조이지만, 코드를 좀 더 간단하고 똑똑하게 만들어보자. 모듈 안에 있는 모든 path 연산이 다음의 공통점을 갖는다.

Path가 prefix:/items 형태다.

▶ items라는 하나의 태그를 갖는다.

▶ 추가적인 responses가 있다.

▶ 앞서 생성한 X-Token을 종속성으로 갖는다.

 

path 연산에 위에서 말한 공통점을 각각 더하지 않고, APIRouter를 통해 더할 수 있다.

from fastapi import APIRouter, Depends, HTTPException

from ..dependencies import get_token_header

router = APIRouter(
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
    responses={404: {"description": "Not found"}},
)


fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}


@router.get("/")
async def read_items():
    return fake_items_db


@router.get("/{item_id}")
async def read_item(item_id: str):
    if item_id not in fake_items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"name": fake_items_db[item_id]["name"], "item_id": item_id}

 

- 위 코드는 app/routers/items.py에 존재하고, dependencies 모듈은 app/depencencies.py에 존재한다. 따라서 상대 경로 ..를 통해 denpendencies를 import 할 수 있다.

 

커스텀 tags, responses, dependencies 더하기

· 앞서 APIRouter를 통해 prefix, tags, response, dependencies를 더했다. 그래서 path 연산 각각에는 이러한 요소를 더하지 않았다. 하지만, 이러한 요소들을 추가적으로 각각의 path 연산에 더할 수도 있다.

from fastapi import APIRouter, Depends, HTTPException

from ..dependencies import get_token_header

router = APIRouter(
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
    responses={404: {"description": "Not found"}},
)

... 생략 ...


@router.put(
    "/{item_id}",
    tags=["custom"],
    responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
    if item_id != "plumbus":
        raise HTTPException(
            status_code=403, detail="You can only update the item: plumbus"
        )
    return {"item_id": item_id, "name": "The great Plumbus"}

- 이렇게 하면 마지막 path operation은 ["items", "custom"] 태그 조합을 갖는다.

- 또한 404와 403을 위한 응답들을 갖는다.

 

The main FastAPI

· app/main.py 모듈은 모든 것을 묶는 애플리케이션의 메인 파일로 만들어보자.

 

1. app/main.py 모듈에 FastAPI 클래스를 import한다. 그리고 global dependencies을 선언한다. 이는 각 APIRouter의종속성과 결합된다.

from fastapi import Depends, FastAPI

from .dependencies import get_query_token

app = FastAPI(dependencies=[Depends(get_query_token)])

 

2. 다른 APIRouter를 갖는 서브모듈을 import한다.  

from fastapi import Depends, FastAPI

from .dependencies import get_query_token
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])

- app/routers/user.pyapp/routers/items.py은 같은 파이썬 app 패키기인 서브 모듈이기 때문에 하나의 .을 통해 상대 경로를 사용하여 import 할 수 있다.

 

3. users와 items를 위한 APIRouter를 포함한다.

from fastapi import Depends, FastAPI

from .dependencies import get_query_token
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)

- app.include_router()를 통해 각 APIRouter를 메인 FastAPI 애플리케이션에 더할 수  있다.

 

4. APIRouter에 커스텀 prefix, tags, response, dependencies를 포함한다.

 

app/internal/admin.py 파일을 아래와 같이 작성한다. 여기에는 여러 프로젝트에서 공유하는 admin 경로 연산이 있는 APIRouter가 포함된다고 가정한다.

from fastapi import APIRouter

router = APIRouter()


@router.post("/")
async def update_admin():
    return {"message": "Admin getting schwifty"}

 

해당 파일은 여러 프로젝트에서 공유되기 때문에 prefix, dependencies, tag 등을 직접 APIRouter에서 수정하거나 더할 수 없다. 이러한 매개변수를 app.include_router()에 전달하는 방식으로 APIRouter를 수정하지 않고 문제를 해결할 수  있다.

 

app/main.py

from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)

- 이렇게하면 원본 APIRouter가 수정되지 않으므로, 동일한 app/internal/admin.py 파일을 다른 프로젝트와 계속 공유할 수 있다. 예를 들어 다른 프로젝트는 다른 인증 방법으로 동일한 APIRouter를 사용할 수 있게된다.

 

5. FastAPI app에 직접 path 연산을 더할 수 있다.

from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


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

- 위에 더해진 코드는 app.include_router()로 더해진 다른 path 연산과 함께 올바르게 작동한다.

 

출처

https://fastapi.tiangolo.com/tutorial/bigger-applications/#avoid-name-collisions

반응형

댓글