Tutorial Paso a Paso para Generar Documentación OpenAPI con Python (Usando PySwagger)

Rebecca Kovács

Rebecca Kovács

12 June 2025

Tutorial Paso a Paso para Generar Documentación OpenAPI con Python (Usando PySwagger)

Generar documentación de API completa y precisa es una parte crítica pero a menudo tediosa del desarrollo de software. La Especificación OpenAPI (anteriormente conocida como Swagger) ha surgido como el estándar de la industria para definir APIs RESTful. Proporciona un formato legible por máquina que permite tanto a humanos como a computadoras descubrir y comprender las capacidades de un servicio sin acceso al código fuente, la documentación o mediante la inspección del tráfico de red.1

Si bien muchos frameworks ofrecen plugins para generar especificaciones OpenAPI a partir de anotaciones de código (como docstrings), hay escenarios en los que podría necesitar un control más directo y programático sobre la creación de la especificación. Esto podría deberse a que está trabajando con un sistema heredado, un framework no estándar, o necesita generar una especificación para una API compuesta por múltiples microservicios.

Aquí es donde entra pyswagger. Es una potente biblioteca de Python que funciona como un conjunto de herramientas para OpenAPI. Si bien a menudo se utiliza como cliente de API para consumir servicios definidos por una especificación OpenAPI, su verdadero poder reside en su modelo de objetos, que le permite construir, manipular y validar una especificación de forma programática.

En este tutorial completo, recorreremos el proceso de uso de pyswagger para generar, de forma manual pero automática, una especificación OpenAPI 3.0 completa para una sencilla aplicación web Python construida con Flask. Construiremos la especificación desde cero, pieza por pieza, demostrando cómo los objetos de pyswagger se mapean directamente a los componentes del estándar OpenAPI. Al final, no solo tendrá un archivo openapi.json generado, sino también una interfaz de usuario de documentación interactiva y en vivo servida directamente desde su aplicación.

💡
¿Quiere una excelente herramienta de prueba de API que genere documentación de API hermosa?

¿Quiere una plataforma integrada y todo en uno para que su equipo de desarrolladores trabaje junto con máxima productividad?

Apidog cumple todas sus demandas, y reemplaza a Postman a un precio mucho más asequible!
button

Parte 1: Configuración del Entorno del Proyecto

Antes de poder empezar a generar nuestra especificación, necesitamos configurar un entorno de desarrollo adecuado. Esto implica crear un entorno Python aislado para gestionar nuestras dependencias e instalar las bibliotecas necesarias.

Creando Su Espacio de Trabajo ⚙️

Primero, vamos a crear un directorio para nuestro proyecto. Abra su terminal o símbolo del sistema y ejecute los siguientes comandos:Bash

# Crear un nuevo directorio para nuestro proyecto
mkdir pyswagger-tutorial
cd pyswagger-tutorial

# Crear un entorno virtual de Python
# En macOS/Linux
python3 -m venv venv
# En Windows
python -m venv venv

Un entorno virtual es un árbol de directorios autocontenido que incluye una instalación de Python y una serie de archivos de soporte. Usar un entorno virtual asegura que los paquetes que instalamos para este proyecto no entren en conflicto con los paquetes instalados para otros proyectos.

Ahora, active el entorno virtual:Bash

# En macOS/Linux
source venv/bin/activate

# En Windows
.\venv\Scripts\activate

Una vez activado, el indicador de su terminal debería cambiar para mostrar el nombre del entorno virtual (por ejemplo, (venv)), indicando que ahora está trabajando dentro de él.

Instalación de las Bibliotecas Necesarias

Con nuestro entorno activo, podemos instalar las bibliotecas de Python que necesitaremos para este tutorial. Necesitamos pyswagger para construir la especificación, Flask para crear nuestra sencilla API web, y PyYAML porque pyswagger lo utiliza para operaciones YAML.Bash

pip install "pyswagger[utils]" Flask PyYAML

La parte [utils] al instalar pyswagger es una buena práctica ya que incluye utilidades útiles, como un validador que usaremos más adelante para verificar la corrección de nuestra especificación generada.

Para una buena gestión del proyecto, es conveniente fijar sus dependencias en un archivo requirements.txt.Bash

pip freeze > requirements.txt

Su archivo requirements.txt ahora contendrá las bibliotecas y sus versiones específicas, haciendo que su proyecto sea fácilmente reproducible por otros.

Creación de la Aplicación Básica con Flask

Ahora, vamos a crear una aplicación mínima con Flask. Esto servirá como base de la API que vamos a documentar.

En el directorio de su proyecto, cree un nuevo archivo llamado app.py y añada el siguiente código:Python

# app.py

from flask import Flask, jsonify

# Inicializar la aplicación Flask
app = Flask(__name__)

@app.route("/")
def index():
    """ Un endpoint simple para verificar si la aplicación está funcionando. """
    return jsonify({"message": "¡La API está activa y funcionando!"})

if __name__ == "__main__":
    # Ejecuta la aplicación Flask en http://127.0.0.1:5000
    app.run(debug=True)

Este código configura un servidor web muy simple con un único endpoint. Para ejecutarlo, asegúrese de que su entorno virtual aún esté activo y ejecute el siguiente comando en su terminal:Bash

python app.py

Debería ver una salida que indica que el servidor está funcionando, algo como esto:

 * Serving Flask app 'app'
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

Ahora puede abrir su navegador web o usar una herramienta como curl para visitar http://127.0.0.1:5000. Debería ver la respuesta JSON: {"message": "¡La API está activa y funcionando!"}.

Con nuestro entorno básico y el esqueleto de la aplicación listos, ahora podemos sumergirnos en los conceptos centrales de pyswagger.


Parte 2: Entendiendo el Modelo de Objetos de pyswagger

Para usar pyswagger de manera efectiva en la generación de una especificación, primero necesita comprender cómo su modelo de objetos se corresponde con la estructura de un documento OpenAPI. Una especificación OpenAPI es esencialmente un gran objeto JSON o YAML con un esquema específico. pyswagger proporciona clases y objetos de Python que reflejan este esquema, permitiéndole construir la especificación de una manera más intuitiva y orientada a objetos.

Conceptos Centrales de la Especificación OpenAPI 3.0 📜

Un documento OpenAPI 3.0 tiene algunos campos clave de nivel superior:

Mapeo de OpenAPI a Objetos de pyswagger

pyswagger proporciona un mapeo limpio de estos conceptos de OpenAPI a objetos de Python. Exploremos los principales que utilizaremos.

El objeto central en pyswagger es App. Puede pensar en una instancia de App como la raíz de su documento OpenAPI.Python

from pyswagger import App

# La raíz del documento de especificación
# Lo inicializamos con una versión, pero también puede cargar desde una URL o archivo
root_app = App(version='3.0.0')

Una vez que tenga su objeto App, puede empezar a poblar sus atributos, que se corresponden directamente con los campos de OpenAPI. pyswagger utiliza un patrón builder, permitiendo una sintaxis fluida y legible.

Info y Servers

Las secciones info y servers son fáciles de poblar.Python

# Poblando el objeto 'info'
root_app.info.title = "API de Usuario"
root_app.info.version = "1.0.0"
root_app.info.description = "Una API simple para gestionar usuarios, utilizada para el tutorial de pyswagger."

# Poblando el array 'servers'
# Se crea un objeto Server y se añade
server = root_app.prepare_obj('Server', {'url': 'http://127.0.0.1:5000', 'description': 'Servidor de desarrollo local'})
root_app.servers.append(server)

Paths y Operations

Las rutas (Paths) se definen en el objeto App. Se añaden nuevas rutas y luego se definen las operaciones (métodos HTTP) dentro de ellas. Cada operación se configura con detalles como un summary (resumen), description (descripción), parameters (parámetros), un requestBody (cuerpo de la solicitud) y responses (respuestas).Python

# Definiendo una ruta y una operación
# Esto no ejecuta nada; simplemente construye la estructura del objeto.
path_item = root_app.define_path('/users')
get_op = path_item.define_op('get')
get_op.summary = "Recuperar una lista de todos los usuarios"

Components: Schemas, Parameters y Responses

El verdadero poder de una especificación OpenAPI bien estructurada proviene de los componentes reutilizables. En lugar de definir la estructura de un objeto "Usuario" cada vez que aparece en una respuesta, lo define una vez en components/schemas y luego lo referencia utilizando un puntero $ref. pyswagger maneja esto de manera elegante.

Schema: Un objeto Schema define un modelo de datos. Puede especificar su tipo (object, string, integer) y sus propiedades.Python

# Preparando un objeto Schema para un Usuario
user_schema = app.prepare_obj('Schema', {
    'type': 'object',
    'properties': {
        'id': {'type': 'integer', 'format': 'int64'},
        'username': {'type': 'string'},
        'email': {'type': 'string', 'format': 'email'}
    },
    'required': ['id', 'username', 'email']
})

# Añadirlo a los componentes reutilizables
app.components.schemas['User'] = user_schema

Parameter: Un objeto Parameter define un único parámetro de operación. Especifica su nombre, dónde se encuentra (in: 'path', 'query', 'header', o 'cookie'), y su esquema.Python

# Preparando un objeto Parameter para un ID de usuario en la ruta
user_id_param = app.prepare_obj('Parameter', {
    'name': 'user_id',
    'in': 'path',
    'description': 'ID del usuario a recuperar',
    'required': True,
    'schema': {'type': 'integer'}
})

Response: Un objeto Response define la estructura de una respuesta para un código de estado HTTP específico. Incluye una description (descripción) y el content (contenido), que especifica el tipo de medio (por ejemplo, application/json) y su esquema.Python

# Preparando un objeto Response para una respuesta 200 OK que devuelve un único usuario
# Note el uso de '$ref' para apuntar a nuestro esquema User reutilizable
ok_user_response = app.prepare_obj('Response', {
    'description': 'Recuperación exitosa de un usuario',
    'content': {
        'application/json': {
            'schema': {'$ref': '#/components/schemas/User'}
        }
    }
})

Comprender este mapeo es la clave para construir su especificación. Esencialmente, está construyendo un grafo de objetos de Python que pyswagger serializará más tarde en un archivo JSON o YAML válido de OpenAPI.


Parte 3: Construyendo una API Simple con Flask

Para hacer práctico nuestro ejercicio de documentación, necesitamos una API real para documentar. Ampliaremos nuestra sencilla aplicación Flask de la Parte 1 en una API REST mínima para gestionar una lista de usuarios. Esta API servirá como la "fuente de verdad" que describiremos con pyswagger.

Diseñando una API Simple de "Usuario" 📝

Implementaremos cuatro endpoints básicos que representan operaciones CRUD (Crear, Leer, Actualizar, Eliminar) comunes:

  1. GET /users: Recupera una lista de todos los usuarios.
  2. POST /users: Crea un nuevo usuario.
  3. GET /users/{user_id}: Obtiene un único usuario por su ID.
  4. DELETE /users/{user_id}: Elimina un usuario por su ID.

Para simplificar, utilizaremos un simple diccionario de Python como nuestra "base de datos" en memoria. En una aplicación del mundo real, esto sería una conexión a una base de datos como PostgreSQL o MongoDB.

Implementación de los Endpoints de Flask

Vamos a actualizar nuestro app.py archivo para incluir la lógica de estos endpoints. Reemplace el contenido de app.py con lo siguiente:Python

# app.py

from flask import Flask, jsonify, request, abort

app = Flask(__name__)

# --- Base de Datos en Memoria ---
# Un diccionario simple para almacenar nuestros usuarios.
# La clave es el user_id (entero), y el valor son los datos del usuario (dict).
USERS_DB = {
    1: {"username": "alice", "email": "alice@example.com"},
    2: {"username": "bob", "email": "bob@example.com"},
    3: {"username": "charlie", "email": "charlie@example.com"},
}
# Un contador para simular IDs auto-incrementales para nuevos usuarios
LAST_INSERT_ID = 3

# --- Endpoints de la API ---

@app.route("/users", methods=["GET"])
def get_users():
    """ Devuelve una lista de todos los usuarios. """
    # Necesitamos convertir el diccionario a una lista de objetos de usuario, incluyendo sus IDs.
    users_list = []
    for user_id, user_data in USERS_DB.items():
        user = {'id': user_id}
        user.update(user_data)
        users_list.append(user)
    return jsonify(users_list)

@app.route("/users", methods=["POST"])
def create_user():
    """ Crea un nuevo usuario. """
    global LAST_INSERT_ID
    if not request.json or 'username' not in request.json or 'email' not in request.json:
        abort(400, description="Falta nombre de usuario o correo electrónico en el cuerpo de la solicitud.")
    
    LAST_INSERT_ID += 1
    new_user_id = LAST_INSERT_ID
    
    new_user = {
        "username": request.json["username"],
        "email": request.json["email"],
    }
    
    USERS_DB[new_user_id] = new_user
    
    # La respuesta debe incluir el ID del usuario recién creado
    response_user = {'id': new_user_id}
    response_user.update(new_user)
    
    return jsonify(response_user), 201

@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id):
    """ Devuelve un único usuario por su ID. """
    if user_id not in USERS_DB:
        abort(404, description=f"Usuario con ID {user_id} no encontrado.")
    
    user_data = USERS_DB[user_id]
    user = {'id': user_id}
    user.update(user_data)
    
    return jsonify(user)

@app.route("/users/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
    """ Elimina un usuario por su ID. """
    if user_id not in USERS_DB:
        abort(404, description=f"Usuario con ID {user_id} no encontrado.")
    
    del USERS_DB[user_id]
    
    # Una respuesta 204 No Content es estándar para eliminaciones exitosas
    return '', 204

if __name__ == "__main__":
    app.run(debug=True, port=5000)

Ahora, si ejecuta python app.py de nuevo, tendrá una API completamente funcional (aunque simple). Puede probarla con curl o una herramienta similar:

Con nuestra API implementada, tenemos un objetivo concreto para nuestros esfuerzos de documentación. El siguiente paso es usar pyswagger para describir cada uno de estos endpoints en detalle.


Parte 4: Auto-Generando la Especificación OpenAPI con pyswagger

Este es el núcleo de nuestro tutorial. Ahora crearemos un script Python separado que importa pyswagger, define la estructura de nuestra API utilizando su modelo de objetos y luego serializa esa estructura en un archivo openapi.json completo. Este enfoque desacopla la generación de la especificación de la lógica de la aplicación, lo que puede ser un patrón muy limpio y mantenible.

Creando el Generador de Especificaciones

En el directorio de su proyecto, cree un nuevo archivo llamado generate_spec.py. Este script será responsable de construir y guardar nuestra especificación OpenAPI.

Construyendo la Especificación, Paso a Paso

Vamos a construir el script generate_spec.py pieza por pieza.

1. Importaciones e Inicialización de la App

Primero, necesitamos importar el objeto App de pyswagger y PyYAML para ayudar a volcar el archivo final. Crearemos nuestro objeto raíz App y poblaremos las secciones básicas info y servers, tal como discutimos en la Parte 2.Python

# generate_spec.py
import json
from pyswagger import App
from pyswagger.contrib.client.requests import Client

# --- 1. Inicializar el objeto raíz App ---
app = App(version='3.0.0')

# --- 2. Población de las secciones Info y Servers ---
app.info.title = "API de Usuario"
app.info.version = "1.0.0"
app.info.description = "Una API simple para gestionar usuarios, para el tutorial de pyswagger."

server = app.prepare_obj('Server', {
    'url': 'http://127.0.0.1:5000',
    'description': 'Servidor de desarrollo local'
})
app.servers.append(server)

2. Definiendo Componentes Reutilizables (Schemas)

Un buen diseño de API evita la repetición. Definiremos nuestro modelo de datos User una vez y lo reutilizaremos. También definiremos un esquema Error genérico para nuestras respuestas de error (como 404 Not Found).

Añada el siguiente código a generate_spec.py:Python

# --- 3. Definiendo Componentes Reutilizables (Schemas) ---

# Esquema para la respuesta de Error
error_schema = app.prepare_obj('Schema', {
    'type': 'object',
    'properties': {
        'code': {'type': 'integer', 'format': 'int32'},
        'message': {'type': 'string'}
    }
})
app.components.schemas['Error'] = error_schema

# Esquema para un único Usuario. Note que las propiedades coinciden con nuestra estructura USERS_DB.
user_schema = app.prepare_obj('Schema', {
    'type': 'object',
    'properties': {
        'id': {
            'type': 'integer',
            'description': 'Identificador único para el usuario.',
            'readOnly': True # El cliente no puede establecer este valor
        },
        'username': {
            'type': 'string',
            'description': 'El nombre de usuario elegido por el usuario.'
        },
        'email': {
            'type': 'string',
            'description': 'La dirección de correo electrónico del usuario.',
            'format': 'email'
        }
    },
    'required': ['id', 'username', 'email']
})
app.components.schemas['User'] = user_schema

# Esquema para crear un usuario (no incluye el campo 'id')
new_user_schema = app.prepare_obj('Schema', {
    'type': 'object',
    'properties': {
        'username': {
            'type': 'string',
            'description': 'El nombre de usuario elegido por el usuario.'
        },
        'email': {
            'type': 'string',
            'description': 'La dirección de correo electrónico del usuario.',
            'format': 'email'
        }
    },
    'required': ['username', 'email']
})
app.components.schemas['NewUser'] = new_user_schema

3. Documentando los Endpoints /users

Ahora definiremos la ruta /users y sus dos operaciones: GET y POST.

Python

# --- 4. Documentando las Rutas (Paths) ---

# -- Ruta: /users --
path_users = app.define_path('/users')

# Operación: GET /users
op_get_users = path_users.define_op('get')
op_get_users.summary = "Listar todos los usuarios"
op_get_users.description = "Devuelve un array JSON de todos los objetos de usuario."
op_get_users.tags.append('Usuarios')

op_get_users.responses.A('200').description = "Una lista de usuarios."
op_get_users.responses.A('200').content.A('application/json').schema.A(
    'array', items={'$ref': '#/components/schemas/User'}
)

# Operación: POST /users
op_post_users = path_users.define_op('post')
op_post_users.summary = "Crear un nuevo usuario"
op_post_users.description = "Añade un nuevo usuario a la base de datos."
op_post_users.tags.append('Usuarios')

op_post_users.requestBody.description = "Objeto de usuario que necesita ser añadido."
op_post_users.requestBody.required = True
op_post_users.requestBody.content.A('application/json').schema.set_ref('#/components/schemas/NewUser')

op_post_users.responses.A('201').description = "Usuario creado exitosamente."
op_post_users.responses.A('201').content.A('application/json').schema.set_ref('#/components/schemas/User')

op_post_users.responses.A('400').description = "Entrada inválida proporcionada."
op_post_users.responses.A('400').content.A('application/json').schema.set_ref('#/components/schemas/Error')

Observe el uso de set_ref y A (que significa "acceso") para una sintaxis más concisa al construir la estructura de objetos anidados.

4. Documentando los Endpoints /users/{user_id}

A continuación, documentaremos la ruta para interactuar con un único usuario. Esta ruta incluye un parámetro de ruta, {user_id}.

Python

# -- Ruta: /users/{user_id} --
path_user_id = app.define_path('/users/{user_id}')

# Podemos definir el parámetro una vez y reutilizarlo para todas las operaciones en esta ruta.
user_id_param = app.prepare_obj('Parameter', {
    'name': 'user_id',
    'in': 'path',
    'description': 'ID del usuario',
    'required': True,
    'schema': {'type': 'integer'}
})
path_user_id.parameters.append(user_id_param)

# Operación: GET /users/{user_id}
op_get_user_id = path_user_id.define_op('get')
op_get_user_id.summary = "Buscar usuario por ID"
op_get_user_id.description = "Devuelve un único usuario."
op_get_user_id.tags.append('Usuarios')

op_get_user_id.responses.A('200').description = "Operación exitosa."
op_get_user_id.responses.A('200').content.A('application/json').schema.set_ref('#/components/schemas/User')

op_get_user_id.responses.A('404').description = "Usuario no encontrado."
op_get_user_id.responses.A('404').content.A('application/json').schema.set_ref('#/components/schemas/Error')

# Operación: DELETE /users/{user_id}
op_delete_user_id = path_user_id.define_op('delete')
op_delete_user_id.summary = "Elimina un usuario"
op_delete_user_id.description = "Elimina un único usuario de la base de datos."
op_delete_user_id.tags.append('Usuarios')

op_delete_user_id.responses.A('204').description = "Usuario eliminado exitosamente."

op_delete_user_id.responses.A('404').description = "Usuario no encontrado."
op_delete_user_id.responses.A('404').content.A('application/json').schema.set_ref('#/components/schemas/Error')

5. Validando y Guardando la Especificación

Finalmente, el paso más satisfactorio. Le pediremos a pyswagger que valide nuestro grafo de objetos construido contra el esquema OpenAPI 3.0. Si es válido, lo volcaremos a un archivo JSON.

Añada este bloque final de código a generate_spec.py:Python

# --- 5. Validar y Guardar la Especificación ---
if __name__ == '__main__':
    try:
        # Validar la especificación generada
        app.validate()
        print("La especificación es válida.")

        # Guardar la especificación en un archivo JSON
        with open('openapi.json', 'w') as f:
            f.write(app.dump_json(indent=2))
        print("openapi.json generado exitosamente")

    except Exception as e:
        print(f"Error de Validación: {e}")

Su archivo generate_spec.py ahora está completo. Ejecútelo desde su terminal:Bash

python generate_spec.py

Si todo es correcto, verá la siguiente salida:

La especificación es válida.
openapi.json generado exitosamente

Ahora tendrá un nuevo archivo, openapi.json, en el directorio de su proyecto. Ábralo y explore su contenido. Verá un documento OpenAPI 3.0 perfectamente estructurado que describe su API Flask con todo detalle.


Parte 5: Sirviendo la Documentación

Tener un archivo openapi.json es excelente para las máquinas y para generar SDKs de cliente, pero para los desarrolladores humanos, una interfaz de usuario interactiva es mucho más útil. En esta parte final, integraremos nuestra especificación generada en nuestra aplicación Flask y la

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs