Introducción¶
milpa es un microframework de Python 3.14 para construir monolitos modulares. No reinventa nada: ensambla cuatro piezas maduras del ecosistema Python detrás de una estructura opinada y un kernel reutilizable.
| Pieza | Para qué |
|---|---|
| FastAPI | HTTP / API |
| Celery | tareas en background y crons |
| SQLAlchemy 2.0 | acceso a datos (agnóstico del motor) |
| Typer | consola (jornal, el "artisan") |
¿Para quién es?¶
Para dos escenarios:
- Arrancar servicios nuevos sin re-decidir la arquitectura cada vez (estructura, correo, colas, crons, i18n, persistencia: ya vienen resueltos y conectados).
- Migrar apps legacy de Laravel a Python conservando los conceptos familiares: artisan, scheduler, mailables, soft-deletes, timestamps automáticos, validación de tokens de Passport.
Filosofía¶
El kernel es el framework¶
Todo lo genérico y reutilizable vive en milpa/Core. Lo específico de tu proyecto vive
fuera de Core:
app/Modules/<Nombre>/— tus features (rutas, jobs, correos, servicios).app/Models/— tus modelos SQLAlchemy.app/Dictionaries/— tus constantes de dominio.app/Resources/— tus vistas, traducciones y estáticos compartidos.
Puedes copiar milpa/Core a otro proyecto y empezar de cero: es la "cosecha" reutilizable.
Convención sobre configuración¶
El framework descubre y conecta solo lo que sueltas en su lugar:
- Sueltas un controller con un
APIRouterbajoModules/X/Http/→ la API lo monta. - Sueltas un
@cron_taskbajoModules/X/Jobs/→ el scheduler lo agenda. - Sueltas un
@console_commandbajoModules/X/Console/Commands/→jornallo expone. - Sueltas un modelo en
app/Models/→ SQLAlchemy lo registra.
No hay un archivo central que editar para "registrar" cada cosa. (Ver Monolito modular.)
Módulos independientes¶
Los módulos no se importan entre sí. Lo fuerza import-linter como contrato de CI.
Cada módulo es un microservicio en potencia: se puede extraer sin desenredar imports
cruzados. El kernel (Core) tampoco depende de los módulos.
Monolingüe por default, i18n opt-in¶
Una app es de un solo idioma salvo que el dev decida traducir. El locale es ambiente
(se fija en la frontera HTTP desde Accept-Language) y se lee donde haga falta con
t(). (Ver Localización.)
Persistencia estilo Spring Data¶
Repositorios tipados Repository[Model, Id] con CRUD heredado; escrituras en servicios
@transactional (commit/rollback automático); lecturas con @auto_session. La sesión
es ambiente (contextvar), no se inyecta por constructor. (Ver
Repositorios y transacciones.)
Calidad forzada¶
El proyecto trae cuatro guardrails que corren en local y en CI:
uv run ruff check . # lint
uv run ruff format . # formato
uv run mypy # tipos (estricto)
uv run lint-imports # fronteras entre módulos
uv run pytest # tests (sin BD)
API pública¶
La superficie estable vive en la fachada raíz — un import plano con lo que el demo y esta guía enseñan:
from milpa import (
Controller, Get, Post, view, negotiate, # web: rutas, controllers y render
Auth, CurrentUser, guarded, Gate, policy, # auth: RBAC/ABAC y guards
job, cron_task, daily, hourly, # background: jobs on-demand y crons
celery_app, broker_guard, retry_policy, # Celery: la app, guarda de broker y reintentos
Mail, Mailable, MailContent, # correo
Observer, dispatch, handles, send, # eventos (1:N) y mediator (1:1)
Pipeline, Pipe, # modelo cebolla
Repository, Factory, Seeder, faker, # datos estilo Spring Data
Base, current_session, transactional, auto_session,
t, current_locale, # i18n de la UI
console_command, settings, # consola y config tipada
)
La fachada es perezosa (PEP 562): import milpa a secas no instancia Celery, no
arrastra el kernel web ni lee tu .env — cada símbolo se resuelve al primer acceso. Las
rutas profundas (from milpa.Core.Http.Routing import Get) siguen siendo válidas; la
fachada solo re-exporta. El paquete publica py.typed (PEP 561), así que mypy/tu IDE
reciben los tipos completos en cualquiera de las dos formas.