Saltar a contenido

Sesiones y transacciones

Sin hacer nada

Cada llamada a un DAO abre su propia sesión, ejecuta, hace commit y la cierra. Para la mayoría de operaciones, eso es lo correcto y no hay que pensar en ello.

public_sync_api.usuario.create(UsuarioCreate(nombre='Ana'))   # una transacción

Varias operaciones en una transacción

Todos los métodos aceptan session=. Pasando la misma sesión a varias llamadas, todas van en la misma transacción: o se aplican todas, o ninguna.

from database.public import sync_session_manager, public_sync_api, UsuarioCreate, PostCreate

with sync_session_manager.get_session() as session:
    usuario = public_sync_api.usuario.create(
        UsuarioCreate(nombre='Ana', email='ana@x.com'), session=session
    )
    public_sync_api.post.create(
        PostCreate(titulo='Primero', contenido='...', autor_id=usuario.id),
        session=session,
    )
    # commit al salir del bloque

Si salta una excepción dentro del bloque, se hace rollback y la excepción se propaga: no queda nada a medias.

try:
    with sync_session_manager.get_session() as session:
        public_sync_api.usuario.create(UsuarioCreate(...), session=session)
        raise RuntimeError('algo ha ido mal')
except RuntimeError:
    pass     # el usuario no se ha creado

El pool

El session manager mantiene un pool de conexiones que se configura en el generador:

PythonClientGenerator(
    output_dir='database',
    pool_size=5,          # conexiones permanentes
    max_overflow=5,       # adicionales bajo demanda
    pool_timeout=30,      # segundos esperando una conexión libre
    pool_recycle=3600,    # segundos antes de renovar una conexión
    pool_pre_ping=True,   # comprueba la conexión antes de usarla
    ssl=True,
    sqlalchemy_logs=False,
)

La URL sale de la variable de entorno que declare el env(...) del schema, leída en runtime: el mismo cliente apunta a una base de datos distinta en cada entorno sin regenerar nada.

ssl=False solo llega al motor asíncrono

connect_args={'ssl': False} es de asyncpg; psycopg2 no acepta ese argumento —lo suyo es sslmode en la URL—, así que emitirlo en los dos rompería el cliente síncrono. Para el caso síncrono, el sslmode va en la URL de conexión.

Modo asíncrono

Con mode='both' (por defecto) o mode='async', la misma API existe en asíncrono:

from database.public import public_async_api, async_session_manager, UsuarioCreate

usuario = await public_async_api.usuario.create(UsuarioCreate(nombre='Ana'))

async with async_session_manager.get_session() as session:
    await public_async_api.post.create(PostCreate(...), session=session)

Requiere el driver asíncrono del motor en el proyecto (tai-sql install --async).

Un solo event loop

El session manager asíncrono es un singleton de módulo: su pool queda atado al event loop que lo estrena. Si tu código crea varios loops —algunos frameworks de test lo hacen—, el segundo fallará con «Event loop is closed».

BigQuery no tiene cliente asíncrono

No existe dialecto asíncrono para BigQuery, así que ahí hay que generar con mode='sync'.

Logs

El cliente registra cada operación con el logger que nombre logger_name ('tai-sql' por defecto), y los mensajes de una transacción se emiten al hacer commit, no antes: lo que ves en el log es lo que quedó aplicado.

PythonClientGenerator(output_dir='database', logger_name='mi-app.db')