Files
2026-06-26 16:19:48 -04:00

76 lines
1.9 KiB
Python

"""
control/base.py
---------------
Standalone SQLAlchemy foundation for the control plane.
`ControlBase` keeps control-plane table metadata fully separate from the
tenant-schema models declared on `app.db.Model`. The engine is built lazily
from the `CONTROL_DATABASE_URL` environment variable.
"""
import os
from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
# Every control model subclasses this base — its own metadata collection.
ControlBase = declarative_base()
def get_control_database_url() -> str:
"""Return CONTROL_DATABASE_URL or raise a clear error if unset."""
url = os.environ.get('CONTROL_DATABASE_URL')
if not url:
raise RuntimeError(
"CONTROL_DATABASE_URL is not set. Example: "
"mysql+pymysql://jqc_control:password@localhost/jqc_control"
)
return url
_engine = None
_Session = None
def get_engine():
"""Lazily build (and cache) the control-plane engine."""
global _engine
if _engine is None:
_engine = create_engine(
get_control_database_url(),
pool_pre_ping=True,
pool_recycle=1800,
future=True,
)
return _engine
def get_sessionmaker():
"""Lazily build (and cache) the control-plane sessionmaker."""
global _Session
if _Session is None:
_Session = sessionmaker(
bind=get_engine(), future=True, expire_on_commit=False
)
return _Session
@contextmanager
def control_session():
"""Context-managed session — commits on success, rolls back on error.
Usage:
with control_session() as s:
s.add(obj)
"""
session = get_sessionmaker()()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()