sqlalchemy: 2.0.25 alembic: 1.13.1 python: 3.11
alembic revision --autogenerate -m "initial migration"
creates empty upgrade
and downgrade
code in the migration.
As shown below I have two model classes in separate files from the Base class.
My alembic/env.py is follows their docs instructions to import my Base class so that it can access metadata. On an empty db, this should produce upgrade code to create the Order and Item tables but it seems like it's not finding those classes from the Base.metadata. 1. On a hunch I put imports of those classes into the env.py (as shown below) and then the migration is autogenerated correctly. 2. As an alternate workaround I also found that putting all my models into one big file along with Base resulted in complete generation.
Workaround 1 is ok but kind of a hassle as the number of model classes grows. Workaround 2 is unacceptable.
Is there a more elegant and reliable way to get it find all model classes for autogeneration?
#models.base.py
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
#models.order.py
from models.base import Base
class Order(Base):
__tablename__ = "Order"
id: Mapped[int] = mapped_column(primary_key=True)
description: Mapped[Optional[str]]
items: Mapped[List["Item"]] = relationship(back_populates="order")
#models.item.py
from models.base import Base
class Item(Base):
__tablename__ = "Item"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
description: Mapped[str] = mapped_column(String(255))
order_id: Mapped[int] = mapped_column(ForeignKey("Order.id"))
order: Mapped["Order"] = relationship(back_populates="items")
#alembic/env.py (excerpt)
from models.base import Base
from models.order import Order # not prescribed by doc but makes it work
from models.item import Item # same as above
target_metadata = Base.metadata
You can define your Base class in an individual file e.g. base_class.py
, reuse it in your models definition, e.g. order.py
and import all your models into a base.py
file to use in alembic env.py
.
E.g.
# db.base_class.py
from typing import Any
from sqlalchemy.ext.declarative import as_declarative, declared_attr
@as_declarative()
class Base:
id: Any
__name__: str
# Generate __tablename__ automatically
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()
# models.order.py
from db.base_class import Base
class Order(Base):
id: Mapped[int] = mapped_column(primary_key=True)
description: Mapped[Optional[str]]
items: Mapped[List["Item"]] = relationship(back_populates="order")
# models.base.py
from db.base_class import Base # noqa
from models.order import Order # noqa
from models.item import Item # noqa
If you have errors when creating the migration scripts, you can import the models in __init__.py
of models
# models.__init__.py
from .order import Order # noqa
from .item import Item # noqa
With this, you will not have to update alembic.env.py every time you create a model, but you will have to import it in models.base.py
, as well as Order and Item.
# alembic.env.py
import sys
from alembic import context
from sqlalchemy import engine_from_config, pool
from pathlib import Path
sys.path.append(str(Path.cwd()))
from db.base import Base
config = context.config
target_metadata = Base.metadata
...