Saltar al contenido

Parámetros de Query y Validaciones de Strings

FastAPI te permite declarar información adicional y validación para tus parámetros.

Tomemos esta aplicación como ejemplo:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

El parámetro de query q es de tipo str | None, eso significa que es de tipo str pero también podría ser None, y de hecho, el valor por defecto es None, así que FastAPI sabrá que no es requerido.

Nota

FastAPI sabrá que el valor de q no es requerido por el valor por defecto = None.

Tener str | None permitirá que tu editor te dé mejor soporte y detecte errores.

Validación adicional

Vamos a asegurar que aunque q sea opcional, cuando se proporcione, su longitud no exceda los 50 caracteres.

Importar Query y Annotated

Para lograrlo, primero importa:

  • Query de fastapi
  • Annotated de typing
from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Nota

FastAPI añadió soporte para Annotated (y empezó a recomendarlo) en la versión 0.95.0.

Si tienes una versión anterior, obtendrías errores al intentar usar Annotated.

Asegúrate de Actualizar la versión de FastAPI a al menos 0.95.1 antes de usar Annotated.

Usar Annotated en el tipo del parámetro q

¿Recuerdas que te dije antes que Annotated se puede usar para añadir metadatos a tus parámetros en la Introducción a Tipos de Python?

Ahora es el momento de usarlo con FastAPI. 🚀

Teníamos esta anotación de tipo:

q: str | None = None

Lo que haremos es envolver eso con Annotated, así que queda:

q: Annotated[str | None] = None

Ambas versiones significan lo mismo, q es un parámetro que puede ser str o None, y por defecto, es None.

Ahora pasemos a lo divertido. 🎉

Añadir Query a Annotated en el parámetro q

Ahora que tenemos este Annotated donde podemos poner más información (en este caso alguna validación adicional), añade Query dentro de Annotated, y configura el parámetro max_length a 50:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Ten en cuenta que el valor por defecto sigue siendo None, así que el parámetro sigue siendo opcional.

Pero ahora, teniendo Query(max_length=50) dentro de Annotated, le estamos diciendo a FastAPI que queremos que tenga validación adicional para este valor, queremos que tenga máximo 50 caracteres. 😎

Consejo

Aquí estamos usando Query() porque este es un parámetro de query. Más adelante veremos otros como Path(), Body(), Header(), y Cookie(), que también aceptan los mismos argumentos que Query().

FastAPI ahora:

  • Validar los datos asegurando que la longitud máxima sea de 50 caracteres
  • Mostrar un error claro para el cliente cuando los datos no son válidos
  • Documentar el parámetro en el esquema OpenAPI de la path operation (para que aparezca en la documentación automática UI)

Alternativa (antigua): Query como valor por defecto

Versiones anteriores de FastAPI (antes de 0.95.0) requerían que usaras Query como valor por defecto de tu parámetro, en lugar de ponerlo en Annotated, hay una alta probabilidad de que veas código usándolo por ahí, así que te lo explicaré.

Consejo

Para código nuevo y siempre que sea posible, usa Annotated como se explicó arriba. Hay múltiples ventajas (explicadas abajo) y ninguna desventaja. 🍰

Así es como usarías Query() como valor por defecto de tu parámetro de función, configurando el parámetro max_length a 50:

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes
from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Como en este caso (sin usar Annotated) tenemos que reemplazar el valor por defecto None en la función con Query(), ahora necesitamos configurar el valor por defecto con el parámetro Query(default=None), sirve para el mismo propósito de definir ese valor por defecto (al menos para FastAPI).

Así que:

q: str | None = Query(default=None)

...hace el parámetro opcional, con un valor por defecto de None, igual que:

q: str | None = None

Pero la versión con Query lo declara explícitamente como un parámetro de query.

Luego, podemos pasar más parámetros a Query. En este caso, el parámetro max_length que aplica a strings:

q: str | None = Query(default=None, max_length=50)

Esto validará los datos, mostrará un error claro cuando los datos no sean válidos, y documentará el parámetro en el esquema OpenAPI de la path operation.

Query como valor por defecto o en Annotated

Ten en cuenta que cuando usas Query dentro de Annotated no puedes usar el parámetro default para Query.

En su lugar, usa el valor por defecto real del parámetro de la función. De lo contrario, sería inconsistente.

Por ejemplo, esto no está permitido:

q: Annotated[str, Query(default="rick")] = "morty"

...porque no está claro si el valor por defecto debería ser "rick" o "morty".

Así que, usarías (preferiblemente):

q: Annotated[str, Query()] = "rick"

...o en bases de código más antiguas encontrarás:

q: str = Query(default="rick")

Ventajas de Annotated

Usar Annotated es lo recomendado en lugar del valor por defecto en los parámetros de función, es mejor por múltiples razones. 🤓

El valor por defecto del parámetro de función es el valor por defecto real, eso es más intuitivo con Python en general. 😌

Podrías llamar a esa misma función en otros lugares sin FastAPI, y funcionaría como se espera. Si hay un parámetro requerido (sin un valor por defecto), tu editor te avisará con un error, Python también se quejará si lo ejecutas sin pasar el parámetro requerido.

Cuando no usas Annotated y en su lugar usas el estilo (antiguo) de valor por defecto, si llamas a esa función sin FastAPI en otros lugares, tienes que recordar pasar los argumentos a la función para que funcione correctamente, de lo contrario los valores serán diferentes de lo que esperas (ej. QueryInfo o algo similar en lugar de str). Y tu editor no se quejará, y Python no se quejará al ejecutar esa función, solo cuando las operaciones internas den error.

Como Annotated puede tener más de una anotación de metadatos, ahora podrías incluso usar la misma función con otras herramientas, como Typer. 🚀

Añadir más validaciones

También puedes añadir un parámetro min_length:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, min_length=3, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Añadir expresiones regulares

Puedes definir un patrón de expresión regular que el parámetro debe cumplir:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None, min_length=3, max_length=50, pattern="^fixedquery$"
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Este patrón de expresión regular específico verifica que el valor del parámetro recibido:

  • ^: empieza con los siguientes caracteres, no tiene caracteres antes.
  • fixedquery: tiene el valor exacto fixedquery.
  • $: termina ahí, no tiene más caracteres después de fixedquery.

Si te sientes perdido con todas estas ideas de "expresión regular", no te preocupes. Son un tema difícil para muchas personas. Todavía puedes hacer muchas cosas sin necesitar expresiones regulares.

Ahora sabes que cuando las necesites puedes usarlas en FastAPI.

Valores por defecto

Puedes, por supuesto, usar valores por defecto distintos a None.

Digamos que quieres declarar el parámetro de query q para que tenga un min_length de 3, y que tenga un valor por defecto de "fixedquery":

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(default="fixedquery", min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Nota

Tener un valor por defecto de cualquier tipo, incluyendo None, hace que el parámetro sea opcional (no requerido).

Parámetros requeridos

Cuando no necesitamos declarar más validaciones o metadatos, podemos hacer que el parámetro de query q sea requerido simplemente no declarando un valor por defecto, así:

q: str

en lugar de:

q: str | None = None

Pero ahora lo estamos declarando con Query, por ejemplo así:

q: Annotated[str | None, Query(min_length=3)] = None

Así que, cuando necesites declarar un valor como requerido mientras usas Query, puedes simplemente no declarar un valor por defecto:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Requerido, puede ser None

Puedes declarar que un parámetro puede aceptar None, pero que sigue siendo requerido. Esto forzaría a los clientes a enviar un valor, incluso si el valor es None.

Para hacer eso, puedes declarar que None es un tipo válido pero simplemente no declarar un valor por defecto:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Lista de parámetros de query / múltiples valores

Cuando defines un parámetro de query explícitamente con Query también puedes declararlo para recibir una lista de valores, o dicho de otra manera, para recibir múltiples valores.

Por ejemplo, para declarar un parámetro de query q que puede aparecer múltiples veces en la URL, puedes escribir:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list[str] | None, Query()] = None):
    query_items = {"q": q}
    return query_items
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list[str] | None = Query(default=None)):
    query_items = {"q": q}
    return query_items

Entonces, con una URL como:

http://localhost:8000/items/?q=foo&q=bar

recibirías los múltiples valores de los parámetros de query q (foo y bar) en una list de Python dentro de tu función de path operation, en el parámetro de función q.

Así, la respuesta a esa URL sería:

{
  "q": [
    "foo",
    "bar"
  ]
}

Consejo

Para declarar un parámetro de query con un tipo list, como en el ejemplo anterior, necesitas usar explícitamente Query, de lo contrario sería interpretado como un cuerpo de request.

La documentación interactiva de la API se actualizará en consecuencia, para permitir múltiples valores:

Lista de parámetros de query / múltiples valores con valores por defecto

También puedes definir una list de valores por defecto si no se proporciona ninguno:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):
    query_items = {"q": q}
    return query_items
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list[str] = Query(default=["foo", "bar"])):
    query_items = {"q": q}
    return query_items

Si vas a:

http://localhost:8000/items/

el valor por defecto de q será: ["foo", "bar"] y tu respuesta será:

{
  "q": [
    "foo",
    "bar"
  ]
}

Usando solo list

También puedes usar list directamente en lugar de list[str]:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
    query_items = {"q": q}
    return query_items
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list = Query(default=[])):
    query_items = {"q": q}
    return query_items

Nota

Ten en cuenta que en este caso, FastAPI no verificará el contenido de la lista.

Por ejemplo, list[int] verificaría (y documentaría) que los contenidos de la lista son enteros. Pero list sola no lo haría.

Declarar más metadatos

Puedes añadir más información sobre el parámetro.

Esa información será incluida en el OpenAPI generado y usada por las interfaces de usuario de la documentación y herramientas externas.

Nota

Ten en cuenta que diferentes herramientas pueden tener diferentes niveles de soporte para OpenAPI.

Algunas podrían no mostrar toda la información extra declarada todavía, aunque en la mayoría de los casos, la característica faltante ya está planificada para desarrollo.

Puedes añadir un title:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(title="Query string", min_length=3)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(default=None, title="Query string", min_length=3),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Y una description:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None,
        Query(
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Alias de parámetros

Imagina que quieres que el parámetro sea item-query.

Como en:

http://127.0.0.1:8000/items/?item-query=foobaritems

Pero item-query no es un nombre de variable válido en Python.

Lo más cercano sería item_query.

Pero todavía necesitas que sea exactamente item-query...

Entonces puedes declarar un alias, y ese alias es lo que se usará para encontrar el valor del parámetro:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(alias="item-query")] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, alias="item-query")):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Deprecar parámetros

Ahora digamos que ya no te gusta este parámetro.

Tienes que dejarlo ahí un tiempo porque hay clientes usándolo, pero quieres que la documentación lo muestre claramente como deprecado.

Entonces pasa el parámetro deprecated=True a Query:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None,
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        alias="item-query",
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
        max_length=50,
        pattern="^fixedquery$",
        deprecated=True,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

La documentación lo mostrará así:

Excluir parámetros de OpenAPI

Para excluir un parámetro de query del esquema OpenAPI generado (y por ende, de los sistemas de documentación automática), configura el parámetro include_in_schema de Query a False:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: str | None = Query(default=None, include_in_schema=False),
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

Validación Personalizada

Podría haber casos donde necesites hacer alguna validación personalizada que no se pueda hacer con los parámetros mostrados arriba.

En esos casos, puedes usar una función validadora personalizada que se aplica después de la validación normal (ej. después de validar que el valor es un str).

Puedes lograr eso usando AfterValidator de Pydantic dentro de Annotated.

Consejo

Pydantic también tiene BeforeValidator y otros. 🤓

Por ejemplo, este validador personalizado verifica que el ID del item empiece con isbn- para un número de libro ISBN o con imdb- para un ID de URL de película IMDB:

import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

Nota

Esto está disponible con Pydantic versión 2 o superior. 😎

Consejo

Si necesitas hacer cualquier tipo de validación que requiera comunicarse con algún componente externo, como una base de datos u otra API, deberías usar Dependencias de FastAPI, aprenderás sobre ellas más adelante.

Estos validadores personalizados son para cosas que se pueden verificar con únicamente los mismos datos proporcionados en la request.

Entender ese Código

El punto importante es simplemente usar AfterValidator con una función dentro de Annotated. Siéntete libre de saltarte esta parte. 🤸


Pero si tienes curiosidad sobre este ejemplo de código específico y todavía estás entretenido, aquí hay algunos detalles extra.

String con value.startswith()

¿Lo notaste? Un string usando value.startswith() puede tomar una tupla, y verificará cada valor en la tupla:

# Code above omitted 👆

def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id

# Code below omitted 👇
👀 Vista previa del archivo completo
import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

Un Item Aleatorio

Con data.items() obtenemos un objeto iterable con tuplas que contienen la clave y el valor de cada item del diccionario.

Convertimos este objeto iterable en una list propiamente dicha con list(data.items()).

Luego con random.choice() podemos obtener un valor aleatorio de la lista, así que, obtenemos una tupla con (id, name). Será algo como ("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy").

Luego asignamos esos dos valores de la tupla a las variables id y name.

Así, si el usuario no proporcionó un ID de item, seguirá recibiendo una sugerencia aleatoria.

...hacemos todo esto en una sola línea simple. 🤯 ¿No amas Python? 🐍

# Code above omitted 👆

@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
👀 Vista previa del archivo completo
import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

Resumen

Puedes declarar validaciones adicionales y metadatos para tus parámetros.

Validaciones y metadatos genéricos:

  • alias
  • title
  • description
  • deprecated

Validaciones específicas para strings:

  • min_length
  • max_length
  • pattern

Validaciones personalizadas usando AfterValidator.

En estos ejemplos viste cómo declarar validaciones para valores str.

Consulta los próximos capítulos para aprender cómo declarar validaciones para otros tipos, como números.