You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.5 KiB

Cuerpo - Modelos Anidados

Con FastAPI, puedes definir, validar, documentar y usar modelos anidados de manera arbitraria (gracias a Pydantic).

Campos de lista

Puedes definir un atributo como un subtipo. Por ejemplo, una list en Python:

{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}

Esto hará que tags sea una lista, aunque no declare el tipo de los elementos de la lista.

Campos de lista con parámetro de tipo

Pero Python tiene una forma específica de declarar listas con tipos internos, o "parámetros de tipo":

Importar List de typing

En Python 3.9 y superior, puedes usar el list estándar para declarar estas anotaciones de tipo como veremos a continuación. 💡

Pero en versiones de Python anteriores a 3.9 (desde 3.6 en adelante), primero necesitas importar List del módulo typing estándar de Python:

{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}

Declarar una list con un parámetro de tipo

Para declarar tipos que tienen parámetros de tipo (tipos internos), como list, dict, tuple:

  • Si estás en una versión de Python inferior a 3.9, importa su versión equivalente del módulo typing
  • Pasa el/los tipo(s) interno(s) como "parámetros de tipo" usando corchetes: [ y ]

En Python 3.9 sería:

my_list: list[str]

En versiones de Python anteriores a 3.9, sería:

from typing import List

my_list: List[str]

Eso es toda la sintaxis estándar de Python para declaraciones de tipo.

Usa esa misma sintaxis estándar para atributos de modelos con tipos internos.

Así, en nuestro ejemplo, podemos hacer que tags sea específicamente una "lista de strings":

{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}

Tipos de conjunto

Pero luego pensamos en ello, y nos damos cuenta de que los tags no deberían repetirse, probablemente serían strings únicos.

Y Python tiene un tipo de datos especial para conjuntos de elementos únicos, el set.

Entonces podemos declarar tags como un conjunto de strings:

{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}

Con esto, incluso si recibes un request con datos duplicados, se convertirá en un conjunto de elementos únicos.

Y siempre que emitas esos datos, incluso si la fuente tenía duplicados, se emitirá como un conjunto de elementos únicos.

Y también se anotará/documentará en consecuencia.

Modelos Anidados

Cada atributo de un modelo Pydantic tiene un tipo.

Pero ese tipo puede ser en sí mismo otro modelo Pydantic.

Así que, puedes declarar "objetos" JSON anidados profundamente con nombres de atributos específicos, tipos y validaciones.

Todo eso, de manera arbitraria.

Definir un submodelo

Por ejemplo, podemos definir un modelo Image:

{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}

Usar el submodelo como tipo

Y luego podemos usarlo como el tipo de un atributo:

{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *}

Esto significaría que FastAPI esperaría un cuerpo similar a:

{
    "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"
    }
}

Nuevamente, haciendo solo esa declaración, con FastAPI obtienes:

  • Soporte de editor (autocompletado, etc.), incluso para modelos anidados
  • Conversión de datos
  • Validación de datos
  • Documentación automática

Tipos especiales y validación

Además de tipos singulares normales como str, int, float, etc., puedes usar tipos singulares más complejos que heredan de str.

Para ver todas las opciones que tienes, revisa el Overview de Tipos de Pydantic. Verás algunos ejemplos en el siguiente capítulo.

Por ejemplo, como en el modelo Image tenemos un campo url, podemos declararlo como una instance de HttpUrl de Pydantic en lugar de un str:

{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}

El string será verificado para ser una URL válida, y documentado en JSON Schema / OpenAPI como tal.

Atributos con listas de submodelos

También puedes usar modelos Pydantic como subtipos de list, set, etc.:

{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}

Esto esperará (convertirá, validará, documentará, etc.) un cuerpo JSON como:

{
    "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 | Información

Nota cómo la clave images ahora tiene una lista de objetos de imagen.

///

Modelos anidados profundamente

Puedes definir modelos anidados tan profundamente como desees:

{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *}

/// info | Información

Observa cómo Offer tiene una lista de Items, que a su vez tienen una lista opcional de Images

///

Cuerpos de listas puras

Si el valor superior del cuerpo JSON que esperas es un array JSON (una list en Python), puedes declarar el tipo en el parámetro de la función, al igual que en los modelos Pydantic:

images: List[Image]

o en Python 3.9 y superior:

images: list[Image]

como en:

{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}

Soporte de editor en todas partes

Y obtienes soporte de editor en todas partes.

Incluso para elementos dentro de listas:

No podrías obtener este tipo de soporte de editor si estuvieras trabajando directamente con dict en lugar de modelos Pydantic.

Pero tampoco tienes que preocuparte por ellos, los dicts entrantes se convierten automáticamente y tu salida se convierte automáticamente a JSON también.

Cuerpos de dicts arbitrarios

También puedes declarar un cuerpo como un dict con claves de algún tipo y valores de algún otro tipo.

De esta manera, no tienes que saber de antemano cuáles son los nombres válidos de campo/atributo (como sería el caso con modelos Pydantic).

Esto sería útil si deseas recibir claves que aún no conoces.


Otro caso útil es cuando deseas tener claves de otro tipo (por ejemplo, int).

Eso es lo que vamos a ver aquí.

En este caso, aceptarías cualquier dict siempre que tenga claves int con valores float:

{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}

/// tip | Consejo

Ten en cuenta que JSON solo admite str como claves.

Pero Pydantic tiene conversión automática de datos.

Esto significa que, aunque tus clientes de API solo pueden enviar strings como claves, mientras esos strings contengan enteros puros, Pydantic los convertirá y validará.

Y el dict que recibas como weights tendrá realmente claves int y valores float.

///

Resumen

Con FastAPI tienes la máxima flexibilidad proporcionada por los modelos Pydantic, manteniendo tu código simple, corto y elegante.

Pero con todos los beneficios:

  • Soporte de editor (¡autocompletado en todas partes!)
  • Conversión de datos (también conocido como parsing/serialización)
  • Validación de datos
  • Documentación del esquema
  • Documentación automática