CreaRack-SL

Biblioteca: nuevos edge types ORM (reads_from / writes_to) y señales Django (triggers)

Biblioteca: nuevos edge types ORM y señales Django

PR #39 · cherry-pick #4 de la evaluación Understand-Anything · s79 · 2026-05-22 · autor: @Esquembri

Contexto

El grafo de conocimiento de la Biblioteca se construía hasta ahora con cuatro tipos de edge: imports, inherits, contains y calls. Todos ellos son relaciones estructurales (quién importa a quién, qué función llama a qué). Sin embargo, ninguno capturaba la relación semántica más frecuente en un backend Django: qué funciones leen o escriben en qué modelos, ni qué receivers de señales se disparan al cambiar un modelo.

Este PR incorpora tres nuevos edge types al parser AST (scripts/bib_ast.py), derivados del scope reducido acordado tras la evaluación Understand-Anything (cherry-pick #4 de 9 propuestos). Los cinco restantes (subscribes, publishes, middleware, deploys, serves) requieren parsers no-Python o dependencias de frameworks externos y se posponen.

Nuevos edge types

reads_from — Lecturas ORM

Patrón detectado: <Model>.objects.<METHOD>(...) donde <METHOD> ∈ ORM_READ_METHODS.

Edge: source_qname (función/método) → target_qname (modelo Django)

Métodos cubiertos (20):

GrupoMétodos
Recuperación básicafilter, get, all, first, last, none
Conteo / existenciacount, exists
Proyecciónvalues, values_list, only, defer
Agregaciónaggregate, annotate
Optimizaciónselect_related, prefetch_related
Ordenación temporallatest, earliest
Bulk / especialesin_bulk, iterator, raw

writes_to — Escrituras ORM

Patrón detectado: <Model>.objects.<METHOD>(...) donde <METHOD> ∈ ORM_WRITE_METHODS.

Edge: source_qname (función/método) → target_qname (modelo Django)

Métodos cubiertos (8):

MétodoSemántica
createInserción unitaria
bulk_createInserción masiva
updateActualización en queryset
deleteEliminación en queryset
bulk_updateActualización masiva
get_or_createLookup + inserción condicional
update_or_createUpsert
bulk_create_or_updateUpsert masivo

triggers — Django signals (@receiver)

Patrón detectado: decorator @receiver(<signal>, sender=<Model>).

Edge: source_qname (función receiver) → target_qname (modelo sender)

Resolución del sender:

  • sender=MyModel (Name) → se resuelve via imports del módulo.
  • sender="app.Model" (string literal) → se usa como qname directo.
  • @receiver(signal) sin sender= → no se crea edge (señal global, sin modelo concreto asociado).

Convención semántica: el edge va de la función receiver → modelo, en la dirección “función que se dispara desde el modelo”. Se sigue la convención Understand-Anything: el receiver “triggers” su lógica desde eventos del modelo.


Implementación técnica

El cambio se concentra íntegramente en scripts/bib_ast.py (+104 LOC):

scripts/bib_ast.py
  ├── ORM_READ_METHODS   (set, 20 strings)
  ├── ORM_WRITE_METHODS  (set, 8 strings)
  ├── SIGNAL_DECORATOR_NAMES  (set, {"receiver"})
  ├── visit_Call()       [extendido] → detecta X.objects.METHOD
  └── _visit_function()  [extendido] → detecta @receiver(...sender=...)

Lógica visit_Call (reads_from / writes_to):

  1. Se obtiene el callee como string con puntos (_call_target).
  2. Se splitea por . → se verifica parts[-2] == "objects" y len(parts) >= 3.
  3. parts[0] (alias del modelo) se resuelve via _resolve_name.
  4. parts[-1] (método) se clasifica en ORM_READ_METHODS / ORM_WRITE_METHODS.
  5. Se emite edge con source_qname = _current_func.

Lógica _visit_function (triggers):

  1. Se itera node.decorator_list.
  2. Se verifica isinstance(dec, ast.Call) y nombre en SIGNAL_DECORATOR_NAMES.
  3. Se busca keyword arg="sender".
  4. Se resuelve el valor (Constant string o Name).
  5. Se emite edge con source_qname = módulo.función (o clase.método).

Resultado en corpus

Corpus completo: 340 archivos .py, 7 apps.

MétricaValor
Edges pre-cambio~5.984
Edges post-cambio~6.644
Nuevos edges totales+660
— reads_from516
— writes_to143
— triggers1
Ratio enriquecimiento~10%
Falsos positivos (sample manual)0

Scope descartado (iteración futura)

Los otros 5 edge types propuestos por Understand-Anything se descartaron por complejidad de parser o dependencias externas:

Edge typeMotivo del descarte
subscribes / publishesRequiere parser de Django Channels / Huey
middlewareSensible a configuración de framework externo
deploys / servesRequiere parsers Dockerfile / docker-compose.yml

Se retoman si emergen consultas concretas del Oráculo que los necesiten.

Riesgo y rollback

Riesgo: BAJO. El cambio solo añade edges nuevos, no modifica ni elimina nodos ni edges existentes.

Impacto de fallo en cron Hetzner:

  • El grafo queda obsoleto pero no se corrompe.
  • Oráculo y Help Widget siguen sirviendo con datos pre-cambio.

Procedimiento de rollback:

# Restaurar backup pre-cherry-pick (s79)
rsync /opt/biblioteca-crons-bak-20260522-pre-cherrypick/ /opt/biblioteca-crons/
# + revert del commit de este PR en main
git revert <SHA>

Backup D1 adicional disponible en C:\dev\backups\d1-pre-cherrypick.


Véase también

  • [[decision—20260522—evaluacion-understand-anything-vs-bibliotecario]]