import importlib import pkgutil from collections.abc import Iterable import sqlalchemy as sa from sqlalchemy.sql.selectable import ForUpdateParameter from fansifter_common.adapters.db.base import Database def save_instance[T](db: Database, *, instance: T, flush: bool) -> T: """Save instance.""" state = sa.inspect(instance, raiseerr=True) if transient := state.transient: # New instance db.session.add(instance) elif state.detached: # Detached instance instance = db.session.merge(instance) elif state.persistent: # Persistent instance pass # Commit or flush the instance. _commit_or_flush(db, flush=transient if flush is None else flush) return instance def delete_instance[T](db: Database, *, instance: T, flush: bool) -> None: """Delete an object from the database.""" db.session.delete(instance) _commit_or_flush(db, flush=flush) def refresh_instance[T]( db: Database, *, instance: T, attribute_names: Iterable[str] | None = None, with_for_update: ForUpdateParameter = None, ) -> None: """Refresh the instance from the database.""" db.session.refresh( instance, attribute_names=attribute_names, with_for_update=with_for_update, ) # Private methods def _commit_or_flush(db: Database, *, flush: bool) -> None: """Commit or flush the instance to the database.""" if not db.in_transaction(): db.session.commit() elif flush: db.session.flush() def autodiscover_models(package: str, *, search: str = "models") -> None: """Import every ```` module and its submodules found anywhere in ``package``. Covers: - ``app.models`` / ``app.models.*`` - ``app.artists.models`` / ``app.artists.models.*`` """ module = importlib.import_module(package) suffix = f".{search}" contains = f".{search}." for _, name, _ in pkgutil.walk_packages(module.__path__, prefix=f"{package}."): if name.endswith(suffix) or contains in name: importlib.import_module(name)