Quién les habla

José Miguel Amaya — APIs en Python

José Miguel Amaya
  • CTO y socio fundador de Xprende Tech. Full Stack Engineer en Efilm Online.
  • Fundador de Piura AI, Tallanix, Python Piura y VICUX.
  • Especialista en Python, Django/FastAPI e IA (LangChain, LangGraph, RAG).
  • Autor de charlas y talleres de desarrollo web y IA aplicada en Python Piura y Piura AI.
comunidad
empresa
Agenda · ~90 minutos

El recorrido de hoy

  • 01 Fundamentos web: qué es una API REST 8 min
  • 02 FastAPI: qué es y por qué 8 min
  • 03 Primeros pasos: hola mundo y docs 8 min
  • 04 Rutas, parámetros y validación 22 min
  • 05 Dependencias y tareas en segundo plano 6 min
  • 06 Base de datos y seguridad 20 min
  • 07 Testing, routers y despliegue 12 min
El caso

Tienda API

Una API para una tienda en línea. Todo el taller gira alrededor de estos recursos.

recurso
Producto

id, nombre, precio, categoría, stock.

recurso
Pedido

productos, cantidad, cliente, total.

recurso
Usuario

credenciales, perfil, permisos.

GET/productos POST/productos PUT/productos/{id} DELETE/productos/{id}
Punto de partida

¿Qué es una API REST?

API
Contrato

Interfaz que permite que dos programas se comuniquen: el cliente pide, el servidor responde.

REST
Convenciones

REpresentational State Transfer: reglas para diseñar esos servicios de forma uniforme sobre HTTP.

i
Se apoya en el protocolo HTTP y se usa en casi toda aplicación web moderna: móviles, frontends, microservicios.
Característica 1

Recursos y endpoints

Todo se modela como un recurso: una entidad con nombre. Cada recurso vive en una URL única.

/productos la colección de productos
/productos/42 un producto concreto
/pedidos/42/items los ítems de un pedido
✓
Nombra recursos con sustantivos en plural (productos), no con acciones (obtenerProductos).
Característica 2

Verbos HTTP

La acción no va en la URL: va en el método HTTP.

GET leer / recuperar datos
POST crear un recurso nuevo
PUT reemplazar un recurso completo
PATCH actualizar parcialmente
DELETE eliminar un recurso
GET /productos?categoria=ropa
i
Mismo endpoint /productos, distinto verbo: GET lo lista, POST lo crea.
Característica 3

Sin estado y en JSON

stateless
Sin estado

Cada petición lleva toda la información necesaria (incluida la identidad). El servidor no recuerda al cliente entre peticiones.

formato
JSON

Los datos viajan en un formato estándar: application/json. Fácil de leer por humanos y máquinas.

respuesta típica
{
  "id": 42, "nombre": "Mouse", "precio": 25.9, "en_stock": true
}
Característica 4

Códigos de estado

HTTP responde con un número que cuenta qué pasó. Se agrupan por familia.

2xx
Éxito. 200 OK · 201 Created · 204 No Content
3xx
Redirección. 301 Moved · 304 Not Modified
4xx
Error del cliente. 400 · 401 · 403 · 404 · 422
5xx
Error del servidor. 500 · 503
!
El 422 es la estrella de FastAPI: datos válidos como JSON pero que no cumplen el esquema.
Por qué REST

Ventajas para el desarrollo

Desacople

Frontend y backend evolucionan por separado; pueden vivir en servidores distintos.

Escalable

Sin estado: se replican servidores y balanceadores con facilidad.

Universal

Independiente del lenguaje y la plataforma del cliente o del servidor.

✓
Un mismo backend sirve a la web, a la app móvil y a otros servicios internos.
El framework

¿Qué es FastAPI?

Un framework web moderno y de alto rendimiento para construir APIs con Python 3.10+, basado en las anotaciones de tipos del lenguaje.

velocidad
ASGI

Asíncrono de verdad, comparable a Node.js y Go.

tipado
Type hints

Los tipos son la fuente de verdad: validación y docs salen de ahí.

adopción
Empresas

Usado por Microsoft, Uber, Netflix y miles de equipos.

¿Por qué FastAPI?

FastAPI vs Flask vs Django

FastAPIFlaskDjango
TipoFramework de APIsMicro-frameworkTodo incluido
ValidaciónPydantic, integradaManual / extensionesForms + DRF
AsyncNativo (ASGI)ParcialParcial (ASGI)
DocsAutomáticas (OpenAPI)ExtensionesDRF + drf-spectacular
Ideal paraAPIs y microserviciosApps pequeñasApps con admin y ORM
i
Los tres son Python. FastAPI brilla cuando el contrato de la API y el rendimiento son la prioridad.
Qué trae de fábrica

Características

Validación automática con Pydantic
Docs interactivas (OpenAPI)
Async nativo con async/await
Dependencias con inyección
Seguridad OAuth2, JWT, API keys
ORM compatible (SQLAlchemy, etc.)
i
Menos código repetido: describes qué datos esperas y FastAPI se encarga del resto.
Arquitectura

Sobre hombros de gigantes

FastAPI es una capa delgada y bien pensada sobre dos bibliotecas maduras.

web
Starlette

Toolkit ASGI (el sucesor de WSGI) para las partes web: rutas, peticiones, respuestas, WebSockets.

datos
Pydantic

Validación y serialización de datos basada en tipos. El corazón del tipado de FastAPI.

✓
Entender estas dos piezas hace que FastAPI deje de ser magia y pase a ser predecible.
Puesta en marcha

Instalación

Python 3.10+ y un entorno virtual. La forma moderna es un solo comando.

crear entorno
bash
$ python -m venv .venv $ source .venv/bin/activate $ pip install "fastapi[standard]"
qué incluye
fastapi el framework
uvicorn el servidor ASGI
fastapi-cli el comando fastapi
pydantic validación de datos
!
Ya no hace falta instalar uvicorn por separado: viene en fastapi[standard].
El mínimo

Hola mundo

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"mensaje": "Hola mundo"}
i
Siete líneas: importamos, creamos la app, declaramos una ruta y devolvemos un diccionario.
Disección

Analicemos el código

app
from fastapi import FastAPI

app = FastAPI()
ruta
@app.get("/")
async def root():
    return {"mensaje": "Hola mundo"}
FastAPI()la clase que provee toda la funcionalidad; app es la instancia con la que se registran rutas.
@app.get("/")decorador de operación: la función de abajo atiende GET /.
async deffunción de operación; se llama en cada petición y devuelve el cuerpo de la respuesta.
A correr

Levantar el servidor

bash
$ fastapi dev main.py INFO Uvicorn running on http://127.0.0.1:8000 INFO Application startup complete. INFO GET / 200 OK
fastapi dev desarrollo + recarga
fastapi run producción
i
Con --reload (incluido en dev) el servidor se reinicia al guardar cada cambio.
Regalo de FastAPI

Documentación automática

Con solo definir las rutas, FastAPI genera una interfaz interactiva para probar la API.

http://127.0.0.1:8000/docs
Tienda APIFastAPI · OpenAPI 3.1 · swagger-ui
GET/productosListar productos
POST/productosCrear producto
GET/productos/{id}Obtener uno
DELETE/productos/{id}Eliminar
✓
/docs usa Swagger UI y /redoc una versión de lectura. Se pueden probar los endpoints sin Postman.
La base de todo

Type hints

Todo FastAPI se apoya en las anotaciones de tipo de Python. No son obligatorias, pero lo cambian todo.

sin tipos
nombre = "Mouse"
precio = 25.9
en_stock = True
con tipos (moderno)
nombre: str = "Mouse"
precio: float = 25.9
en_stock: bool = True
i
Desde Python 3.9+ se usan los genéricos nativos: list[str], dict[str, int]. Y desde 3.10, str | None en lugar de Optional[str].
Mapa del bloque

El ciclo de una petición

Todo lo que veremos en este bloque ocurre en este recorrido.

Peticiónmétodo + path + body
→
ValidaciónPydantic (422 si falla)
→
DependenciasDepends(...)
→
Funcióntu lógica
→
Respuestaresponse_model → JSON
✓
Si entiendes este orden, cada pieza de FastAPI encaja en su sitio.
Vocabulario

Path y operaciones

Path última parte de la URL, desde el primer /
Operación el método HTTP aplicado a ese path
Endpoint la combinación path + operación
todas las operaciones
@app.get("/")
@app.post("/")
@app.put("/")
@app.patch("/")
@app.delete("/")
@app.options("/")
@app.head("/")
Datos en la URL

Parámetros de ruta

Un segmento variable se declara entre llaves y llega como argumento de la función.

main.py
@app.get("/productos/{producto_id}")
async def obtener_producto(producto_id: int):
    return {"producto_id": producto_id}
i
Al pedir /productos/42, FastAPI llama a la función con producto_id=42.
Validación gratis

Parámetros con tipos

Declarar el tipo activa el parseo y la validación automáticos.

main.py
@app.get("/productos/{producto_id}")
async def obtener(producto_id: int):
    return {"producto_id": producto_id}
✓
/productos/42 → 200 OK, producto_id es un int.
×
/productos/abc → 422 con un error explicando el tipo.
Valores predefinidos

Enum: valores permitidos

Cuando un parámetro solo admite ciertos valores, usa Enum.

main.py
from enum import Enum

class Categoria(str, Enum):
    electronica = "electronica"
    ropa = "ropa"
    hogar = "hogar"

@app.get("/productos/categoria/{categoria}")
async def por_categoria(categoria: Categoria):
    return {"categoria": categoria}
✓
La documentación mostrará un desplegable con las opciones válidas. Cualquier otro valor da 422.
Datos en la query

Parámetros de consulta

Van después del ? en la URL, separados por &. Se usan para filtrar, paginar u ordenar.

URL
GET /productos?skip=0&limit=10
main.py
@app.get("/productos")
async def listar_productos(skip: int = 0, limit: int = 10):
    return productos[skip : skip + limit]
i
Todo argumento que no está en el path y tiene un tipo simple se interpreta como parámetro de consulta.
Tres casos

Por defecto, opcional o requerido

por defecto
skip: int = 0
limit: int = 10
opcional
q: str | None = None
requerido
q: str
i
Si no se envía, toma el valor por defecto.
i
Puede faltar; llega como None.
!
Si falta, FastAPI responde 422.
✓
Regla de oro: sin valor por defecto = requerido.
Reglas declarativas

Validación en parámetros

Con Query y Path añades restricciones que se validan y aparecen en /docs.

main.py
from fastapi import Path, Query

@app.get("/productos")
async def listar(
    skip: int = Query(0, ge=0),
    limit: int = Query(10, ge=1, le=100),
    q: str | None = Query(None, min_length=2),
):
    ...

@app.get("/productos/{producto_id}")
async def obtener(producto_id: int = Path(gt=0)):
    ...
✓
ge/le (rangos) y min_length se validan solos: fuera de rango → 422.
Enviar datos

Request body con Pydantic

Los datos que envía el cliente se modelan con una clase de Pydantic.

main.py
from pydantic import BaseModel, Field

class ProductoCreate(BaseModel):
    nombre: str
    descripcion: str | None = None
    precio: float = Field(gt=0)
    en_stock: bool = True

@app.post("/productos")
async def crear_producto(producto: ProductoCreate):
    return producto
i
FastAPI lee el JSON, lo convierte, lo valida y te entrega un objeto ProductoCreate listo para usar.
Cuando los datos no cuadran

Validación automática

Si el cuerpo no cumple el esquema, FastAPI responde 422 con el detalle exacto.

enviamos
{
  "nombre": "Mouse", "precio": -5
}
respuesta 422
{
  "detail": [{
    "loc": ["body", "precio"],
    "msg": "Input should be greater than 0"
  }]
}
✓
No escribimos ni un if: la regla vive en el modelo y el error indica campo y motivo.
Todo junto

Body + path + query

FastAPI decide de dónde tomar cada parámetro según su tipo y su nombre.

main.py
@app.put("/productos/{producto_id}")
async def actualizar(
    producto_id: int,
    producto: ProductoCreate,
    notificar: bool = False,
):
    return {"id": producto_id, **producto.model_dump()}
i
producto_id → viene del path.
i
producto (Pydantic) → del body.
i
notificar (simple) → de la query.
Controlar la salida

response_model

Define qué se devuelve. FastAPI filtra los campos que no estén en el modelo de salida.

main.py
class UsuarioIn(BaseModel):
    username: str
    password: str

class UsuarioOut(BaseModel):
    username: str

@app.post("/usuarios", response_model=UsuarioOut)
async def crear_usuario(usuario: UsuarioIn):
    return usuario  # la contraseña nunca se expone
×
Nunca devuelvas el modelo de entrada si contiene datos sensibles: usa un modelo de salida.
Comunicar el resultado

Códigos de estado explícitos

Por defecto todo responde 200. Indica el código correcto con status_code.

main.py
from fastapi import status

@app.post("/productos", status_code=status.HTTP_201_CREATED)
async def crear_producto(producto: ProductoCreate):
    return producto
201 recurso creado
204 sin contenido (DELETE)
Errores con sentido

Errores y HTTPException

Para errores de negocio, lanza una excepción con su código HTTP.

main.py
from fastapi import HTTPException

@app.get("/productos/{producto_id}", response_model=ProductoOut)
async def obtener(producto_id: int, db: AsyncSession = Depends(get_db)):
    producto = await db.get(Producto, producto_id)
    if producto is None:
        raise HTTPException(status_code=404, detail="Producto no encontrado")
    return producto
i
FastAPI convierte la excepción en {"detail": "..."} con el código indicado. Puedes unificar el formato con @app.exception_handler.
Para no tropezar

Errores comunes

×
Olvidar async o el tipo en un parámetro: pierdes validación y docs.
×
Devolver el modelo de entrada con la contraseña incluida.
×
Usar .dict() (Pydantic v1) en vez de .model_dump().
×
Bloquear el event loop con código síncrono pesado dentro de async def.
×
Confundir path y query: si está en la URL es path; si es filtro, query.
×
No declarar response_model y filtrar datos sensibles.
Patrón clave

Inyección de dependencias

Una dependencia es una función que FastAPI resuelve y entrega a tu ruta automáticamente.

Reutilizar

Defines una vez y la usas en muchas rutas.

Testear

Fácil de reemplazar por una versión de prueba.

Ordenar

Separa la lógica de las operaciones externas.

✓
La usaremos para paginación, conexión a la base de datos y el usuario autenticado.
En código

Una dependencia en acción

main.py
from fastapi import Depends

def paginacion(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

@app.get("/productos")
async def listar(pag: dict = Depends(paginacion)):
    return pag
i
Depends(paginacion) le dice a FastAPI: resuelve esa función (incluidos sus query params) y pásame el resultado.
Trabajo diferido

Tareas en segundo plano

Para cosas que deben pasar después de responder: correos, notificaciones, logs.

main.py
from fastapi import BackgroundTasks

def enviar_correo(email: str, mensaje: str):
    ...

@app.post("/pedidos")
async def crear_pedido(email: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(enviar_correo, email, "Pedido confirmado")
    return {"estado": "creado"}
✓
El cliente recibe la respuesta al instante; el correo se envía después, sin bloquearlo.
Combinando patrones

BackgroundTasks + dependencias

BackgroundTasks también funciona dentro de una dependencia; todas las tareas se fusionan y se ejecutan juntas al final.

main.py
def auditar(background_tasks: BackgroundTasks, q: str | None = None):
    if q:
        background_tasks.add_task(registrar_busqueda, q)
    return q

@app.get("/productos")
async def listar(q: str = Depends(auditar)):
    return {"q": q}
i
Se pueden declarar tareas en la ruta, en una dependencia o en una subdependencia: FastAPI reutiliza el mismo objeto.
Límites

¿Y si la tarea es pesada?

background tasks
Simple

Mismo proceso, tras la respuesta. Ideal para correos, notificaciones y logs.

cola de tareas
Celery / RQ

Otro proceso, con broker (Redis/RabbitMQ). Para trabajo pesado, reintentos y varios servidores.

!
Si la tarea puede tardar minutos o debe sobrevivir a un reinicio, usa una cola, no BackgroundTasks.
12-factor

Configuración por entorno

Nada de secretos en el código: se leen del entorno con pydantic-settings.

config.py
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    model_config = SettingsConfigDict(env_file=".env")

settings = Settings()
!
Instala pip install pydantic-settings. Añade .env a .gitignore.
Persistencia

Base de datos con SQLAlchemy

FastAPI no impone un ORM. El estándar de facto es SQLAlchemy 2.0.

database.py
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase

DATABASE_URL = "postgresql+psycopg://tienda:tienda@localhost/tienda"

engine = create_async_engine(DATABASE_URL)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)

class Base(DeclarativeBase):
    pass
i
Instala pip install "psycopg[binary]". El prefijo postgresql+psycopg usa psycopg 3, que funciona en modo async y sync.
Arranque y cierre

Ciclo de vida: lifespan

Recursos que se abren al arrancar la app y se cierran al apagarla.

main.py
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    await conectar_recursos()
    yield
    await liberar_recursos()

app = FastAPI(lifespan=lifespan)
✓
Reemplaza a los antiguos eventos startup/shutdown. Ideal para pools de conexión y clientes HTTP.
Dos capas

Modelo ORM vs esquema Pydantic

models.py · tabla
from sqlalchemy.orm import Mapped, mapped_column
from database import Base

class Producto(Base):
    __tablename__ = "productos"
    id: Mapped[int] = mapped_column(primary_key=True)
    nombre: Mapped[str]
    precio: Mapped[float]
schemas.py · API
from pydantic import BaseModel, ConfigDict

class ProductoOut(BaseModel):
    id: int
    nombre: str
    precio: float
    model_config = ConfigDict(from_attributes=True)
✓
El modelo ORM describe la tabla; el esquema Pydantic describe lo que entra y sale por la API. No los mezcles.
Conectar todo

Dependencia de sesión y CRUD

main.py
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

async def get_db():
    async with SessionLocal() as db:
        yield db

@app.get("/productos", response_model=list[ProductoOut])
async def listar(db: AsyncSession = Depends(get_db)):
    result = await db.scalars(select(Producto))
    return result.all()
i
get_db usa yield: abre la sesión, la entrega y la cierra al terminar. Y con async with + await no bloquea el event loop.
!
Si usas SQLAlchemy síncrono, declara la ruta con def (no async def): FastAPI la ejecuta en un threadpool y evita bloquear el loop.
Versionar el esquema

Migraciones con Alembic

El esquema de la base de datos evoluciona con versiones reproducibles.

bash
$ alembic init migrations $ alembic revision --autogenerate -m "crear productos" $ alembic upgrade head
!
Usa Base.metadata.create_all() solo en pruebas. En producción, siempre migraciones.
Proteger la API

Autenticación y autorización

Autenticar es saber quién eres; autorizar es saber qué puedes hacer.

security.py
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
i
El esquema OAuth2 dice que el token se obtiene en /token. FastAPI lo usa para pedir el header Authorization: Bearer ... y mostrarlo en /docs.
La dependencia de usuario

get_current_user

security.py
from fastapi import HTTPException

async def get_current_user(token: str = Depends(oauth2_scheme)):
    usuario = decodificar_token(token)  # verifica firma y expiración
    if usuario is None:
        raise HTTPException(
            status_code=401,
            detail="Token inválido",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return usuario

@app.get("/usuarios/me")
async def perfil(usuario: Usuario = Depends(get_current_user)):
    return usuario
×
En producción usa tokens JWT firmados (python-jose / PyJWT) y contraseñas con hash (passlib/bcrypt). Nunca guardes contraseñas en texto plano.
Obtener el token

Login: /token

main.py
from fastapi.security import OAuth2PasswordRequestForm

@app.post("/token")
async def login(form: OAuth2PasswordRequestForm = Depends()):
    # verificar form.username y form.password
    token = crear_token(form.username)
    return {"access_token": token, "token_type": "bearer"}
✓
Con esto, el botón Authorize de /docs funciona y puedes probar rutas protegidas sin escribir headers a mano. Requiere pip install python-multipart.
Autenticación ≠ autorización

Autorización: roles y scopes

Ya sabes quién es; ahora define qué puede hacer.

main.py
from fastapi import Security

@app.post(
    "/productos",
    dependencies=[Security(oauth2_scheme, scopes=["productos:escribir"])],
)
async def crear_producto(producto: ProductoCreate):
    ...
i
Los scopes viajan en el token y se verifican en get_current_user. Alternativa simple: un campo rol en el usuario.
Frontend y backend

CORS

Si un frontend en otro dominio llama a tu API, el navegador exige CORS.

main.py
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://mitienda.com"],
    allow_methods=["*"],
    allow_headers=["*"],
)
!
Evita allow_origins=["*"] en producción: enumera los dominios permitidos.
Probar la API

Testing con TestClient

Gracias a Starlette, probar una API es como usar requests.

test_main.py
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_root():
    r = client.get("/")
    assert r.status_code == 200
    assert r.json() == {"mensaje": "Hola mundo"}
i
Funciones que empiezan con test_ y asserts estándar: pytest lo entiende sin configuración.
Organizar

Separar código y tests

main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_main():
    return {"msg": "Hello World"}
test_main.py
from fastapi.testclient import TestClient
from .main import app

client = TestClient(app)

def test_read_main():
    r = client.get("/")
    assert r.json() == {"msg": "Hello World"}
✓
Un archivo por módulo (test_<módulo>.py) mantiene el proyecto navegable a medida que crece.
Cuando hay async

Tests asíncronos

Para probar código asíncrono (por ejemplo una base de datos async) se usa httpx y anyio.

test_main.py
import pytest
from httpx import AsyncClient, ASGITransport
from main import app

@pytest.fixture
def anyio_backend():
    return "asyncio"

@pytest.mark.anyio
async def test_root():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        r = await ac.get("/")
    assert r.status_code == 200
!
Ya no se pasa app=app directamente al cliente: desde httpx 0.27 se usa ASGITransport.
Tests sin base de datos

Sobrescribir dependencias

Reemplaza una dependencia real por una de prueba sin tocar el código.

test_productos.py
from main import app, get_db
from fastapi.testclient import TestClient

async def db_falsa():
    yield FakeSession()

app.dependency_overrides[get_db] = db_falsa

def test_listar():
    client = TestClient(app)
    assert client.get("/productos").status_code == 200
✓
Tests rápidos y aislados: no dependen de una base de datos real. Recuerda limpiar app.dependency_overrides.
Modularizar

Routers con APIRouter

Un router por recurso: el proyecto crece sin que main.py explote.

routers/productos.py
from fastapi import APIRouter

router = APIRouter(
    prefix="/productos",
    tags=["productos"],
)

@router.get("/")
async def listar(...):
    ...
main.py
from routers import productos

app.include_router(productos.router)
✓
prefix evita repetir la ruta y tags agrupa los endpoints en /docs.
Orden

Estructura de un proyecto real

app/main.pycrea la app, incluye routers y middleware.
app/models.pytablas de SQLAlchemy.
app/schemas.pymodelos Pydantic de entrada/salida.
app/crud.pyoperaciones contra la base de datos.
app/database.pyengine, sesión y Base.
app/security.pytokens y usuario actual.
tests/pruebas junto al código que cubren.
A producción

Despliegue

local
bash
$ fastapi dev main.py
producción
bash
$ fastapi run main.py --workers 4
Docker

Imagen reproducible; un contenedor por servicio.

Workers

Varios procesos para aprovechar los núcleos de CPU.

Variables de entorno

Secretos y URLs de BD fuera del código.

Y esto no termina aquí

Qué más tiene FastAPI

WebSockets FormData Subida de archivos Cookies Middleware Lifespan GraphQL (Strawberry) SQLModel Alembic Pydantic Settings OAuth2 social Streaming
✓
Todo lo que viste se combina: una API con base de datos, auth, tests y despliegue es la base de cualquier producto.
Para llevar

Chuleta de FastAPI

Ruta @app.get("/productos")
Path param def f(id: int)
Query param skip: int = 0
Body def f(item: ProductoCreate)
Validación Field(gt=0)
Salida response_model=Out
Dependencia x = Depends(fn)
Docs /docs · /redoc