absent1706 / sqlalchemy-mixins

Active Record, Django-like queries, nested eager load and beauty __repr__ for SQLAlchemy
MIT License
756 stars 67 forks source link

sqlalchemy-mixins incompatible with Flask-SQLAlchemy's custom model class recommendation. #34

Open kkinder opened 4 years ago

kkinder commented 4 years ago

This is a little convoluted to explain, but I've found that, sqlalchemy-mixin does not exactly work with Flask-SQLAlchemy as both are documented.

According to the Flask-SQLAlchemy docs, if you want to define your own base class, you pass it to SQLAlchemy(). For example:

from flask_sqlalchemy import Model

class MyModel(Model):
    @declared_attr
    def id(cls):
        return db.Column(db.Integer, primary_key=True, index=True)

db = SQLAlchemy(model_class=MyModel)

class Pet(db.Model):
    ...

Notice that you're inheriting from db.Model, not from MyModel. That's because, when I pass model_class to SQLAlchemy, it does some magic afterwards to actually add declarative_base() as a superclass and generates a final class that merges the two.

Why is this important, you ask? Consider this code block:

from flask_sqlalchemy import Model
from sqlalchemy_mixins import AllFeaturesMixin

class MyModel(Model. AllFeaturesMixin):
    @declared_attr
    def id(cls):
        return db.Column(db.Integer, primary_key=True, index=True)

db = SQLAlchemy(model_class=MyModel)

While I'm using AllFeaturesMixin and Flask-SQLAlchemy's Model class as recommended, I'll get an exception.

    return db.Column(db.Integer, primary_key=True, index=True)
NameError: name 'db' is not defined

The reason is that Flask-SQLAlchemy's SQLAlchemy() class does not expect a subclass from Base in Base = declarative_base(). It mixes in declarative base on its own.

That is to say, with straight Flask-SQLAlchemy, the part of the above code that resolves the declared_attr doesn't run until db is instanciated. But because sqlalchemy-mixins inherits directly from Base, its declared_attrs are resolved immediately and there's a NameError.

My general suspicion is that the solution is for sqlalchemy-mixins to perhaps be mixins, not subclasses of Base. But I'm not sure. At any rate, I thought I would bring up the issue for consideration. Thanks for the great library!

phretor commented 4 years ago

@kkinder why do you do this?

class Pet(db.Model):

and not this:

class Pet(MyModel):
kkinder commented 4 years ago

@phretor because I'm using Flask-SQLAlchemy and that's how you pull in its goodness.

maximdeclercq commented 4 years ago

I managed to implement something like this as follows:

...
db = SQLAlchemy(app)

class BaseModel(db.Model, AllFeaturesMixin):
    __abstract__ = True

BaseModel.set_session(db.session)

class User(BaseModel):
    id = db.Column(db.Integer, primary_key=True)

I hope this works for you.

lekhnath commented 1 year ago

Did not work

lekhnath commented 1 year ago

Getting error:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases