Documentando nuestra API: Guía manual de OpenAPI
Cuando programábamos aplicaciones monolíticas clásicas, la propia interfaz HTML (los <form>, los <input>) servía como documentación implícita: el usuario veía una caja que decía "Nombre de la vía" y sabía qué rellenar.
En el mundo de las APIs RESTful, no hay interfaz visual por defecto. Si le pasamos a un compañero de frontend la URL http://localhost:5000/api/v1/vias, su primera pregunta será: "¿Qué verbos HTTP acepta? ¿Qué JSON te tengo que enviar en el POST? ¿Qué me vas a devolver?".
Para resolver esto nació OpenAPI Specification (OAS) (anteriormente conocido como Swagger). Es un estándar de la industria para describir de forma agnóstica (independiente del lenguaje de programación) cómo funciona una API REST.
Vamos a ver cómo crear un archivo de especificación OpenAPI desde cero, a mano, utilizando el formato YAML (que es mucho más legible para los humanos que escribirlo en JSON).
La Anatomía de un documento OpenAPI
Un documento OpenAPI versión 3 (la más estándar actualmente) se divide en grandes bloques funcionales. Vamos a construir el archivo openapi.yaml paso a paso basándonos en nuestro ejemplo del rocódromo.
1. Metadatos básicos y Servidores (openapi, info, servers)
Lo primero es definir qué versión del estándar usamos, dar nombre a nuestra API y especificar dónde está alojada.
openapi: 3.0.3
info:
title: API del Rocódromo Local
description: Servicio para gestionar las vías y bloques de escalada.
version: 1.0.0
servers:
- url: http://localhost:5000/api/v1
description: Servidor local de desarrollo
- url: https://api.rocodromolocal.com/v1
description: Servidor de producción
2. Los Modelos de Datos (components)
¿Recuerdas los DTOs que hicimos con Pydantic? Aquí es donde los traducimos para que el resto del mundo los entienda. En OpenAPI, esto se define dentro de components/schemas.
Vamos a definir cómo es el objeto ViaDTO que devolvemos, y el NewViaDTO que pedimos para crear una vía.
components:
schemas:
# Este equivale a nuestro ViaDTO de salida
Via:
type: object
required: # Campos que siempre van a estar presentes
- id
- nombre
- grado
- user_id
properties:
id:
type: integer
example: 101
nombre:
type: string
example: "El Techo del Mono"
grado:
type: string
example: "6b+"
altura:
type: number
format: float # Para especificar que permite decimales
example: 15.5
desplome:
type: boolean
default: true
user_id:
type: string
format: uuid
# Este equivale a nuestro NewViaDTO de entrada (sin ID)
NewVia:
type: object
required:
- nombre
- grado
- user_id
properties:
nombre:
type: string
grado:
type: string
altura:
type: number
desplome:
type: boolean
user_id:
type: string
Fíjate en el uso de example. Es una práctica fundamental para que quien lea la documentación vea datos reales.
3. Los Endpoints o Rutas (paths)
Ahora que hemos definido nuestros "tipos de datos", vamos a documentar las rutas reales de nuestro controlador de Flask.
Documentaremos dos operaciones: un GET para obtener todas las vías y un POST para crear una nueva. Usaremos la palabra clave $ref (referencia) para apuntar a los esquemas que definimos en el paso anterior y no repetir código.
paths:
/vias:
# Definición del método GET
get:
summary: Obtiene la lista de todas las vías
tags:
- Vías # Sirve para agrupar visualmente en la documentación
responses:
"200":
description: Lista de vías recuperada con éxito.
content:
application/json:
schema:
type: array # Indicamos que devolvemos una lista
items:
$ref: "#/components/schemas/Via" # Referencia al componente Via
# Definición del método POST
post:
summary: Crea una nueva vía en el sistema
tags:
- Vías
requestBody:
description: Objeto JSON con los datos de la nueva vía
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewVia" # Referencia al componente NewVia
responses:
"201":
description: Vía creada exitosamente. Devuelve la vía con su nuevo ID.
content:
application/json:
schema:
$ref: "#/components/schemas/Via"
"400":
description: Error de validación en los datos enviados.
El resultado final y cómo visualizarlo
Si juntas todos estos bloques respetando la indentación de YAML (espacios, no tabuladores), tendrás un archivo openapi.yaml completo y profesional.
Pero escribir código YAML a ciegas no es muy gratificante. La verdadera magia de OpenAPI es que existen herramientas visuales que leen este archivo y generan una página web interactiva preciosa, donde los desarrolladores no solo pueden leer la documentación, ¡sino ejecutar peticiones de prueba directamente desde la pantalla!
¿Cómo probar lo que acabamos de escribir?
- Abre tu navegador y ve a editor.swagger.io.
- Borra el código de ejemplo que viene por defecto en el panel izquierdo.
- Pega nuestro código YAML.
- Magia: En el panel derecho verás renderizada automáticamente tu interfaz visual, agrupada por métodos, con ejemplos interactivos.
Integrando Swagger UI con Flasgger
Escribir el openapi.yaml a mano está genial para entender el estándar, pero copiar y pegar el código en el editor online de Swagger no es práctico para el día a día. Lo ideal es que nuestra propia API sirva su documentación.
Para ello, vamos a utilizar Flasgger, una extensión de Flask que empaqueta la interfaz gráfica de Swagger UI y la conecta con nuestras rutas.
1. Instalación
Primero, necesitamos instalar la librería en nuestro entorno virtual:
pip install flasgger
2. Configurando Flasgger para leer nuestro YAML maestro
Flasgger permite documentar las rutas de muchas maneras (por ejemplo, escribiendo el YAML directamente en los comentarios o docstrings de cada función).
Sin embargo, como nosotros ya hemos hecho el trabajo duro de crear un archivo openapi.yaml centralizado y completo, la forma más limpia es decirle a Flasgger que simplemente lea ese archivo y lo renderice.
En el archivo principal de tu aplicación (normalmente app.py), configuramos la extensión así:
from flask import Flask, jsonify
from flasgger import Swagger
app = Flask(__name__)
# 1. Configuración básica para indicar que usamos el estándar OpenAPI 3
app.config['SWAGGER'] = {
'title': 'API del Rocódromo Local',
'uiversion': 3,
'openapi': '3.0.3'
}
# 2. Inicializamos Swagger indicándole dónde está nuestro archivo YAML manual
# Asumimos que openapi.yaml está en la misma carpeta que app.py
swagger = Swagger(app, template_file='openapi.yaml')