클래스 기반 의존성 주입¶
의존성 주입(Dependency Injection) 시스템을 본격적으로 살펴보기 전에, 이전 예제를 개선하여 보다 확장성 있는 구조로 업그레이드해보겠습니다.
기존 예제에서의 dict
사용¶
이전 예제에서는 의존성 함수를 통해 dict
객체를 반환하는 방식으로 공통 쿼리 매개변수를 처리했습니다.
from typing import Annotated
from nexify import Depends, Nexify, Query
app = Nexify()
def common_parameters(
q: Annotated[str | None, Query()] = None,
skip: Annotated[int, Query()] = 0,
limit: Annotated[int, Query()] = 100,
) -> dict:
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items")
def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users")
def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
하지만 commons
의 타입이 dict
이기 때문에, 자동 완성이나 타입 검사를 활용하는 데 제한이 있습니다.
의존성으로 사용 가능한 요소¶
지금까지는 함수(Function)를 사용하여 의존성을 선언하는 방법을 배웠습니다.
이는 가장 일반적인 방식이지만, 의존성을 정의하는 유일한 방법은 아닙니다. 핵심 개념은 의존성이 "호출 가능(callable)"해야 한다는 점입니다.
"호출 가능(callable)"이란?¶
파이썬에서 "호출 가능"하다는 것은 해당 객체가 함수처럼 호출될 수 있음을 의미합니다. 즉, 함수뿐만 아니라 특정 조건을 충족하는 클래스나 다른 객체도 호출 가능할 수 있습니다.
예를 들어, 다음과 같은 클래스를 살펴보겠습니다:
class A:
def __init__(self, name: str):
self.name = name
example = A("example")
print(callable(example)) # True
여기서 marry는 Cat 클래스의 인스턴스입니다. 그런데 우리는 marry를 만들기 위해 Cat을 "호출"했습니다. 즉, 클래스 자체가 호출 가능하다는 것을 알 수 있습니다.
클래스 기반 의존성 정의¶
그래서 Nexify에서는 파이썬 클래스를 의존성으로 사용할 수 있습니다.
Nexify는 전달된 의존성이 호출 가능한지(callable) 여부와 해당 객체가 가지는 매개변수(parameters)를 확인합니다.
만약 의존성이 호출 가능하다면, Nexify는 이를 내부적으로 매개변수 분석 및 하위 의존성 처리를 수행합니다.
매개변수가 없는 호출 가능 객체 역시 동일한 방식으로 적용됩니다.
"호출 가능"한 것을 의존성으로서 Nexify에 전달하면, 그 "호출 가능"한 것의 매개변수들을 분석한 후 이를 핸들러 함수를 위한 매개변수와 동일한 방식으로 처리합니다. 하위-의존성 또한 같은 방식으로 처리합니다.
이제, 기존의 common_parameters
의존성을 클래스로 변환하여 개선해보겠습니다.
from typing import Annotated
from nexify import Depends, Nexify, Query
app = Nexify()
class CommonQueryParams:
def __init__(
self,
q: Annotated[str | None, Query()] = None,
skip: Annotated[int, Query()] = 0,
limit: Annotated[int, Query()] = 100,
):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items")
def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):
return commons
이 클래스의 __init__
메서드를 보면, 이전의 common_parameters
함수와 동일한 매개변수를 정의하고 있습니다.
이러한 방식의 장점은 다음과 같습니다:
- 타입 안전성(Type Safety):
CommonQueryParams
의 인스턴스를 반환하므로,dict
가 아닌 명확한 타입을 가지게 됩니다. - 자동 완성 지원: 클래스의 속성을 직접 참조할 수 있어 IDE에서 자동 완성을 활용할 수 있습니다.
- 보다 직관적인 코드: 데이터를 구조화된 객체로 다룰 수 있어 유지보수가 용이합니다.
- OpenAPI 문서 자동화: Nexify는 해당 클래스의 매개변수를 자동으로 문서화하고 검증합니다.