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,65 @@
|
||||
"""Open a connection to a Microsoft SQL Server (Navision) via pymssql."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def mssql_connect(host: str, database: str, user: str, password: str,
|
||||
port: int = 1433, login_timeout: int = 15,
|
||||
query_timeout: int = 30):
|
||||
"""Open a connection to a Microsoft SQL Server instance (e.g. Navision).
|
||||
|
||||
Uses the pymssql driver. Credentials are always supplied by the caller
|
||||
(typically read from `pass`/env) and never hardcoded. The connection is
|
||||
impure I/O: it touches the network and the database server.
|
||||
|
||||
pymssql expects the TCP port as a string, so `port` is converted before
|
||||
being passed through. `login_timeout` bounds the connect/login phase, which
|
||||
is what keeps an invalid host from hanging indefinitely; `query_timeout`
|
||||
bounds individual queries run on the resulting connection.
|
||||
|
||||
Args:
|
||||
host: SQL Server host or IP. From WSL2 this must be the Windows LAN IP
|
||||
(e.g. "10.0.0.5"), not "localhost" — localhost does not reach the
|
||||
Windows host from inside WSL2.
|
||||
database: Name of the database to connect to (e.g. "navdb").
|
||||
user: SQL Server login user (e.g. "sa").
|
||||
password: Password for the login user. Pass it from `pass`/env, never
|
||||
as a string literal.
|
||||
port: TCP port of the SQL Server instance. Defaults to 1433. Converted
|
||||
to a string internally because pymssql requires a string port.
|
||||
login_timeout: Seconds allowed for the connect/login phase before it
|
||||
fails. Defaults to 15. Keeps an unreachable host from hanging.
|
||||
query_timeout: Seconds allowed for each query executed on the returned
|
||||
connection before it times out. Defaults to 30.
|
||||
|
||||
Returns:
|
||||
An open pymssql.Connection. The caller is responsible for closing it
|
||||
with `.close()` when done.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If pymssql is not installed, or if the connection/login
|
||||
fails. The message includes host:port and database for context and
|
||||
the original exception is chained for debugging.
|
||||
"""
|
||||
# Lazy import so the module loads even without pymssql installed.
|
||||
try:
|
||||
import pymssql
|
||||
except ImportError as exc: # pragma: no cover - exercised only without dep
|
||||
raise RuntimeError(
|
||||
"pymssql is required for mssql_connect; install pymssql"
|
||||
) from exc
|
||||
|
||||
try:
|
||||
return pymssql.connect(
|
||||
server=host,
|
||||
user=user,
|
||||
password=password,
|
||||
database=database,
|
||||
port=str(port),
|
||||
login_timeout=login_timeout,
|
||||
timeout=query_timeout,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
f"mssql_connect failed connecting to {host}:{port}/{database}: {exc}"
|
||||
) from exc
|
||||
Reference in New Issue
Block a user