Volver a la wiki

Cierre de R7 — Toda la API valida entrada con Ninja Schemas (v1.45.2)

Resumen

Segunda y última mitad de la rectificación de validación de entrada en la auditoría integral s187: los 12 endpoints que quedaban sin tipar migraron de json.loads + dict.get() a Ninja Schemas declarativos. Con esto se cierra R7 completo (P2 en v1.45.0 + P1a en v1.45.1 + P1b en v1.45.2) y, con él, toda la cola de s187 (R1-R7).

Impacto de usuario

Ninguno visible — los campos y comportamiento (errores 400) son idénticos. Es endurecimiento interno: cada endpoint ahora valida y convierte datos de entrada en objetos typados antes de procesarlos, en lugar de manipular diccionarios anónimos.


Cambios en R7 lote P1b

Endpoints migrados (12 total)

AppEndpointSchemaNotas
blueprintsPOST /{bp_id}/annotationsAnnotationInputSchemacreate_annotation: data libre-form (dict Konva o JSON string)
POST /{bp_id}/delete_connectionConnectionDeleteInAlias from/to → from_id/to_id
POST /{bp_id}/duplicate_elementDuplicateElementInCopia de rack o anotación
POST /prompt/savePromptSaveInGuardar prompt del AI Brain
POST /prompt/save-asPromptSaveAsInGuardar con nombre
signagePOST /signage/playlists/preview-svgPreviewSvgInItems libres (SVG composer los valida internamente)
POST /signage/deploy-contentDeployContentInWebDAV + credenciales opcionales
POST /signage/publish-setupPublishSetupInConfig de SignagePlayer
networkPOST /mib-assistant/applyMibProposalInMerge sin sobrescritura de propuesta de OIDs
POST /mac/oui-lookupOuiLookupInBatch lookup de MAC → fabricante
terminalPOST /fleet/configFleetConfigInModo de asignación de roles (auto/manual)
core/HelpPOST /ask + POST /ask-streamHelpAskInProxy a workspace bib_ask + variante streaming

Schemas nuevos creados

# blueprints/api/schemas.py
class AnnotationInputSchema(Schema):
    type: str | None = None
    data: Any = None  # free-form Konva dict/string JSON

class ConnectionDeleteIn(Schema):
    from_id: int | str = Field("", alias="from")  # alias palabra reservada
    to_id: int | str = Field("", alias="to")
    type: str = "rack_connection"

class DuplicateElementIn(Schema):
    type: str = ""
    id: int | None = None

# blueprints/api/prompt.py
class PromptSaveIn(Schema):
    content: str = ""

class PromptSaveAsIn(Schema):
    name: str = ""
    content: str = ""

# signage/api/schemas.py
class PreviewSvgIn(Schema):
    items: list[dict] = []  # libre-form — forma interna validada por composer

# monitoring/api/signage/deploy.py
class DeployContentIn(Schema):
    profile_id: int | None = None
    asset_ids: list[int] = []
    username: str = ""
    password: str = ""  # credenciales WebDAV opcionales

class PublishSetupIn(Schema):
    profile_id: int | None = None
    clear_action: str | None = None
    corporate_asset_id: int | None = None
    playlist_id: int | None = None
    schedule_id: int | None = None
    playback: dict | None = None

# network/api/vendor.py
class MibProposalIn(Schema):
    monitoring_oids: dict = {}
    deep_discovery_oids: dict = {}

class OuiLookupIn(Schema):
    macs: list[str] = []

# terminal/api/fleet.py
class FleetConfigIn(Schema):
    role_mode: str = "auto"

# core/api_help.py
class HelpAskIn(Schema):
    question: str = ""
    mode: str | None = None  # "help" | "tutor"
    history: list[dict] | None = None  # multi-turn chat (tutor)
    source_type: str | None = None
    app: str | None = None

Correcciones importantes

AnnotationInputSchema reescrito:

Excepción deliberada: update_annotation

Cambios mínimos de implementación

Patrón general:

# Antes
try:
    body_data = json.loads(request.body) if request.body else {}
except (json.JSONDecodeError, TypeError):
    body_data = {}
value = body_data.get("key", default)

# Ahora
def endpoint(request, payload: SchemaClass):
    value = payload.key_name  # o payload.dict() si se necesita dict completo

Diff típico: 6-10 líneas de boilerplate json/dict removidas por endpoint. La lógica de negocio intacta.


Hitos de R7 completo

LoteVersiónEndpointsÁmbito
P2v1.45.06Endpoints generales (CRUD de blueprints, racks, placements)
P1av1.45.15Mutadores de racks (create, update, delete, etc.)
P1bv1.45.2 (esta)12Blueprints (annotations, connections, prompts), signage, network, terminal, Help
Total—23 endpointsAuditoría s187 R1-R7 completada

Cola s187 cerrada

Con R7 cierran todos los lotes de s187:

Quedan aparte: decisiones de stack “M” (psycopg3, Vite 7) que se resolverán en futuras iniciativas. Task #165 (save_device_network_config management_config) documentado como out-of-scope deliberado.


Patrones técnicos establecidos

Uso de alias en Pydantic

Campos con nombres reservados de Python o conflictos JSON → Field(..., alias="..."):

class ConnectionDeleteIn(Schema):
    from_id: int | str = Field("", alias="from")  # JSON: "from"
    to_id: int | str = Field("", alias="to")      # JSON: "to"

Cliente envía {"from": 123, "to": 456} → Ninja deserializa automáticamente.

Campos libres (Any, dict)

Algunos endpoints aceptan estructuras sin forma fija → se documentan explícitamente:

class AnnotationInputSchema(Schema):
    data: Any = None  # Komva dict serializado o JSON string — validación interna en handler

class PreviewSvgIn(Schema):
    items: list[dict] = []  # Forma interna consumida por svg_composer

Alternativa rechazada: json.loads manual. Razón: Ninja Schemas dan tipado + documentación OpenAPI automática, aunque algunos campos sean libres.

Migración progresiva

No fue “big bang”: R7 se dividió en 3 lotes por ciclo de auditoría, permitiendo review y tests granulares. Cada lote en su PR y versión minor.


Verificación y testing


Véase también

Subir