들어가기 앞서
자바를 메인언어로 백엔드를 공부했지만 어쩔수없이 FastAPI 를 해야하니 알아보는 시간을 갖도록 했다.
자바에서도 DB 와 같은 외부 서드파티 저장소와의 연결을 위한 Connection 객체가 있듯,
Python 에서도 충분히 있을거라고 생각했고
Springboot 때 처럼 커넥션 객체를 트랜잭션이 시작할때 열리고 트랜잭션 종료때 반환되는 방식인가 했지만?
Depends 라는 녀석을 Springboot 로 따지만 Controller 함수에서 사용하는것 처럼 보인다.
필자가 사용하는 환경은 아래와 같다.
Lang: Python 3.11
ORM: sqlalchemy 2.0.23
web: FastAPI 0.104.1
사용
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from typing import Generator
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" # 예시 URL
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Dependency
def get_db() -> Generator[Session, None, None]:
db: Session = SessionLocal()
try:
yield db
finally:
db.close()
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(Item).filter(Item.id == item_id).first()
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item
실제로 콜 해보면 DB 가 준비되어있다는 가정아래에 정상적으로 db_session 을 얻어온뒤 쿼리까지 문제없이 처리된다.
그러면 대충 유추해볼 수 있는것이 실제로 read_item 이라는 함수를 호출하기전에 db 라는 Session 타입의 파라미터를 누가 채워준다는 뜻이다.
이는 Springboot 에서 HandlerMethodArgumentResolver 와 어노테이션을 결합하여 사용했던것과 유사한것 같다.
이해하기 위해서 제너레이터 라는 타입을 먼저 간단하게 알아보면
저 함수는 독특하게도 get_db 를 쌩으로 호출하면 제너레이터 객체가 반환된다.
진짜 함수의 내부동작을 일으키려면 next() 라는 표준함수를 통해 yield 동작까지 수행되고
한번 더 yield 를 누르게되면 남은 finally 와 같은 블록을 처리하게 된다.
마치 함수 내부를 필요한 부분만큼만 잘라서 지연평가 되도록 사용하는것 처럼 보인다.
암튼 자바에서도 HandlerMapper 를 통해 url path 에 맞는 메소드를 호출하듯
FastAPI 도 어떤 주체가 @app.get 과 같은 경로에 맞는 함수를 호출할 것이며
호출할 때 파라미터 검사를 통해 Depends 타입이 있는지 검사하고
Depends 타입이 있다면 내부에 등록된 함수를 먼저 처리 하려고 할 것이다.
fastapi/fastapi/dependencies/utils.py at cd40c5b40ffd8ba0c6a6a6c96bbf34ec1cf9c525 · fastapi/fastapi
FastAPI framework, high performance, easy to learn, fast to code, ready for production - fastapi/fastapi
github.com
내부적으로 컨텍스트 메니저라는 클래스로 다시한번 더 감싸게 되는데 이는 간단하게 파이썬의 with as 절에서 사용하기 위함이다.
java 에서 Closeable 인터페이스와 try with resources 를 통해 자원관리를 자동으로 수행한다면
파이썬에서는 컨텍스트 니저와 with as 절을 통해 관리한다.
유추해보자면 __enter__ 라는것을 스레드풀에 넘기는거라면, __enter__ 안에 Depends 에서 넘긴 제너레이터가 들어가있고
내부적으로 next() 호출을 하고 다시 반환을 거쳐 실제 API 가 호출되고 나서
__exit__ 라는것을 통해 내부적으로 한번더 next() 를 호출하는게 아닐까 싶다.
활용
공식문서와 위의 링크속 실제 구현코드상으로만 봐도 일반함수도 Depends 에 충분히 들어갈 수 있는것 같다.
필자 같은경우, FastAPI 에서도 SpringSecurity 의 @Authentication 어노테이션 비스무리하게 만들고 싶어서
Depends(get_authentication) 을 만들어서 활용하고 있다.