Ir al contenido

Controladores HTTP

Un controlador HTTP organiza la lógica de uno o varios endpoints. Cuando el router encuentra una coincidencia y los middleware permiten continuar, Orionis construye el controlador mediante el contenedor, invoca su acción y procesa la respuesta devuelta.

Mantener las acciones en controladores permite que los archivos de rutas se concentren en declarar endpoints y que los servicios se ocupen de la lógica de negocio.

Resource e Invokable son dos estructuras de controlador. --api es una opción independiente que cambia las anotaciones de respuesta; puede utilizarse con cualquiera de las dos.

Desde la raíz del proyecto, con su entorno Python activo, puedes utilizar el comando directo:

Ventana de terminal
orionis make:http-controller ExampleController

O su alternativa mediante el archivo local reactor:

Ventana de terminal
python -B reactor make:http-controller ExampleController

La sintaxis es make:http-controller name [--invoke] [--api]:

Argumento u opciónObligatorioPredeterminadoEfecto
nameSíSin valorNombre utilizado para construir el archivo y la clase.
--invokeNoFalseGenerar únicamente la acción __call__, en lugar de las siete acciones Resource.
--apiNoFalseImportar JSONResponse y utilizarlo en las anotaciones de retorno y docstrings.

-h o --help muestran la ayuda. Las opciones --invoke y --api se pueden combinar.

El destino predeterminado es app/http/controllers, obtenido de la ruta de aplicación app_http_controllers. Si personalizas esa ruta, el generador utiliza su ubicación configurada.

nameClase generadaArchivo relativo al proyecto
ExampleControllerExampleControllerapp/http/controllers/examplecontroller.py
UserControllerUserControllerapp/http/controllers/usercontroller.py
user_controller o userUserControllerapp/http/controllers/user_controller.py
admin/userUserControllerapp/http/controllers/admin/user_controller.py

El archivo utiliza minúsculas y añade _controller cuando falta el sufijo. La clase se forma a partir de los segmentos separados por _ y añade Controller si falta. No se insertan guiones bajos entre palabras PascalCase: UserController produce usercontroller.py, no user_controller.py.

Se admiten subdirectorios con / o \, normalizados a minúsculas, y una extensión final como .py. Los directorios se crean automáticamente. No se admiten rutas absolutas, segmentos .. ni nombres con guiones.

El generador no sobrescribe archivos existentes y no ofrece --force. Las modalidades siguientes son alternativas: usa un nombre distinto para conservar varias o edita el archivo ya generado.

Un Resource Controller reúne acciones relacionadas con un recurso. Resulta útil cuando consultas, formularios, creación, actualización y eliminación pertenecen a la misma entidad.

Es la estructura predeterminada:

Ventana de terminal
orionis make:http-controller UserController

Alternativa local:

Ventana de terminal
python -B reactor make:http-controller UserController

El archivo app/http/controllers/usercontroller.py contiene:

from orionis.http import HttpResponse
from orionis.http.base import BaseController
class UserController(BaseController):
"""
Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to
generate only ``__call__`` and the API option to annotate JSON responses.
"""
async def index(self) -> HttpResponse:
"""Display a listing of the resource.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""
async def create(self) -> HttpResponse:
"""Show the form for creating a resource.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""
async def store(self) -> HttpResponse:
"""Store a newly created resource.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""
async def show(self) -> HttpResponse:
"""Display the specified resource.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""
async def edit(self) -> HttpResponse:
"""Show the form for editing the resource.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""
async def update(self) -> HttpResponse:
"""Update the specified resource.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""
async def destroy(self) -> HttpResponse:
"""Remove the specified resource.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""

BaseController, importado desde orionis.http.base, es una clase base marcadora: no implementa CRUD, validación, autorización ni acceso a datos. HttpResponse es el alias de tipos del framework para Response.

Todas las acciones son asíncronas, reciben únicamente self y están anotadas con HttpResponse. El generador no añade automáticamente Request, identificadores ni dependencias.

AcciónPropósito indicado por la plantilla
indexListar el recurso.
createMostrar el formulario de creación.
storeGuardar un nuevo recurso.
showMostrar un recurso concreto.
editMostrar el formulario de edición.
updateActualizar un recurso concreto.
destroyEliminar un recurso concreto.

Estos nombres organizan responsabilidades; sus cuerpos no realizan esas operaciones hasta que los implementes.

Implementar acciones y registrarlas en rutas

Sección titulada «Implementar acciones y registrarlas en rutas»

Para probar dos acciones de lectura, sustituye el archivo del ejemplo por esta implementación mínima:

from orionis.http import HttpResponse, response
from orionis.http.base import BaseController
class UserController(BaseController):
async def index(self) -> HttpResponse:
return response.json({"users": []})
async def show(self, user_id: int) -> HttpResponse:
return response.json({"id": user_id})

En el archivo registrado como API, normalmente routes/api.py:

from app.http.controllers.usercontroller import UserController
from orionis.support.facades import Route
Route.get("/users", [UserController, "index"])
Route.get("/users/{user_id:int}", [UserController, "show"])

GET /users devuelve {"users": []}; GET /users/42 devuelve {"id": 42}. La segunda acción añade user_id a la firma para recibir el parámetro de su ruta.

La pareja [Clase, "acción"] identifica el método sin ejecutarlo. También se admite una tupla. Pasa la clase, sin instanciarla.

No hay registro automático de rutas Resource. El generador no asocia verbos HTTP a las acciones: en este ejemplo elegimos GET para ambas lecturas. Declara las demás rutas explícitamente con los métodos HTTP que corresponda a tus acciones implementadas. Consulta Rutas.

Un Invokable Controller concentra una operación en __call__. Es útil para un endpoint con una responsabilidad concreta, sin reunir varias acciones en la misma clase.

Ventana de terminal
orionis make:http-controller UserController --invoke

Alternativa local:

Ventana de terminal
python -B reactor make:http-controller UserController --invoke

En esta modalidad, app/http/controllers/usercontroller.py contiene únicamente una acción:

from orionis.http import HttpResponse
from orionis.http.base import BaseController
class UserController(BaseController):
"""
Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to
generate only ``__call__`` and the API option to annotate JSON responses.
"""
async def __call__(self) -> HttpResponse:
"""Handle the request for this controller.
Returns
-------
HttpResponse
Response produced when this action is implemented.
"""

__call__ es el método que el router selecciona cuando registras directamente una clase invocable. El esqueleto recibe sólo self; puedes añadir parámetros de ruta y dependencias como en cualquier acción.

Sustituye el esqueleto por una implementación que devuelva una respuesta:

from orionis.http import HttpResponse, response
from orionis.http.base import BaseController
class UserController(BaseController):
async def __call__(self) -> HttpResponse:
return response.text("User endpoint ready")

En el archivo de rutas:

from app.http.controllers.usercontroller import UserController
from orionis.support.facades import Route
Route.get("/user-summary", UserController)

GET /user-summary ejecuta __call__() y devuelve el texto con estado 200. También se admite [UserController, "__call__"], aunque la clase directa expresa la misma intención de forma breve. El router no acepta una instancia ni un método ligado como acción.

--api cambia la importación de HttpResponse por JSONResponse, las anotaciones de retorno y el tipo mencionado en las docstrings. No añade código de respuesta, serialización, rutas ni persistencia.

La estructura sigue dependiendo de --invoke: sin esa opción conserva las siete acciones Resource, incluidas create y edit; con ella genera sólo __call__.

Ventana de terminal
orionis make:http-controller UserController --api

Alternativa local:

Ventana de terminal
python -B reactor make:http-controller UserController --api

El código generado completo es:

from orionis.http import JSONResponse
from orionis.http.base import BaseController
class UserController(BaseController):
"""
Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to
generate only ``__call__`` and the API option to annotate JSON responses.
"""
async def index(self) -> JSONResponse:
"""Display a listing of the resource.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""
async def create(self) -> JSONResponse:
"""Show the form for creating a resource.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""
async def store(self) -> JSONResponse:
"""Store a newly created resource.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""
async def show(self) -> JSONResponse:
"""Display the specified resource.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""
async def edit(self) -> JSONResponse:
"""Show the form for editing the resource.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""
async def update(self) -> JSONResponse:
"""Update the specified resource.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""
async def destroy(self) -> JSONResponse:
"""Remove the specified resource.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""

Para implementar las acciones de lectura con respuestas JSON explícitas, edita el archivo:

from orionis.http import JSONResponse, response
from orionis.http.base import BaseController
class UserController(BaseController):
async def index(self) -> JSONResponse:
return response.json({"users": []})
async def show(self, user_id: int) -> JSONResponse:
return response.json({"id": user_id})

Regístralas en routes/api.py con las mismas parejas de clase y acción:

from app.http.controllers.usercontroller import UserController
from orionis.support.facades import Route
Route.get("/users", [UserController, "index"])
Route.get("/users/{user_id:int}", [UserController, "show"])

Las opciones se combinan sin restricciones entre ellas:

Ventana de terminal
orionis make:http-controller UserController --invoke --api

Alternativa local:

Ventana de terminal
python -B reactor make:http-controller UserController --invoke --api

El resultado completo es:

from orionis.http import JSONResponse
from orionis.http.base import BaseController
class UserController(BaseController):
"""
Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to
generate only ``__call__`` and the API option to annotate JSON responses.
"""
async def __call__(self) -> JSONResponse:
"""Handle the request for this controller.
Returns
-------
JSONResponse
Response produced when this action is implemented.
"""

Una implementación funcional de esa acción única sería:

from orionis.http import JSONResponse, response
from orionis.http.base import BaseController
class UserController(BaseController):
async def __call__(self) -> JSONResponse:
return response.json({"status": "ready"})

En routes/api.py:

from app.http.controllers.usercontroller import UserController
from orionis.support.facades import Route
Route.get("/user-summary", UserController)

Este endpoint devuelve JSON con estado 200. Los ejemplos Resource e Invokable son alternativas; registra una sola acción por combinación de verbo y path.

Una anotación JSONResponse no convierte por sí sola el valor retornado. Los ejemplos construyen la respuesta mediante response.json(...).

Por separado, el kernel admite resultados dict o msgspec.Struct y los convierte en JSONResponse con estado 200, independientemente de --api y de la anotación. No convierte cualquier tipo de valor: una respuesta textual debe construirse, por ejemplo, con response.text(...). Consulta Respuestas HTTP.

El contexto API o WEB procede de la configuración de app.withRouting(...), no de --api, del nombre del controlador ni de su anotación. Un controlador sin --api también puede devolver JSON, como en el primer ejemplo Resource. Consulta la separación de rutas API y WEB.

La columna de argumentos se añade a orionis make:http-controller o a python -B reactor make:http-controller. El tipo indicado es el esperado al implementar la acción, no una respuesta ya creada por el esqueleto.

EstructuraArgumentosAcciones generadasRetorno anotadoUso habitualRegistro en ruta
ResourceUserControllerLas siete acciones de recurso.HttpResponseVarias operaciones del mismo recurso.[UserController, "index"]
InvokableUserController --invoke__call__HttpResponseUna operación concreta.UserController
Resource con APIUserController --apiLas mismas siete acciones.JSONResponseVarias operaciones con respuestas JSON.[UserController, "index"]
Invokable con APIUserController --invoke --api__call__JSONResponseUna operación con respuesta JSON.UserController

Al atender una ruta, Orionis construye la clase del controlador y resuelve las dependencias anotadas de su constructor. Después invoca la acción, aportando los parámetros de ruta por nombre y resolviendo sus dependencias restantes. La preparación de metadatos durante el arranque no ejecuta el constructor ni las acciones ordinarias.

Este ejemplo utiliza dos servicios concretos sin dependencias, que el contenedor puede construir automáticamente. Guarda app/services/user_label_service.py:

import logging
class UserLabelService:
def label(self, user_id: int) -> str:
return f"User {user_id}"
class UserAuditService:
def record_view(self, user_id: int, path: str) -> None:
logging.getLogger(__name__).info("Viewing user %s at %s", user_id, path)

En app/http/controllers/show_user_controller.py:

from app.services.user_label_service import UserAuditService, UserLabelService
from orionis.http import JSONResponse, Request, response
from orionis.http.base import BaseController
class ShowUserController(BaseController):
def __init__(self, labels: UserLabelService) -> None:
self.labels = labels
async def __call__(
self,
request: Request,
user_id: int,
audit: UserAuditService,
) -> JSONResponse:
audit.record_view(user_id, request.path)
return response.json({
"id": user_id,
"label": self.labels.label(user_id),
"path": request.path,
})

Registra la clase en routes/api.py, sustituyendo la declaración equivalente si ya utilizas ese path:

from app.http.controllers.show_user_controller import ShowUserController
from orionis.support.facades import Route
Route.get("/users/{user_id:int}", ShowUserController)

GET /users/42 devuelve {"id": 42, "label": "User 42", "path": "/users/42"}.

  • labels: UserLabelService permite construir e inyectar el servicio en el constructor. Los servicios abstractos o contratos necesitan su binding correspondiente.
  • audit: UserAuditService se resuelve al invocar esta acción. El cliente no lo envía en la URL ni en el payload: es una dependencia del servidor.
  • request: Request obtiene la petición actual registrada en el scope HTTP; no debes construirla manualmente.
  • user_id llega como entero por el convertidor {user_id:int}. Debes aceptar ese nombre en la acción, también cuando recibes Request.
  • Las anotaciones identifican dependencias, pero el convertidor declarado en la ruta determina cómo se convierte el segmento. Añadir : int sólo en la firma no cambia el patrón de ruta.
  • Response es el resultado que devuelve la acción; no es una respuesta precreada que el controlador reciba automáticamente.

Usa el constructor para servicios utilizados por varias acciones de la clase y los parámetros de método para servicios que sólo necesita una acción. Este mecanismo también se aplica a acciones Resource: si renombras __call__ como show, registra [ShowUserController, "show"]. El constructor se resuelve primero y después los argumentos de la acción; los valores de ruta se proporcionan a la acción, no al constructor.

El ejemplo no necesita bindings porque ambos servicios son concretos y autoconstruibles. Si necesitas compartir la misma instancia entre resoluciones, configura su ciclo de vida. La inyección ocurre cuando Orionis construye e invoca el controlador; una llamada Python directa como controller.show(...) requiere que proporciones tú sus argumentos.

Agrupa los controladores en app/http/controllers por responsabilidad o dominio cuando el proyecto crezca; por ejemplo, admin/user permite generar una clase dentro de admin. Utiliza nombres consistentes, Resource para acciones de un recurso e Invokable para operaciones concretas. Son recomendaciones de organización, no restricciones del router.

Mantén la coordinación HTTP en el controlador y delega la lógica de negocio a servicios. La clase base no realiza operaciones de base de datos ni implementa automáticamente las acciones generadas.

Los middleware se ejecutan antes de la invocación y pueden impedir que ésta ocurra. Si una acción falla, las capas HTTP y el kernel delegan su tratamiento al sistema de Excepciones. Para formatos, encabezados, cookies o descargas consulta Respuestas.