Python FastAPI 의존성 주입 (Depends 함수)

Munang·2022년 6월 6일
8

python

목록 보기
6/8

파이썬에서 의존성 주입은 Depends 함수를 통해 실행된다.

1. 의존성 주입이 뭔데?

A라는 코드가 있고, B라는 코드가 있다고 하자.
A는 반드시 B가 실행되어야 실행되는 흐름이라고 한다면, A와 B는 의존관계라고 할 수 있다. A를 상위계층이고, B를 하위계층이라고 본다면, A는 B의 수정에 따라서 반드시 수정되어야 한다. 즉, 상위-> 하위 사이에 강한 의존이 발생된다. 하위클래스의 동작이 상위 클래스에 영향을 주게 된다.

이러한 전통적인 안좋은 코드를 역전(반대의 방향으로)시키기 위한 원칙이 DIP원칙이다.

  • 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
  • 둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.

즉, 상위 -> 하위로 이어졌던 의존의 흐름을 하위 에서 상위의 방향으로 역전 시키는 것이다.

이렇게 되면, 상위 계층이 하위 계층에 영향을 받지 않고 코드가 수정될 수 있다.
이때, 상위 클래스는 하위 계층인 추상 클래스에 의존하게 된다. 그런데, 추상 클래스의 구현체가 여러개가 있다면? 혹은, 구현체가 하나만 존재한다 해도 이 구현체를 실제 코드에 맞춰줘야 하는데 이것을 의존성 주입이라고 한다.

1) 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스만 의존하고 있어야 한다.

2) 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.

3) 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

- 이일민, 토비의 스프링 3.1, 에이콘(2012), p114

스프링에 나온 의존성 주입을 충족하는 3가지 조건이다. FastAPI 에서는 이러한 행위를 Depends 함수로 구현했다.

2. Python - FastAPI 코드 예시

글로 설명하면 이해가 잘 안되기 때문에, 예시로 설명하겠다. 이 예시는, FastAPI 공식 페이지에서 가져왔다.

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

만약 read_users 함수에서 Depends 없이 함수 내부에 그대로 작성했다면, common_parameters 내부의 리턴 값이 바뀌었을 때 read_users함수의 내용도 바뀌었을 것이다.

하지만, 이를 구현체에만 의존하게끔 의존성을 주입시켰다.

이렇게 하면 코드의 반복이 줄고, 재사용 가능성이 커진다는 장점이 생긴다.

3. Depends

의존성 주입을 하게끔 Depends로 선언해주면 FastAPI는 이와 관련된 요청이 왔을 때,
Depends로 의존성을 주입받은 함수를 먼저 실행시킨 후, 메인 로직을 실행, 결과를 반환한다.

이때, Depends 함수를 실행하는 방법이 2가지 있는데 하나는 함수로 사용, 하나는 클래스를 이용하는 것이다.

1) 함수

from fastapi import Depends, FastAPI

app = FastAPI()

async def properties(offset: int = 0, limit: int = 100):
    return {"offset": offset, "limit": limit}

@app.get("/books/")
async def get_books(params: dict = Depends(properties)):
    return params

@app.get("/authors/")
async def get_authors(params: dict = Depends(properties)):
    return params

[실행 순서]

  • 올바른 매개변수를 사용하여 종속성("신뢰할 수 있는") 함수인 properties를 호출합니다.
    함수에서 결과를 가져옵니다.
    그 결과를 경로 작업 기능 의 매개변수인 params에 할당합니다 .

2) 클래스

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

이전과 동일하게, Depends함수 내에 인자로 클래스를 선언했다.
FastAPI 에서는 이렇게 하면 해당 클래스의 "인스턴스"가 생성되고 인스턴스가 commons함수에 매개변수로 전달된다.

이때, commons: CommonQueryParams = Depends(CommonQueryParams) 이 부분에서 CommonQueryParams가 중복되기 때문에 축약하는 방법을 제공한다.

1안)
async def read_items(commons=Depends(CommonQueryParams)):
유형 주석을 생략하는 방법이다.
파라미터: 유형 -> 이 부분이 생략 되었다.

2안)
async def read_items(commons: CommonQueryParams = Depends()):
종속성을 매개변수의 유형으로 선언하고 내부에 전체 클래스 를 다시 작성 하지 않아도 에 매개변수 없이 해당 함수의 매개변수에 Depends()대한 "기본" 값(다음 값 )을 사용합니다

1안 보다는 2안이 파라미터의 타입을 먼저 명시적으로 표현하기 때문에 권장되는 듯 하다.

1개의 댓글

comment-user-thumbnail
2023년 5월 8일

Depends가 뭐고 Depends()는 대체 왜 있는가 싶었는지가 궁금했는데, 잘 이해가 됐네요. 좋은 글 감사합니다.

답글 달기