Aller au contenu

Corps - Modèles imbriqués

🌐 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

Avec FastAPI, vous pouvez définir, valider, documenter et utiliser des modèles imbriqués à n'importe quelle profondeur (grâce à Pydantic).

Déclarer des champs de liste

Vous pouvez définir un attribut comme étant un sous-type. Par exemple, une list Python :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list = []


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

Cela fera de tags une liste, bien que le type des éléments de la liste ne soit pas déclaré.

Champs de liste avec paramètre de type

Mais Python a une manière spécifique de déclarer des listes avec des types internes, ou « paramètres de type » :

Déclarer une list avec un paramètre de type

Pour déclarer des types qui ont des paramètres de type (types internes), comme list, dict, tuple, passez le(s) type(s) interne(s) comme « paramètres de type » à l'aide de crochets : [ et ]

my_list: list[str]

C'est simplement la syntaxe Python standard pour les déclarations de type.

Utilisez cette même syntaxe standard pour les attributs de modèles avec des types internes.

Ainsi, dans notre exemple, nous pouvons faire de tags spécifiquement une « liste de chaînes » :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

Types set

Mais en y réfléchissant, nous réalisons que les tags ne devraient pas se répéter, ce seraient probablement des chaînes uniques.

Et Python dispose d'un type de données spécial pour les ensembles d'éléments uniques, le set.

Nous pouvons alors déclarer tags comme un set de chaînes :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

Avec cela, même si vous recevez une requête contenant des doublons, elle sera convertie en un set d'éléments uniques.

Et chaque fois que vous renverrez ces données, même si la source contenait des doublons, elles seront renvoyées sous la forme d'un set d'éléments uniques.

Elles seront également annotées / documentées en conséquence.

Modèles imbriqués

Chaque attribut d'un modèle Pydantic a un type.

Mais ce type peut lui-même être un autre modèle Pydantic.

Ainsi, vous pouvez déclarer des « objets » JSON profondément imbriqués avec des noms d'attributs, des types et des validations spécifiques.

Tout cela, de manière arbitrairement imbriquée.

Définir un sous-modèle

Par exemple, nous pouvons définir un modèle Image :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

Utiliser le sous-modèle comme type

Nous pouvons ensuite l'utiliser comme type d'un attribut :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

Cela signifie que FastAPI attendrait un corps similaire à :

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": ["rock", "metal", "bar"],
    "image": {
        "url": "http://example.com/baz.jpg",
        "name": "The Foo live"
    }
}

Là encore, avec cette simple déclaration, avec FastAPI vous obtenez :

  • Prise en charge par l'éditeur (autocomplétion, etc.), même pour les modèles imbriqués
  • Conversion des données
  • Validation des données
  • Documentation automatique

Types spéciaux et validation

Outre les types singuliers normaux comme str, int, float, etc. vous pouvez utiliser des types singuliers plus complexes qui héritent de str.

Pour voir toutes les options dont vous disposez, consultez l’aperçu des types de Pydantic. Vous verrez quelques exemples au chapitre suivant.

Par exemple, comme dans le modèle Image nous avons un champ url, nous pouvons le déclarer comme instance de HttpUrl de Pydantic au lieu de str :

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

La chaîne sera vérifiée comme URL valide et documentée comme telle dans JSON Schema / OpenAPI.

Attributs avec des listes de sous-modèles

Vous pouvez également utiliser des modèles Pydantic comme sous-types de list, set, etc. :

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    images: list[Image] | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

Cela attendra (convertira, validera, documentera, etc.) un corps JSON comme :

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": [
        "rock",
        "metal",
        "bar"
    ],
    "images": [
        {
            "url": "http://example.com/baz.jpg",
            "name": "The Foo live"
        },
        {
            "url": "http://example.com/dave.jpg",
            "name": "The Baz"
        }
    ]
}

Info

Remarquez que la clé images contient maintenant une liste d'objets image.

Modèles profondément imbriqués

Vous pouvez définir des modèles imbriqués à une profondeur arbitraire :

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    images: list[Image] | None = None


class Offer(BaseModel):
    name: str
    description: str | None = None
    price: float
    items: list[Item]


@app.post("/offers/")
async def create_offer(offer: Offer):
    return offer

Info

Remarquez que Offer a une liste d’Item, qui à leur tour ont une liste optionnelle d’Image.

Corps de listes pures

Si la valeur de premier niveau du corps JSON attendu est un array JSON (une list Python), vous pouvez déclarer le type dans le paramètre de la fonction, de la même manière que dans les modèles Pydantic :

images: list[Image]

comme :

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
    return images

Bénéficier de la prise en charge de l'éditeur partout

Et vous bénéficiez de la prise en charge de l'éditeur partout.

Même pour les éléments à l'intérieur des listes :

Vous ne pourriez pas obtenir ce type de prise en charge de l'éditeur si vous travailliez directement avec des dict au lieu de modèles Pydantic.

Mais vous n'avez pas à vous en soucier non plus, les dict entrants sont convertis automatiquement et votre sortie est également convertie automatiquement en JSON.

Corps de dict arbitraires

Vous pouvez également déclarer un corps comme un dict avec des clés d’un certain type et des valeurs d’un autre type.

De cette façon, vous n'avez pas besoin de savoir à l'avance quels sont les noms de champs/attributs valides (comme ce serait le cas avec des modèles Pydantic).

Cela serait utile si vous voulez recevoir des clés que vous ne connaissez pas à l'avance.


Un autre cas utile est lorsque vous souhaitez avoir des clés d'un autre type (par exemple int).

C'est ce que nous allons voir ici.

Dans ce cas, vous accepteriez n'importe quel dict tant qu'il a des clés int avec des valeurs float :

from fastapi import FastAPI

app = FastAPI()


@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
    return weights

Astuce

Gardez à l'esprit que JSON ne prend en charge que des str comme clés.

Mais Pydantic dispose d'une conversion automatique des données.

Cela signifie que, même si vos clients d'API ne peuvent envoyer que des chaînes comme clés, tant que ces chaînes contiennent des entiers purs, Pydantic les convertira et les validera.

Et le dict que vous recevez dans weights aura en réalité des clés int et des valeurs float.

Récapitulatif

Avec FastAPI, vous bénéficiez de la flexibilité maximale fournie par les modèles Pydantic, tout en gardant votre code simple, concis et élégant.

Mais avec tous les avantages :

  • Prise en charge par l'éditeur (autocomplétion partout !)
  • Conversion des données (a.k.a. parsing / sérialisation)
  • Validation des données
  • Documentation des schémas
  • Documentation automatique