--- name: query_project_pg kind: pipeline lang: py domain: pipelines version: "1.0.0" purity: impure signature: "def query_project_pg(project: str, sql: str, max_rows: int = 10000) -> dict" description: "Pipeline one-shot que ejecuta un SELECT contra el Postgres de un proyecto conocido del ecosistema (captacion_clientes, seo_analytics) sin reescribir la resolucion del DSN ni la conexion a mano. Compone resolve_pg_dsn(project) (resuelve el DSN desde env var / .env / pass) con pg_query(dsn, sql, max_rows) (SELECT read-only via psycopg2 que devuelve filas como list[dict]). Elimina el patron inline que el agente repetia: grep al .env + fallback a pass + psql crudo. El caller solo pasa el nombre del proyecto y el SQL; el password sale de pass en runtime, nunca hardcodeado. Devuelve lo que devuelve pg_query en exito {status:'ok', columns, rows, row_count, truncated}, o propaga el {status:'error', error} de resolve_pg_dsn si falla la resolucion del DSN (sin tocar Postgres). Sin lanzar." tags: [postgres, postgresql, sql, query, pipelines] uses_functions: [resolve_pg_dsn_py_infra, pg_query_py_infra] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] tested: false tests: [] test_file_path: "" file_path: "python/functions/pipelines/query_project_pg.py" params: - name: project desc: "Nombre del proyecto conocido. Acepta clave canonica ('captacion', 'seo') o alias largo ('captacion_clientes', 'seo_analytics'). Se pasa tal cual a resolve_pg_dsn; un proyecto desconocido propaga su {status:'error'}." - name: sql desc: "Sentencia SQL a ejecutar (pensada para SELECT). Este pipeline no expone parametros posicionales: interpola solo valores constantes y de confianza. Para entradas no confiables usa pg_query directamente con su argumento params (%s)." - name: max_rows desc: "Numero maximo de filas a materializar en memoria (default 10000). Se pasa tal cual a pg_query; si la query produce mas, el resultado se trunca y truncated queda en True." output: "dict. En exito (propaga pg_query): {status:'ok', columns:[str,...], rows:[{col:val,...},...], row_count:int, truncated:bool}. En error: si la resolucion del DSN falla, {status:'error', error:str} de resolve_pg_dsn; si la query falla, {status:'error', error:str} de pg_query. Sin lanzar." --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from pipelines.query_project_pg import query_project_pg # Una sola llamada: resuelve el DSN de captacion_clientes y cuenta filas. res = query_project_pg("captacion", "SELECT COUNT(*) FROM product_opportunities") print(res["status"]) # ok print(res["rows"][0]) # {'count': 42} # Lanzable directo desde la CLI del registry (corre la demo del __main__): # ./fn run query_project_pg ``` ## Cuando usarla Usala cada vez que necesites leer datos del Postgres de un proyecto del ecosistema (captacion_clientes, seo_analytics) en un solo paso, en vez de resolver el DSN a mano y abrir la conexion tu mismo. Es el reemplazo directo del bloque inline `DSN=$(grep ... .env) ; psql "$DSN" -c "SELECT ..."`. Para varias queries con el mismo proyecto, o si necesitas parametros posicionales seguros (%s), resuelve el DSN una vez con `resolve_pg_dsn` y llama a `pg_query` directamente reusando el DSN. ## Gotchas - Impuro: resuelve el DSN (lee env / .env / pass) y abre una conexion a Postgres. Depende del entorno de la maquina y de que el contenedor del proyecto este levantado. - Solo lectura: `pg_query` marca la transaccion `SET TRANSACTION READ ONLY`. No uses este pipeline para INSERT/UPDATE/DELETE. - No expone `params` posicionales: el SQL se ejecuta tal cual. NO interpoles entradas no confiables en el string (riesgo de inyeccion); para eso usa `pg_query` con su argumento `params`. - El resultado se trunca a `max_rows` filas (default 10000) para proteger memoria; revisa `truncated` en la salida. - La ruta del `.env` del proyecto se resuelve relativa a `FN_REGISTRY_ROOT` o, en su defecto, al cwd. Lanza desde la raiz del registry o exporta esa env var.