--- name: tryton-rpc-probe description: Use cuando se necesite hacer requests RPC de prueba contra Tryton (probes) con el paquete sabatron-tryton-rpc-client. Dispara ante tryton, sabatron, probe rpc, model.product.search/read, model.product.category.read, fields_get, credenciales Tryton staging o verificaciones empíricas de respuestas RPC. NO usar curl: solo el paquete de python. --- # Probes RPC a Tryton con sabatron-tryton-rpc-client Guía para ejecutar requests RPC de prueba (probes) contra Tryton dentro de este proyecto, usando el paquete `sabatron-tryton-rpc-client` y el contenedor de desarrollo. Sirve para verificar campos, modelos, credenciales y respuestas antes de planear o implementar cambios de integración (TDD: confirmar empíricamente antes de asumir). ## Reglas obligatorias - **Nunca usar curl.** Solo el paquete python `sabatron-tryton-rpc-client` (el RPC de Tryton enmascara/muestra estados HTTP distintos que confunden a curl). - **No hardcodear credenciales**: leer los valores `TRYTON_HOST`, `TRYTON_DATABASE`, `TRYTON_USERNAME`, `TRYTON_PASSWORD` de `.env` (raíz del proyecto) y pasarlos al contenedor con `-e`. No imprimir la contraseña (a lo sumo imprimir el nombre de la base de datos). - **Respetar rate-limit**: ante `HTTP 429` esperar (p. ej. ~120s) antes de reintentar; no hacer ráfagas de logins fallidos. - Verificar primero el login/credenciales con una sonda mínima antes de asumir que falla por campos o datos. ## Comando base (plantilla) Todos los probes se ejecutan con `docker compose` dev sin levantar dependencias (`--no-deps`): ```bash docker compose -f docker-compose.dev.yml run --rm --no-deps \ -e TRYTON_HOST= \ -e TRYTON_DATABASE= \ -e TRYTON_USERNAME= \ -e TRYTON_PASSWORD= \ django python -c ' import os from sabatron_tryton_rpc_client.client import Client c = Client(hostname=os.environ["TRYTON_HOST"], database=os.environ["TRYTON_DATABASE"], username=os.environ["TRYTON_USERNAME"], password=os.environ["TRYTON_PASSWORD"]) c.connect() print("CONNECTED OK", os.environ["TRYTON_DATABASE"]) ctx = {"company": 1} print(c.call("model.product.product.search", [[["salable", "=", True]], 0, 5, [["rec_name", "ASC"], ["id", None]], ctx])) ' ``` Patrón de la librería: ```python from sabatron_tryton_rpc_client.client import Client c = Client(hostname=..., database=..., username=..., password=...) c.connect() # login resultado = c.call("modelo.metodo", (args..., contexto)) # contexto {"company": 1} ``` ## Ver estados HTTP reales (401/403/429 enmascarados) La librería **enmascara** los errores HTTP del login como `UnboundLocalError: cannot access local variable 'response'...` (bug en `jsonrpc.py:__request`). Para ver el código real, monkeypatchear el transporte XML-RPC y capturar `ProtocolError`: ```python import xmlrpc.client from sabatron_tryton_rpc_client import jsonrpc orig = xmlrpc.client.Transport.request def wrapped(self, host, handler, body, verbose=False): try: return orig(self, host, handler, body, verbose=verbose) except xmlrpc.client.ProtocolError as e: print("HTTP_STATUS", e.errcode, repr(e.errmsg)) raise jsonrpc.Transport.request = wrapped # luego Client(...).connect() / ServerProxy(...) ``` Guía rápida de códigos vistos en el proyecto: - `HTTP 200` + fault JSON (p. ej. `FATAL: database "..." does not exist`): la DB no existe o el endpoint responde con error de aplicacion. - `HTTP 401`: credenciales rechazadas (usuario/password) en esa base de datos. - `HTTP 403`: ruta/método denegado (p. ej. `common.db.list`). - `HTTP 429`: rate-limit, esperar antes de reintentar. ## Convención de campos RPC de Tryton (agrupación con punto) Los campos con punto (ej. `template.categories.name`) NO llegan como una clave plana: Tryton los agrupa bajo la clave del modelo padre con puntos. En `model.product.product.read`, con los fields `["id", "name", "template.categories", "template.categories.name"]`, la respuesta llega así: ```json { "id": 488, "name": "Cloruro de magnesio", "template.": { "id": 1248, "categories": [6], "categories.": [{"id": 6, "name": "Alimentos Transformados Y Granos"}] } } ``` Combinaciones verificadas en staging (Tryton 8): | fields solicitados | claves resultantes | |---|---| | `template.categories` | `categories: [ids]` (sin `categories.`) | | `template.categories.id` | `categories.: [{"id": ...}]` | | `template.categories.name` | `categories.: [{"id", "name"}]` (id viene incluido) | | ambos `.id` + `.name` | igual que solo `.name` | Conclusión operativa: pedir `template.categories.name` basta para traer ids y nombres; `template.categories.id` es redundante. No hay `active` en `product.category` (fields_get lo confirma) → activar/desactivar categorías es solo local. ## Ejemplos de sondas útiles Login + datos de productos con categorias anidadas: ```python ctx = {"company": 1} pids = c.call("model.product.product.search", [[["salable", "=", True]], 0, 1000, [["rec_name", "ASC"], ["id", None]], ctx]) detalles = c.call("model.product.product.read", (pids, ["id", "name", "default_uom.id", "default_uom.rec_name", "list_price", "template.categories", "template.categories.name"], ctx)) ``` Listar los campos reales de un modelo (evita asumir campos): ```python campos = c.call("model.product.category.fields_get", ([], ctx)) print(sorted(campos)) ``` Listar bases de datos (suele estar bloqueado con 403): ```python from sabatron_tryton_rpc_client.jsonrpc import ServerProxy p = ServerProxy(hostname, 8000, "") print(p.common.db.list()) ``` ## Integración con tests - Los tests de integración NO llaman Tryton real: mockean `sabatron_tryton_rpc_client.client.Client.call` y `.connect` (ver `don_confiao/tests/test_products_from_tryton.py`, `test_categories_from_tryton.py`). - Los probes de esta skill son solo para verificación empírica/planeación contra staging, no para la suite de tests.