의존성으로서의 클래스¶
의존성 주입 시스템에 대해 자세히 살펴보기 전에 이전 예제를 업그레이드 해보겠습니다.
이전 예제의 딕셔너리
¶
이전 예제에서, 우리는 의존성(의존 가능한) 함수에서 딕셔너리
객체를 반환하고 있었습니다:
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
우리는 경로 작동 함수의 매개변수 commons
에서 딕셔너리
객체를 얻습니다.
그리고 우리는 에디터들이 딕셔너리
객체의 키나 밸류의 자료형을 알 수 없기 때문에 자동 완성과 같은 기능을 제공해 줄 수 없다는 것을 알고 있습니다.
더 나은 방법이 있을 것 같습니다...
의존성으로 사용 가능한 것¶
지금까지 함수로 선언된 의존성을 봐왔습니다.
아마도 더 일반적이기는 하겠지만 의존성을 선언하는 유일한 방법은 아닙니다.
핵심 요소는 의존성이 "호출 가능"해야 한다는 것입니다
파이썬에서의 "호출 가능"은 파이썬이 함수처럼 "호출"할 수 있는 모든 것입니다.
따라서, 만약 당신이 something
(함수가 아닐 수도 있음) 객체를 가지고 있고,
something()
또는
something(some_argument, some_keyword_argument="foo")
상기와 같은 방식으로 "호출(실행)" 할 수 있다면 "호출 가능"이 됩니다.
의존성으로서의 클래스¶
파이썬 클래스의 인스턴스를 생성하기 위해 사용하는 것과 동일한 문법을 사용한다는 걸 알 수 있습니다.
예를 들어:
class Cat:
def __init__(self, name: str):
self.name = name
fluffy = Cat(name="Mr Fluffy")
이 경우에 fluffy
는 클래스 Cat
의 인스턴스입니다. 그리고 우리는 fluffy
를 만들기 위해서 Cat
을 "호출"했습니다.
따라서, 파이썬 클래스는 호출 가능합니다.
그래서 FastAPI에서는 파이썬 클래스를 의존성으로 사용할 수 있습니다.
FastAPI가 실질적으로 확인하는 것은 "호출 가능성"(함수, 클래스 또는 다른 모든 것)과 정의된 매개변수들입니다.
"호출 가능"한 것을 의존성으로서 FastAPI에 전달하면, 그 "호출 가능"한 것의 매개변수들을 분석한 후 이를 경로 동작 함수를 위한 매개변수와 동일한 방식으로 처리합니다. 하위-의존성 또한 같은 방식으로 처리합니다.
매개변수가 없는 "호출 가능"한 것 역시 매개변수가 없는 경로 동작 함수와 동일한 방식으로 적용됩니다.
그래서, 우리는 위 예제에서의 common_paramenters
의존성을 클래스 CommonQueryParams
로 바꿀 수 있습니다.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
클래스의 인스턴스를 생성하는 데 사용되는 __init__
메서드에 주목하기 바랍니다:
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
...이전 common_parameters
와 동일한 매개변수를 가집니다:
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
이 매개변수들은 FastAPI가 의존성을 "해결"하기 위해 사용할 것입니다
함수와 클래스 두 가지 방식 모두, 아래 요소를 갖습니다:
문자열
이면서 선택사항인 쿼리 매개변수q
.- 기본값이
0
이면서정수형
인 쿼리 매개변수skip
- 기본값이
100
이면서정수형
인 쿼리 매개변수limit
두 가지 방식 모두, 데이터는 변환, 검증되고 OpenAPI 스키마에 문서화됩니다.
사용해봅시다!¶
이제 아래의 클래스를 이용해서 의존성을 정의할 수 있습니다.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
FastAPI는 CommonQueryParams
클래스를 호출합니다. 이것은 해당 클래스의 "인스턴스"를 생성하고 그 인스턴스는 함수의 매개변수 commons
로 전달됩니다.
타입 힌팅 vs Depends
¶
위 코드에서 CommonQueryParams
를 두 번 작성한 방식에 주목하십시오:
commons: CommonQueryParams = Depends(CommonQueryParams)
마지막 CommonQueryParams
변수를 보면:
... = Depends(CommonQueryParams)
... FastAPI가 실제로 어떤 것이 의존성인지 알기 위해서 사용하는 방법입니다. FastAPI는 선언된 매개변수들을 추출할 것이고 실제로 이 변수들을 호출할 것입니다.
이 경우에, 첫번째 CommonQueryParams
변수를 보면:
commons: CommonQueryParams ...
... FastAPI는 CommonQueryParams
변수에 어떠한 특별한 의미도 부여하지 않습니다. FastAPI는 이 변수를 데이터 변환, 검증 등에 활용하지 않습니다. (활용하려면 = Depends(CommonQueryParams)
를 사용해야 합니다.)
사실 아래와 같이 작성해도 무관합니다:
commons = Depends(CommonQueryParams)
..전체적인 코드는 아래와 같습니다:
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons=Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons=Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
그러나 자료형을 선언하면 에디터가 매개변수 commons
로 전달될 것이 무엇인지 알게 되고, 이를 통해 코드 완성, 자료형 확인 등에 도움이 될 수 있으므로 권장됩니다.
코드 단축¶
그러나 여기 CommonQueryParams
를 두 번이나 작성하는, 코드 반복이 있다는 것을 알 수 있습니다:
commons: CommonQueryParams = Depends(CommonQueryParams)
FastAPI는 특히 의존성이 FastAPI가 클래스 자체의 인스턴스를 생성하기 위해 "호출"하는 클래스인 경우, 조금 더 쉬운 방법을 제공합니다.
이러한 특정한 경우에는 아래처럼 사용할 수 있습니다:
이렇게 쓰는 것 대신:
commons: CommonQueryParams = Depends(CommonQueryParams)
...이렇게 쓸 수 있습니다.:
commons: CommonQueryParams = Depends()
의존성을 매개변수의 타입으로 선언하는 경우 Depends(CommonQueryParams)
처럼 클래스 이름 전체를 다시 작성하는 대신, 매개변수를 넣지 않은 Depends()
의 형태로 사용할 수 있습니다.
아래에 같은 예제가 있습니다:
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip : commons.skip + commons.limit]
response.update({"items": items})
return response
...이렇게 코드를 단축하여도 FastAPI는 무엇을 해야하는지 알고 있습니다.
팁
만약 이것이 도움이 되기보다 더 헷갈리게 만든다면, 잊어버리십시오. 이것이 반드시 필요한 것은 아닙니다.
이것은 단지 손쉬운 방법일 뿐입니다. 왜냐하면 FastAPI는 코드 반복을 최소화할 수 있는 방법을 고민하기 때문입니다.