Declarative Base (AppBase)¶
This module defines the project's canonical SQLAlchemy declarative base (AppBase) and re-exports the Mapped and mapped_column helpers to standardize ORM model definitions across the codebase.
Overview¶
AppBaseis a Singleton Component subclassingsqlalchemy.orm.DeclarativeBase.- All ORM models in the project must subclass
AppBase. - It acts as the central registry for your database schema (
MetaData). - It is integrated with Pico-IoC, allowing you to inject the registry into other components (like database configurers).
1. Defining Models¶
You should define your models by inheriting from AppBase. Use the re-exported Mapped and mapped_column for clean, typed definitions compatible with SQLAlchemy 2.0.
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import relationship
# Import from the library
from pico_sqlalchemy import AppBase, Mapped, mapped_column
class User(AppBase):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(50), unique=True)
posts: Mapped[list["Post"]] = relationship(back_populates="author")
class Post(AppBase):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
content: Mapped[str] = mapped_column(String)
author: Mapped["User"] = relationship(back_populates="posts")
2. Dependency Injection & Schema Management¶
Unlike standard SQLAlchemy apps where you might access Base.metadata globally, pico-sqlalchemy registers AppBase as a Singleton Component.
This allows you to inject it into your DatabaseConfigurer to perform schema operations (like creating tables) during startup.
Example: Creating Tables on Startup¶
import asyncio
from pico_ioc import component
from pico_sqlalchemy import DatabaseConfigurer, AppBase
@component
class TableCreationConfigurer(DatabaseConfigurer):
# 1. Inject AppBase (The singleton registry)
def __init__(self, base: AppBase):
self.base = base
def configure_database(self, engine):
async def init_schema():
async with engine.begin() as conn:
# 2. Access metadata through the injected instance
await conn.run_sync(self.base.metadata.create_all)
asyncio.run(init_schema())
3. Best Practices¶
- Inheritance: Always subclass
AppBasefor your entities. Do not create your ownDeclarativeBase. - Imports: Import
Mappedandmapped_columnfrompico_sqlalchemyto ensure version consistency. - Injection over Globals: While
AppBase.metadatais technically accessible statically, prefer injectingAppBaseinto services or configurers that need access to the schema registry. This makes your components easier to test and mock.
4. Migrations (Alembic)¶
For external tools like Alembic which run outside the IoC container context, you can still access the metadata statically.
In your alembic/env.py:
from pico_sqlalchemy import AppBase
from myapp.models import User, Post # Import models to register them
# Point Alembic to the shared metadata
target_metadata = AppBase.metadata
Auto-generated API¶
pico_sqlalchemy.base ¶
SQLAlchemy declarative base registered as a pico-ioc singleton.
All application ORM models should inherit from AppBase so that they share a single MetaData registry, which is required for DatabaseConfigurer hooks (e.g. metadata.create_all) and for Alembic migrations.
Mapped and mapped_column are re-exported from SQLAlchemy for convenience.
Example::
from pico_sqlalchemy import AppBase, Mapped, mapped_column
from sqlalchemy import String
class User(AppBase):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
AppBase ¶
Bases: DeclarativeBase
Central SQLAlchemy DeclarativeBase for all application models.
Registered as a pico-ioc singleton so that the same MetaData instance is shared across the entire application. All ORM model classes should inherit from this base.