Inducción técnicaUso interno · v0.2 · octubre 2026

04 · Backend

Python y FastAPI

El servidor de un sistema Naoro es una API en FastAPI sobre PostgreSQL. Esta página repasa cada pieza: qué es, para qué la usamos y qué leer primero.

Python moderno

Usamos Python 3.12 o posterior. Si vienes de versiones anteriores, repasa sobre todo las anotaciones de tipos (list[str], X | None, Annotated) y async / await. FastAPI y Pydantic se apoyan en los tipos para validar, documentar y autocompletar.

FastAPI

FastAPI

Framework de API

Define endpoints con funciones de Python anotadas. A partir de los tipos valida la entrada, serializa la salida y genera la documentación interactiva en /docs.

  • Dependencias (Depends) para sesión de base de datos, usuario actual y permisos. Es el mecanismo central: apréndelo bien.
  • Routers por dominio (APIRouter), montados en main.py.
  • lifespan para abrir y cerrar conexiones al arrancar y apagar.

Pydantic v2

Validación y configuración

Los esquemas de entrada y salida de la API son modelos de Pydantic. La configuración (base de datos, llaves, URLs de integraciones) se lee del entorno con BaseSettings. Nunca se escribe una credencial en el código.

Si vienes de Pydantic v1: .dict() ahora es .model_dump(), orm_mode ahora es from_attributes y los validadores cambiaron de nombre.

Datos

Segundo plano e integraciones

Calidad

pytest · ruff · Docker Compose

Pruebas, estilo y entorno
  • Las reglas de negocio tienen pruebas unitarias sin base de datos. Los endpoints, pruebas con el cliente de pruebas de FastAPI.
  • ruff da formato y revisa estilo. Lo que ruff marca se corrige antes de pedir revisión.
  • Docker Compose levanta el entorno completo (api, db, redis, worker) con un solo comando. Lo que corre en tu máquina es lo mismo que corre en el servidor.

Ejemplo: una regla de punta a punta

La regla de Finanzas del análisis, escrita en el servidor. Primero la regla, en Python puro:

app/aprobaciones/rules.py
from dataclasses import dataclass
from decimal import Decimal
from typing import Literal

Tipo = Literal["compra", "gasto", "contratacion", "presupuesto"]


@dataclass(frozen=True)
class Solicitud:
    tipo: Tipo
    area: str
    monto: Decimal


@dataclass(frozen=True)
class Resultado:
    regla: str
    aplica: bool
    razon: str
    decide: str


UMBRAL_FINANZAS = Decimal("20000")


def finanzas(s: Solicitud) -> Resultado:
    es_gasto = s.tipo in ("compra", "gasto")
    return Resultado(
        regla="Compras y gastos sobre el umbral pasan por Finanzas",
        aplica=es_gasto and s.monto > UMBRAL_FINANZAS,
        razon=f"{s.monto} vs {UMBRAL_FINANZAS}" if es_gasto else "No es compra ni gasto",
        decide="Finanzas",
    )

Su prueba, sin base de datos ni HTTP:

tests/aprobaciones/test_rules.py
from decimal import Decimal
from app.aprobaciones.rules import Solicitud, finanzas


def test_compra_sobre_umbral_pasa_por_finanzas():
    r = finanzas(Solicitud(tipo="compra", area="Operaciones", monto=Decimal("25000")))
    assert r.aplica and r.decide == "Finanzas"


def test_contratacion_no_pasa_por_finanzas():
    r = finanzas(Solicitud(tipo="contratacion", area="Personas", monto=Decimal("90000")))
    assert not r.aplica

Y el endpoint, delgado: valida, llama al servicio y responde.

app/aprobaciones/router.py
from decimal import Decimal
from typing import Annotated

from fastapi import APIRouter, Depends
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth import Usuario, usuario_actual
from app.db import sesion
from . import service
from .rules import Tipo

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

Sesion = Annotated[AsyncSession, Depends(sesion)]
Yo = Annotated[Usuario, Depends(usuario_actual)]


class SolicitudIn(BaseModel):
    tipo: Tipo
    area: str
    monto: Decimal = Field(gt=0)


class SolicitudOut(BaseModel):
    id: int
    estado: str
    ruta: list[str]


@router.post("", response_model=SolicitudOut, status_code=201)
async def crear(datos: SolicitudIn, db: Sesion, yo: Yo):
    return await service.crear(db, datos, autor=yo)

Al terminar deberías poder

  • Levantar con Docker Compose una API con PostgreSQL y ver su documentación en /docs.
  • Crear un dominio nuevo con router, esquemas, modelo, servicio y migración.
  • Escribir una regla de negocio en Python puro con sus pruebas.
  • Proteger un endpoint con usuario actual y rol usando dependencias.
  • Llamar una API externa con reintentos y registrar la llamada.