Aller au contenu

Utiliser des classes comme dépendances

🌐 Traduction par IA et humains

Cette traduction a été réalisée par une IA guidée par des humains. 🤝

Elle peut contenir des erreurs d'interprétation du sens original, ou paraître peu naturelle, etc. 🤖

Vous pouvez améliorer cette traduction en nous aidant à mieux guider le LLM d'IA.

Version anglaise

Avant d'aller plus loin dans le système d'Injection de dépendances, mettons à niveau l'exemple précédent.

Un dict de l'exemple précédent

Dans l'exemple précédent, nous renvoyions un dict depuis notre dépendance (« dependable ») :

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

Mais nous recevons alors un dict dans le paramètre commons de la fonction de chemin d'accès.

Et les éditeurs ne peuvent pas apporter beaucoup d'assistance (comme l'autocomplétion) pour les dict, car ils ne peuvent pas connaître leurs clés ni les types de valeurs.

Nous pouvons faire mieux ...

Ce qui fait d'un objet une dépendance

Jusqu'à présent, vous avez vu des dépendances déclarées sous forme de fonctions.

Mais ce n'est pas la seule manière de déclarer des dépendances (même si c'est probablement la plus courante).

L'élément clé est qu'une dépendance doit être un « callable ».

Un « callable » en Python est tout ce que Python peut « appeler » comme une fonction.

Ainsi, si vous avez un objet something (qui n'est peut‑être pas une fonction) et que vous pouvez « l'appeler » (l'exécuter) comme :

something()

ou

something(some_argument, some_keyword_argument="foo")

alors c'est un « callable ».

Utiliser des classes comme dépendances

Vous remarquerez que pour créer une instance d'une classe Python, vous utilisez la même syntaxe.

Par exemple :

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


fluffy = Cat(name="Mr Fluffy")

Dans ce cas, fluffy est une instance de la classe Cat.

Et pour créer fluffy, vous « appelez » Cat.

Donc, une classe Python est aussi un « callable ».

Ainsi, avec FastAPI, vous pouvez utiliser une classe Python comme dépendance.

Ce que FastAPI vérifie réellement, c'est qu'il s'agit d'un « callable » (fonction, classe ou autre) et des paramètres qui y sont définis.

Si vous passez un « callable » comme dépendance dans FastAPI, il en analysera les paramètres et les traitera de la même manière que les paramètres d'une fonction de chemin d'accès. Y compris les sous‑dépendances.

Cela s'applique également aux callables sans aucun paramètre. Comme ce serait le cas pour des fonctions de chemin d'accès sans paramètres.

Nous pouvons alors remplacer la dépendance « dependable » common_parameters ci‑dessus par la classe 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

Faites attention à la méthode __init__ utilisée pour créer l'instance de la classe :

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

... il a les mêmes paramètres que notre précédent 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

Ce sont ces paramètres que FastAPI utilisera pour « résoudre » la dépendance.

Dans les deux cas, il y aura :

  • Un paramètre de requête optionnel q qui est un str.
  • Un paramètre de requête skip qui est un int, avec une valeur par défaut de 0.
  • Un paramètre de requête limit qui est un int, avec une valeur par défaut de 100.

Dans les deux cas, les données seront converties, validées, documentées dans le schéma OpenAPI, etc.

Utiliser

Vous pouvez maintenant déclarer votre dépendance en utilisant cette classe.

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 appelle la classe CommonQueryParams. Cela crée une « instance » de cette classe et l'instance sera passée comme paramètre commons à votre fonction.

Annotation de type vs Depends

Remarquez que nous écrivons CommonQueryParams deux fois dans le code ci‑dessus :

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

Astuce

Privilégiez la version avec Annotated si possible.

commons: CommonQueryParams = Depends(CommonQueryParams)

Le dernier CommonQueryParams, dans :

... Depends(CommonQueryParams)

... est ce que FastAPI utilisera réellement pour savoir quelle est la dépendance.

C'est à partir de celui‑ci que FastAPI extraira les paramètres déclarés et c'est ce que FastAPI appellera effectivement.


Dans ce cas, le premier CommonQueryParams, dans :

commons: Annotated[CommonQueryParams, ...

Astuce

Privilégiez la version avec Annotated si possible.

commons: CommonQueryParams ...

... n'a aucune signification particulière pour FastAPI. FastAPI ne l'utilisera pas pour la conversion des données, la validation, etc. (car il utilise Depends(CommonQueryParams) pour cela).

Vous pourriez en fait écrire simplement :

commons: Annotated[Any, Depends(CommonQueryParams)]

Astuce

Privilégiez la version avec Annotated si possible.

commons = Depends(CommonQueryParams)

... comme dans :

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

Mais il est recommandé de déclarer le type ; ainsi, votre éditeur saura ce qui sera passé comme paramètre commons, et pourra vous aider avec l'autocomplétion, les vérifications de type, etc. :

Raccourci

Mais vous voyez qu'il y a ici de la duplication de code : nous écrivons CommonQueryParams deux fois :

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

Astuce

Privilégiez la version avec Annotated si possible.

commons: CommonQueryParams = Depends(CommonQueryParams)

FastAPI fournit un raccourci pour ces cas, lorsque la dépendance est spécifiquement une classe que FastAPI va « appeler » pour créer une instance de la classe elle‑même.

Pour ces cas précis, vous pouvez faire ce qui suit :

Au lieu d'écrire :

commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]

Astuce

Privilégiez la version avec Annotated si possible.

commons: CommonQueryParams = Depends(CommonQueryParams)

... vous écrivez :

commons: Annotated[CommonQueryParams, Depends()]

Astuce

Privilégiez la version avec Annotated si possible.

commons: CommonQueryParams = Depends()

Vous déclarez la dépendance comme type du paramètre et vous utilisez Depends() sans aucun paramètre, au lieu d'avoir à réécrire la classe entière à l'intérieur de Depends(CommonQueryParams).

Le même exemple ressemblerait alors à ceci :

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

... et FastAPI saura quoi faire.

Astuce

Si cela vous semble plus déroutant qu'utile, ignorez‑le, vous n'en avez pas besoin.

Ce n'est qu'un raccourci. Parce que FastAPI tient à vous aider à minimiser la duplication de code.