Saltar a contenido

Documentación de la API (Parte 2)#


iThome Ironman 2024

Esta es la entrega número 18 de la serie de tutoriales de Django Ninja.

En el artículo anterior, exploramos algunas configuraciones importantes de Django Ninja que afectan la presentación de la documentación de la API. Son habilidades básicas de la documentación automatizada de la API que no se deben ignorar.

¡Pero esto no es suficiente! Queremos que esta documentación sea aún más vívida y resulte clara y fácil de entender al leerla.

La clave de ello reside en los ejemplos de datos dentro de la documentación de la API. Un buen ejemplo permite entender todo de un vistazo, reduciendo eficazmente el tiempo de comprensión y reflexión.

Este artículo explicará cómo utilizar la configuración de Field en Pydantic para mejorar integralmente la claridad y legibilidad de la documentación de la API. Exploraremos cómo añadir ejemplos llenos de vida a los documentos generados automáticamente, haciéndolos más cercanos a la realidad.

Todos los cambios de código de este artículo se pueden consultar en este PR.

Proyecto de ejemplo en GitHub#

👉 Django-Ninja-Tutorial


El rol de Pydantic en Django Ninja#

Pydantic es un paquete que realiza la validación y serialización de datos, ampliamente utilizado en marcos como FastAPI y Django Ninja.

En Django Ninja, Pydantic se utiliza para definir los Schema, los cuales determinan cómo la API procesa los datos en las solicitudes y respuestas HTTP, convirtiéndolos automáticamente en documentación conforme al estándar OpenAPI.

La fortaleza de Pydantic radica en que no solo valida datos, sino que también puede proporcionar explicaciones adicionales, ejemplos y valores por defecto para los campos de la documentación mediante la configuración de Field.

Estas configuraciones detalladas se reflejarán automáticamente en la documentación de la API convertida, ayudando a los desarrolladores a comprender mejor el comportamiento y significado de la API.

Pydantic Field#

El Field de Pydantic es una herramienta potente que sirve para ofrecer más información detallada sobre cada campo de datos, como títulos, descripciones, ejemplos y valores por defecto.

Estas configuraciones no solo ayudan a la validación de datos, sino que también mejoran drásticamente la legibilidad de la documentación de la API. A continuación se presentan algunos parámetros comunes de Field:

  • title: Establece un título para el campo, ayudando a los desarrolladores a comprender rápidamente la función de dicho campo.
  • description: Ofrece una descripción del campo para que se entiendan mejor sus usos y limitaciones.
  • examples: Establece valores de ejemplo, ayudando a los desarrolladores a comprender intuitivamente los formatos de entrada y salida de la API.
  • default: Primer parámetro posicional, proporciona el valor por defecto del campo. Cuando la entrada no proporcione dicho valor, se usará automáticamente el valor por defecto.

Utilizar bien estos parámetros permite generar documentación de API de alta calidad.

El punto de equilibrio entre código y documentación#

¡Sin embargo! Debemos acercarnos un poco a la «realidad»; si cada API requiriera que escribieras tanto contenido, podría resultar en una carga excesiva para el desarrollador.

Además, al usar una gran cantidad de parámetros, la documentación ciertamente se ve mejor, ¡pero el código que la genera terminará siendo larguísimo!

Debemos encontrar un punto de equilibrio que brinde suficiente información sin volver el código demasiado farragoso.

Desde esta perspectiva, considero que los dos parámetros más importantes son default y examples, ¡especialmente este último!

Por ello, este artículo se enfocará en presentar estos dos, lo que no solo concentra el aprendizaje, sino que se adapta mejor a mi rutina diaria de desarrollo.


Documentación oficial y código fuente#

Si deseas conocer más sobre los parámetros y el uso de Pydantic Field, debes consultar la documentación oficial de Pydantic, no la de Django Ninja.

En la documentación de Django Ninja no hay una sección dedicada a explicar el uso de Field. Esto se debe a que Field es en realidad una funcionalidad de Pydantic, no algo exclusivo de Django Ninja.

Sin embargo, si vas a leer esa documentación, es posible que descubras que su explicación sobre todos los parámetros de Field tampoco es sumamente detallada.

Para conocer todos los parámetros disponibles, me parece que revisar el código fuente es lo más rápido. Y deducir su uso a partir de las pistas de tipo (type hints) de la firma de la función (sí, Field es una función) también resulta una excelente alternativa.


A continuación, explicaremos cómo usar los parámetros examples y default de Pydantic Field para hacer la documentación de la API más vívida y rigurosa.

Añadir «ejemplos» a la documentación de la API#

En la entrega anterior mencionamos las deficiencias de la documentación actual de la API, donde el problema de la «falta de ejemplos reales» aún no se ha resuelto.

A continuación se muestra el ejemplo de respuesta para «obtener la información de una sola publicación» en la documentación:

{
    "id": 0,
    "title": "string",
    "content": "string",
    "author": {
        "id": 0,
        "username": "string",
        "email": "string"
    },
    "created_at": "2024-09-22T08:58:55.960Z",
    "updated_at": "2024-09-22T08:58:55.960Z"
}

Ni 0 ni "string" pueden considerarse buenos ejemplos de documentación: ninguno es lo suficientemente real.

Ahora añadiremos ejemplos al Schema de respuesta; el código es el siguiente:

class _AuthorInfo(Schema):
    id: int = Field(examples=[1])
    username: str = Field(examples=['Alice'])
    email: str = Field(examples=['[email protected]'])

class PostResponse(Schema):
    id: int = Field(examples=[1])
    title: str = Field(examples=['Ninja is awesome!'])
    content: str = Field(examples=['This is my first post.'])
    author: _AuthorInfo
    created_at: datetime = Field(examples=['2021-01-01T00:00:00Z'])
    updated_at: datetime = Field(examples=['2021-01-01T00:00:00Z'])
    ...

Aquí hay dos puntos clave.

Punto clave 1: el parámetro examples#

Solo he utilizado el parámetro examples, ya que es lo más sencillo y los ejemplos son realmente una parte muy importante de la documentación.

Además, este examples tiene su miga; si lo escribes como example, por ejemplo:

class PostResponse(Schema):
    id: int = Field(example=1)

En la práctica también funciona normalmente, pero Mypy te advertirá:

Unexpected keyword argument "example" for "Field"; did you mean "examples"?

Así es, porque en el Pydantic v2 actual, Field solo tiene el parámetro examples. example debía de ser la forma de trabajar en Pydantic v1, y Django Ninja aún mantiene la compatibilidad con ambos.

Pensando en el futuro, se recomienda seguir usando examples, no solo para evitar las advertencias de Mypy, sino también para mantenerse en sintonía con la versión más reciente de Pydantic.

Punto clave 2: ejemplos en Schema anidados#

Para ejemplos en Schema anidados, basta con añadir Field en el Schema de nivel inferior. La capa que lo referencia no necesita declararlo:

class _AuthorInfo(Schema):  # Este es el nivel inferior anidado, requiere escribir Field
    id: int = Field(examples=[1])
    username: str = Field(examples=['Alice'])
    email: str = Field(examples=['[email protected]'])

class PostResponse(Schema):
    ...
    author: _AuthorInfo  # No hace falta volver a escribir Field
    ...

Efecto real#

Echemos un vistazo a la respuesta real en la documentación de la API; extraigo directamente el valor JSON de la página:

{
    "id": 1,
    "title": "Ninja is awesome!",
    "content": "This is my first post.",
    "author": {
        "id": 1,
        "username": "Alice",
        "email": "[email protected]"
    },
    "created_at": "2021-01-01T00:00:00Z",
    "updated_at": "2021-01-01T00:00:00Z"
}

En comparación con el 0 y "string" de antes, ¿no resulta mucho más vívido y fácil de leer?


El momento adecuado para usar el parámetro default#

A mi modo de ver, la mayor parte del tiempo no necesitamos definir valores por defecto.

Te sugiero que tampoco lo escribas así:

class PostResponse(Schema):
    id: int = 1
    ...

Aunque en la documentación también se muestre el valor de ejemplo como 1, en realidad esta forma de escribirlo es equivalente a la siguiente:

class PostResponse(Schema):
    id: int = Field(default=1)
    ...

Esto es en realidad definir un valor por defecto. Como dijimos antes, si el Schema se usa en una solicitud HTTP y el cliente no proporciona el valor de ese campo, Django Ninja usará automáticamente el valor por defecto.

Esto es muy probable que cause resultados inesperados.

El flujo correcto es: cuando el frontend no proporciona un valor, Django Ninja debería devolver una respuesta 422.

Por lo tanto, no necesitas en absoluto (ni deberías) definir un valor por defecto, salvo en la siguiente situación.

Usar el valor por defecto None en campos opcionales#

Personalmente recomiendo usar el parámetro default únicamente cuando el campo de la solicitud sea «opcional (optional)».

Y en este caso, el valor por defecto debería ser None.

Para demostrarlo, crearemos una nueva API: «Crear usuario» (es decir, registro de usuario). Esta API volverá a ser mencionada y mejorada repetidamente en las tutorías posteriores.

¿Recuerdas cómo en nuestro modelo User el campo bio era opcional?

class User(AbstractUser):
    email = models.EmailField(unique=True)  # email único obligatorio
    bio = models.TextField(null=True)  # Campo de biografía personal (opcional)
    ...

Por lo tanto, el Schema de solicitud de nuestra API es el siguiente; mira directamente la configuración del campo bio:

class CreateUserRequest(Schema):
    ...
    bio: str | None = Field(
        default=None,
        examples=['Hello, I am Alice.']
    )

La configuración default=None dentro de Field evita que la API falle cuando el cliente no ingrese ningún valor.

Además, presta atención al type hint bio: str | None; nunca omitas None, ya que afectará el resultado del renderizado de la documentación (este es el resultado con None):

Con None, la documentación de la API mostrará que el valor del campo es opcional (string | null).


Resumen y siguientes pasos#

Tras el aprendizaje y las mejoras de este capítulo, ¡nuestra documentación de la API ha alcanzado un nivel de 80 puntos! En la mayoría de los proyectos de desarrollo, esta calidad de documentación se puede considerar bastante sobresaliente.

A continuación entraremos al quinto capítulo: Validación de datos y manejo de errores.

Este capítulo cubrirá cómo implementar una validación de datos eficaz en Django Ninja, así como cómo manejar y responder elegantemente a diversas situaciones de error posibles.

Mediante estas técnicas, seremos capaces de construir API más robustas y confiables.