Description
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).