Skip to content

Support subclassing models for default attributes with alembic migration support similar to sqlalchemy declarative base. #687

Open
@bubthegreat

Description

@bubthegreat

NOTE: This might just be because I couldn't find any documentation on the proper way to do this!

  • GINO version: current
  • Python version: 3.7.x
  • asyncpg version: current
  • aiocontextvars version: current
  • PostgreSQL version: current

Description

I want to be able to subclass GINO db.Model and have the inheritance still respect alembic migration support.

What I Did

I created a subclass factory from db.Model with the intent of adding some common configurable attributes to the model class (For FastAPI

import logging

from gino.ext.starlette import Gino


from .. import config

LOGGER = logging.getLogger(__name__)

db = Gino(
    dsn=config.DB_DSN,
    pool_min_size=config.DB_POOL_MIN_SIZE,
    pool_max_size=config.DB_POOL_MAX_SIZE,
    echo=config.DB_ECHO,
    ssl=config.DB_SSL,
    use_connection_for_request=config.DB_USE_CONNECTION_FOR_REQUEST,
    retry_limit=config.DB_RETRY_LIMIT,
    retry_interval=config.DB_RETRY_INTERVAL,
)
LOGGER.info("Database initialized: %s", db)


def CRUDModel(table, validator=None):
    class Base(db.Model):

        __tablename__ = table

        id = db.Column(db.BigInteger(), primary_key=True)

        @classmethod
        def _prefix(cls):
            return table

        @classmethod
        def _tags(cls):
            return [table]

        @classmethod
        def _validator(cls):
            return validator

    return Base


This let me add some default routers programmatically:



def add_router(app, model):

    LOGGER.info("Loading model %s.", model)

    router = APIRouter()

    @router.get("/{uid}")
    async def get_item(uid: int):
        instance = await model.get_or_404(uid)
        return instance.to_dict()

    @router.post("/")
    async def add_item(item: model._validator()):
        instance = await model.create(nickname=item.name)
        return instance.to_dict()

    @router.delete("/{uid}")
    async def delete_item(uid: int):
        instance = await model.get_or_404(uid)
        await instance.delete()
        return dict(id=uid)

    app.include_router(
        router,
        prefix=f"/{model._prefix()}",
        tags=model._tags(),
        responses={404: {"description": "Not found"}},
    )

The problem is that alembic doesn't recognize the subclass attributes, so when I try and create this subclass:


class UserValidator(BaseModel):
    first_name: str
    last_name: str


class User(CRUDModel('users', UserValidator)):

    first_name = db.Column(db.Unicode(), default="unnamed")
    last_name = db.Column(db.Unicode(), default="unnamed")

The id field from the CRUDModel Base is the only attribute that's found by alembic's automigrations generation. Either I'm subclassing this wrong, or it's not supported by the db.Model the same way that sqlalchemy allows for (Subclassing a common base class for default attributes).

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionA community question, closed when inactive.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions