Перейти до змісту

Класи як залежності

🌐 Переклад ШІ та людьми

Цей переклад виконано ШІ під керівництвом людей. 🤝

Можливі помилки через неправильне розуміння початкового змісту або неприродні формулювання тощо. 🤖

Ви можете покращити цей переклад, допомігши нам краще спрямовувати AI LLM.

Англійська версія

Перш ніж заглибитися у систему впровадження залежностей, оновімо попередній приклад.

dict з попереднього прикладу

У попередньому прикладі ми повертали dict із нашого «залежного»:

from typing import Annotated

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: Annotated[dict, Depends(common_parameters)]):
    return commons


@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return commons
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

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

Але тоді ми отримуємо dict у параметрі commons функції операції шляху.

І ми знаємо, що редактори не можуть надати багато підтримки (наприклад, автодоповнення) для dict, адже вони не знають їхніх ключів і типів значень.

Можна зробити краще…

Що робить об’єкт залежністю

Дотепер ви бачили залежності, оголошені як функції.

Але це не єдиний спосіб оголошувати залежності (хоча, ймовірно, найпоширеніший).

Ключовий момент у тому, що залежність має бути «викликаємим».

«Викликаємий» у Python - це будь-що, що Python може «викликати», як функцію.

Отже, якщо у вас є об’єкт something (який може й не бути функцією) і ви можете «викликати» його (виконати) так:

something()

або

something(some_argument, some_keyword_argument="foo")

тоді це «викликаємий».

Класи як залежності

Ви могли помітити, що для створення екземпляра класу Python ви використовуєте той самий синтаксис.

Наприклад:

class Cat:
    def __init__(self, name: str):
        self.name = name


fluffy = Cat(name="Mr Fluffy")

У цьому випадку fluffy - екземпляр класу Cat.

А для створення fluffy ви «викликаєте» Cat.

Отже, клас Python також є викликаємим.

Тож у FastAPI ви можете використовувати клас Python як залежність.

Насправді FastAPI перевіряє, що це «викликаємий» об’єкт (функція, клас чи щось інше) і які параметри в нього оголошені.

Якщо ви передаєте «викликаємий» як залежність у FastAPI, він проаналізує параметри цього об’єкта і обробить їх так само, як параметри для функції операції шляху. Включно з підзалежностями.

Це також стосується викликаємих без жодних параметрів. Так само, як і для функцій операцій шляху без параметрів.

Тоді ми можемо змінити залежність common_parameters вище на клас CommonQueryParams:

from typing import Annotated

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: Annotated[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
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

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 Annotated

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: Annotated[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
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

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 Annotated

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: Annotated[dict, Depends(common_parameters)]):
    return commons


@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return commons
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

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, який є str.
  • Параметр запиту skip, який є int, зі значенням за замовчуванням 0.
  • Параметр запиту limit, який є int, зі значенням за замовчуванням 100.

В обох випадках дані будуть перетворені, перевірені й задокументовані в схемі OpenAPI тощо.

Використання

Тепер ви можете оголосити залежність, використовуючи цей клас.

from typing import Annotated

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: Annotated[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
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

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 у вашу функцію.

Анотація типів проти Depends

Зверніть увагу, що вище ми двічі пишемо CommonQueryParams:

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

Порада

Надавайте перевагу варіанту з Annotated, якщо це можливо.

commons: CommonQueryParams = Depends(CommonQueryParams)

Останній CommonQueryParams у:

... Depends(CommonQueryParams)
  • це те, що FastAPI фактично використає, щоб дізнатися, яка залежність.

Саме з нього FastAPI витягне оголошені параметри і саме його FastAPI буде викликати.


У цьому випадку перший CommonQueryParams у:

commons: Annotated[CommonQueryParams, ...

Порада

Надавайте перевагу варіанту з Annotated, якщо це можливо.

commons: CommonQueryParams ...

…не має жодного особливого значення для FastAPI. FastAPI не використовуватиме його для перетворення даних, перевірки тощо (адже для цього використовується Depends(CommonQueryParams)).

Насправді ви могли б написати просто:

commons: Annotated[Any, Depends(CommonQueryParams)]

Порада

Надавайте перевагу варіанту з Annotated, якщо це можливо.

commons = Depends(CommonQueryParams)

…як у:

from typing import Annotated, Any

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: Annotated[Any, 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
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

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: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

Порада

Надавайте перевагу варіанту з Annotated, якщо це можливо.

commons: CommonQueryParams = Depends(CommonQueryParams)

FastAPI надає скорочення для таких випадків, коли залежність - це саме клас, який FastAPI «викличе», щоб створити екземпляр цього класу.

Для таких випадків ви можете зробити так:

Замість того щоб писати:

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

Порада

Надавайте перевагу варіанту з Annotated, якщо це можливо.

commons: CommonQueryParams = Depends(CommonQueryParams)

…напишіть:

commons: Annotated[CommonQueryParams, Depends()]

Порада

Надавайте перевагу варіанту з Annotated, якщо це можливо.

commons: CommonQueryParams = Depends()

Ви оголошуєте залежність як тип параметра і використовуєте Depends() без параметрів, замість того щоб вдруге писати повний клас усередині Depends(CommonQueryParams).

Той самий приклад виглядатиме так:

from typing import Annotated

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: Annotated[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
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

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 дбає про мінімізацію дублювання коду.