feat(infra): conexion y consulta directa a SQL Server (Navision) via pymssql
Grupo de capacidad nuevo 'sql-connect' (3 funciones) para conectar a un
Microsoft SQL Server (donde corre Navision) y consultar directamente, en
lugar del ida y vuelta manual de pegar CSVs.
- mssql_connect_py_infra: abre conexion pymssql (login_timeout acotado,
credenciales por argumento, RuntimeError claro si falla).
- mssql_query_py_infra: SELECT parametrizada con binding seguro (sin
inyeccion) sobre conexion abierta; devuelve {columns, rows, row_count};
0 filas -> lista vacia; max_rows con fetchmany; read-only.
- run_mssql_query_py_pipelines: one-shot que compone connect+query y cierra
siempre; CLI imprime JSON o CSV; contrasena desde env var (pass).
Pagina madre docs/capabilities/sql-connect.md + fila en INDEX.md.
Dependencia pymssql>=2.3.13 anadida a python/pyproject.toml + uv.lock.
Tests mock-based (11) verdes; error path verificado end-to-end contra el
driver real (host inalcanzable -> RuntimeError, acotado por login_timeout).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
---
|
||||
name: mssql_connect
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def mssql_connect(host: str, database: str, user: str, password: str, port: int = 1433, login_timeout: int = 15, query_timeout: int = 30) -> pymssql.Connection"
|
||||
description: "Abre una conexion pymssql a un Microsoft SQL Server (donde corre Navision). Las credenciales llegan siempre por argumento (el caller las saca de pass/env), nunca hardcodeadas. login_timeout acota la fase de conexion/login para evitar cuelgues con un host inalcanzable. Devuelve el objeto conexion pymssql para iterar queries despues."
|
||||
tags: [mssql, sqlserver, navision, sql-connect, infra]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [pymssql]
|
||||
params:
|
||||
- name: host
|
||||
desc: "Host o IP del servidor SQL Server. Desde WSL2 debe ser la IP LAN de Windows (ej. 10.0.0.5), no localhost."
|
||||
- name: database
|
||||
desc: "Nombre de la base de datos a la que conectar (ej. navdb)."
|
||||
- name: user
|
||||
desc: "Usuario de login de SQL Server (ej. sa)."
|
||||
- name: password
|
||||
desc: "Contrasena del usuario de login. Se pasa desde pass/env, nunca como literal."
|
||||
- name: port
|
||||
desc: "Puerto TCP del SQL Server. Por defecto 1433. La funcion lo convierte a string porque pymssql lo exige asi."
|
||||
- name: login_timeout
|
||||
desc: "Segundos permitidos para la fase de conexion/login antes de fallar. Por defecto 15. Evita que un host inalcanzable cuelgue indefinidamente."
|
||||
- name: query_timeout
|
||||
desc: "Segundos permitidos para cada query ejecutada sobre la conexion devuelta antes de hacer timeout. Por defecto 30."
|
||||
output: "Un objeto pymssql.Connection abierto. El caller es responsable de cerrarlo con .close() al terminar."
|
||||
tested: true
|
||||
tests: ["test_golden_connect_passes_string_port_and_kwargs", "test_error_path_wraps_failure_with_host"]
|
||||
test_file_path: "python/functions/infra/mssql_connect_test.py"
|
||||
file_path: "python/functions/infra/mssql_connect.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "python", "functions"))
|
||||
from infra.mssql_connect import mssql_connect
|
||||
|
||||
# La IP debe ser la IP LAN del servidor Windows: desde WSL2 "localhost" NO
|
||||
# llega al host Windows. La contrasena llega del entorno, nunca literal.
|
||||
conn = mssql_connect(
|
||||
host="10.0.0.5",
|
||||
database="navdb",
|
||||
user="sa",
|
||||
password=os.environ["MSSQL_PASSWORD"],
|
||||
port=1433,
|
||||
login_timeout=15,
|
||||
)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT TOP 1 name FROM sys.databases")
|
||||
print(cur.fetchone())
|
||||
finally:
|
||||
conn.close()
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Usala cuando necesites abrir una conexion a un Microsoft SQL Server (donde
|
||||
corre Navision) antes de iterar queries con `mssql_query`. Es el primer paso
|
||||
de cualquier pipeline que lea datos de Navision: abre la conexion una vez,
|
||||
reutilizala para varias queries, y cierrala al final. Triggers: "conecta a
|
||||
Navision", "lee de SQL Server", "abre conexion mssql".
|
||||
|
||||
## Gotchas
|
||||
|
||||
- WSL2 -> Windows: usa la IP LAN del servidor Windows, NUNCA `localhost`. Desde dentro de WSL2 `localhost` no alcanza el host Windows (el reenvio de localhost solo funciona Windows -> WSL, no al reves).
|
||||
- pymssql necesita el puerto como string. La funcion ya convierte `port` a `str(port)` internamente, asi que tu pasas un int normal.
|
||||
- `login_timeout` esta acotado (15s por defecto) precisamente para que un host inalcanzable o mal configurado falle con un RuntimeError claro en vez de colgarse indefinidamente. Ajustalo si la red es lenta, pero no lo dejes sin limite.
|
||||
- Credenciales NUNCA hardcodeadas: `user`/`password` llegan por argumento desde `pass`/env. No las escribas literales en el codigo del caller.
|
||||
- Cierra la conexion con `.close()` al terminar (idealmente en un `finally`). La funcion devuelve un handle abierto y no gestiona su ciclo de vida.
|
||||
- Requiere `pymssql` instalado en el venv (import perezoso: el modulo importa sin la dependencia, pero la llamada falla con RuntimeError claro si falta).
|
||||
Reference in New Issue
Block a user