Paso 1 legacy_db_read: la única puerta a la legacy
En m-b-core, shared/legacy_db/legacy_db.py:228 define async def legacy_db_read(sql: str, params: dict | None = None) -> list[dict]: "Run one read-only query and return rows as dicts. The default for reads." Todos los queries.py la importan. Nosotros escribimos la misma firma sobre SQLite:
"""Settings: the ONLY module that reads environment variables (as in m-b-core)."""
import os
class Settings:
ENV: str = os.getenv("ENV", "dev")
LEGACY_DB_URL: str = os.getenv("LEGACY_DB_URL", "sqlite+aiosqlite:///./legacy.db")
settings = Settings()"""Read access to the legacy DB.
In m-b-core this module wraps aiomysql (legacy MySQL) / asyncpg (Postgres replica).
Here it wraps SQLite via aiosqlite so the practices run on any laptop, but the
contract is the same: `legacy_db_read(sql, params) -> list[dict]`, SQL always
with named parameters (`:name`), never interpolated values.
"""
from __future__ import annotations
from typing import Any
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
from shared.settings import settings
_engine: AsyncEngine | None = None
def get_engine() -> AsyncEngine:
global _engine
if _engine is None:
_engine = create_async_engine(settings.LEGACY_DB_URL)
return _engine
async def legacy_db_read(sql: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]:
"""Run one read-only query and return rows as dicts. The default for reads."""
async with get_engine().connect() as conn:
result = await conn.execute(text(sql), params or {})
return [dict(row) for row in result.mappings().all()]from .legacy_db import legacy_db_read
__all__ = ["legacy_db_read"]text() y no un ORM. Guideline 10 de m-b-core: "Queries are raw SQL via SQLAlchemy text()". text() entiende :nombre como parámetro y se lo pasa al driver ya separado del SQL: el valor nunca se concatena. result.mappings().all() devuelve filas como diccionarios: es la parte que en PHP hacías con fetch_assoc. El global _engine es un singleton perezoso: un pool por proceso, no una conexión por llamada.