Dependency Injection
Dependency Injection이란 코드의 재활용을 위해 제공해주는 fastapi의 기능이다.
가령 두개의 path operation이 있다고 하자
path1, path2 두개 모두 덧셈을 하는 로직이 필요하다.
그리고 두 path operation에 둘다 덧셈을 해주는 로직을 각각 작성하였다.
똑같은 로직인데 비효율적이지 않은가?
이에대한 해결책은 덧셈을 하는 로직을 하나 만들고 두개의 path operation에 dependency 를 걸어주면 되는것이다!
from typing import Optional
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: Optional[str] = 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
사용법은 아래와 같다.
재활용될 dependency 코드를 작성한다.
async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
Depends를 import한다
from fastapi import Depends, FastAPI
dependency를 걸어준다
async def read_items(commons: dict = Depends(common_parameters)):
async def read_users(commons: dict = Depends(common_parameters)):
자 여기서 주목할 몇가지를 보자
async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
디펜던시가 걸린 함수의 파라미터를 보면 path operation의 파라미터와 유사하다.
즉 dependency가 걸린 함수는 데코레이터(@app.get 같은거) 없는 path operation이라고 생각하면 된다!
다음은 Depends에 파라미터이다.
Depends에 올 파라미터는 함수처럼 사용이 가능한 파라미터가 와야하며, single 파라미터이여야 한다.
async def read_items(commons: dict = Depends(common_parameters)):
async def read_users(commons: dict = Depends(common_parameters)):
함수처럼 사용이 가능하다는 말은 꼭 함수가 아니여도 된다는것!
이제 docs를 확인해보자. 이전에 일반적으로 작성한 path operation 처럼 나온다!
Class Dependency
앞서 Depends의 파라미터는 함수처럼 쓸 수 있는 파라미터 하나가 와야한다고 했다.
그 말인 즉슨 클래스의 생성자는 함수처럼 사용이 가능하니 클래스도 가능하다는 얘기이다!
from typing import Optional
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: Optional[str] = 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
클래스 디펜던시는 앞선 함수 파라미터에 비해 아래와 같은 장점을 가진다.
- 클래스 안에 여러가지 기능을 추가해서 리턴해 줄 수 있다
- 클래스의 멤벼변수가 key값이 되니 IDE에서 코드 작성할 때 자동완성에 이점이 있다
앞선 예제에서는 함수가 dict만 리턴해 주었다. 이를 사용할 때 q, skip, limit 같은 key값들은 ide에서 자동완성이 안되니 내가 수기로 입력해줘야 하는 불편함이 있다.
허나 클래스로 디펜던시를 사용하면 멤버변수이니 key값을 내가 찾지 않아도 자동완성 된다.
Depends의 Type annotation
허나 위 코드에 한가지 단점이 있다.
commons: CommonQueryParams = Depends(CommonQueryParams)
CommonQueryParams가 두번이나 반복되었다.
사실 fastapi는 = 뒤에있는 코드만 call을 한다.
... = Depends(CommonQueryParams)
그러므로 사실 아래처럼만 적어줘도 문제가 없다.
commons = Depends(CommonQueryParams)
그런데 한가지 문제가 있다.
commons의 타입을 명시해주지 않아서 IDE에서 자동완성이 안된다.
그래서 어쩔수 없이 개발의 편의성을 위해 아래처럼 적어주는게 낫다.
commons: CommonQueryParams = Depends(CommonQueryParams)
이문제를 어떻게 해결 할 수 있을가?
Shortcut
fastapi는 이를 해결해주기 위해 아래와 같은 shortcut을 제공한다.
commons: CommonQueryParams = Depends()
그러면 알아서 Depends가 CommonQueryParams 인스턴스를 만들어 리턴해준다.
그럼 코드중복도 해결하고 리턴타입이 명시되어있으니 IDE의 자동완성문제도 해결되었다!
from typing import Optional
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: Optional[str] = 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
Sub Dependency
depency의 dependency를 만들 수 있다.
원하는 만큼 deep하게 만들수 있다.
from typing import Optional
from fastapi import Cookie, Depends, FastAPI
app = FastAPI()
def query_extractor(q: Optional[str] = None):
return q
def query_or_cookie_extractor(
q: str = Depends(query_extractor), last_query: Optional[str] = Cookie(None)
):
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
return {"q_or_cookie": query_or_default}
위코드를 설명하자면
디펜던시의 디펜던시를 만든다.
def query_extractor(q: Optional[str] = None):
return q
디펜던시의 디펜던시가 될 함수를 디펜던시 함수에 연결시켜준다
def query_or_cookie_extractor(
q: str = Depends(query_extractor), last_query: Optional[str] = Cookie(None)
):
if not q:
return last_query
return q
마지막으로 path operation에 연결시켜준다
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
Path Operation Decorator's Dependency
만약에 디펜던시가 리턴하는 값이 필요하지 않고 실행은 꼭 해야한다면
path operation decorator에 디펜던시를 달 수 있다.
사용방법은 데코레이터에 dependencies 파라미터에 Depends를 list형태로 넘겨준다.
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
여기서 주목할점은 verify_key 디펜던시 이다. 값을 리턴하고 있다.
async def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
하지만 저 리턴값은 path operation에 전달되지 않는다.
Global Dependency
개발을 하면서 어플리케이션 전체에 dependency를 걸어주고 싶다면 global dependency를 이용하면 된다
from fastapi import Depends, FastAPI, Header, HTTPException
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
@app.get("/items/")
async def read_items():
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
@app.get("/users/")
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
사용법은 위와 같다.
Dependency with yield
FastAPI에서는 yield를 사용하여 디펜던시가 끝나면 몇가지 작업을 수행할 수 있다.
데이터베이스 디펜던시 with yield
대표적인 예제가, 데이터베이스를 생성한뒤 사용하고 사용이 끝났으면 데이터베이스를 종료하는 작업이 있다.
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
response를 보내기전에 yield 까지의 코드만이 실행된다.
그리고 response가 전달되면 마지막으로 finally 이후 코드가 수행된다.
'Fast API > fastapi배우기' 카테고리의 다른 글
Fast API 배우기 21부 - Middleware (0) | 2021.11.05 |
---|---|
Fast API 배우기 20부 - Security, authentication 인증 시스템 (2) | 2021.11.04 |
Fast API 배우기 18부 - jsonable_encoder, PUT, PATCH (0) | 2021.11.03 |
Fast API 배우기 17부 - Path Operation Configuration (0) | 2021.11.03 |
Fast API 배우기 16부 - Handling Errors (1) | 2021.11.02 |
댓글