Mixin in SQLAlchemy can be used to share functionalities and attributes between Models. But as I undersand, it also achieve using inheritance over abstract class. So my question is what is the difference between the following two implementations:
Using abstract Model:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declared_attr,
db = SQLAlchemy()
class BaseModel(db.Model):
__abstract__ = True
#declared_attr
def iid(cls):
return db.Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
#declared_attr
def created_on(cls):
return db.Column(db.DateTime, server_default=db.func.now())
#declared_attr
def updated_on(cls):
return db.Column(
db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now()
)
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
class SomeEntity(BaseModel):
some_field = db.Column(db.String)
Using Mixin:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declared_attr,
db = SQLAlchemy()
class Mixin(object):
#declared_attr
def iid(cls):
return db.Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
#declared_attr
def created_on(cls):
return db.Column(db.DateTime, server_default=db.func.now())
#declared_attr
def updated_on(cls):
return db.Column(
db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now()
)
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
class SomeEntity(db.Model, Mixin):
some_field = db.Column(db.String)
Are those implementations equivalent ? In which case should I use a Mixin instead of an abstract Model class ?
Related
In my project, based on Flask + SQLAlchemy I have more files for more models.
For example:
# file: models_users.py
user_roles_association = db.Table('user_roles_association',
Column('role_id',
db.Integer,
db.ForeignKey('roles.id')),
Column('user_id',
db.Integer,
db.ForeignKey('users.id'))
)
class User(UserMixin, Model):
__tablename__ = 'users'
id = Column(db.Integer, primary_key=True)
first_name = Column(db.String(40), nullable=True)
last_name = Column(db.String(40), nullable=True)
roles = db.relationship('Role',
secondary=user_roles_association,
backref=db.backref('users', lazy='dynamic'),
lazy='dynamic')
def __init__(self, first_name, last_name, **kwargs):
db.Model.__init__(self,
first_name=first_name,
last_name=last_name,
**kwargs)
def my_roles(user_id):
user = User.query.get(user_id)
return user.roles.order_by(Role.id).all()
Second file:
# file: models_cofig.py
class Role(UserMixin, Model):
__tablename__ = 'roles'
id = Column(db.Integer, primary_key=True)
name = Column(db.String(40), unique=True, nullable=False)
def __init__(self, name, **kwargs):
"""Create instance."""
db.Model.__init__(self,
name=name,
**kwargs)
def __repr__(self):
return str(self.name)
Okay, and question: What is a right way to make method, where I can use models from other model files?
Dirty way:
# file models_user.py
from apps.config import Role
class User(UserMixin, Model):
__tablename__ = 'users'
...
...
def my_roles(user_id):
user = User.query.get(user_id)
return user.roles.order_by(Role.id).all()
Note: Model Role is in second file.
And I need use right and clear way for custom methods across more models files.
Thanks for any answers.
I have several classes:
import uuid
from app import db, create_app
from sqlalchemy.sql import func
from sqlalchemy.dialects.postgresql import UUID, ARRAY, JSONB
class Ticket(db.Model):
__tablename__ = 'tickets'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
time = db.Column(db.DateTime, server_default=func.now(), index=True)
proposed_names = db.Column(ARRAY(db.String))
measurements = db.relationship('TempMeasurement', back_populates='ticket')
class BaseMeasurement(object):
id = db.Column(db.Integer, primary_key=True)
#declared_attr
def type_id(self):
return db.Column(db.Integer, db.ForeignKey('optical_data_types.id'))
#declared_attr
def type(self):
return db.relationship('OpticalDataType')
#declared_attr
def operator_id(self):
return db.Column(db.Integer, db.ForeignKey('operators.id'))
#declared_attr
def operator(self):
return db.relationship('Operator')
#declared_attr
def item_id(self):
return db.Column(db.String, db.ForeignKey('items.serial'))
#declared_attr
def item(self):
return db.relationship('Item')
time = db.Column(db.DateTime, index=True)
instrument = db.Column(db.String)
instrument_sn = db.Column(db.String)
data = db.Column(JSONB)
class TempMeasurement(db.Model, BaseMeasurement):
__tablename__ = 'ticket_data'
id = db.Column(db.Integer, primary_key=True)
ticket_id = db.Column(UUID(as_uuid=True), db.ForeignKey('tickets.id'), index=True)
ticket = db.relationship('Ticket', back_populates='measurements')
original_paths = db.Column(ARRAY(db.String))
What I want/expect is that I can create a Ticket with several child TempMeasurements and commit this to the database. Something like:
app = create_app()
with app.app_context():
ticket = Ticket()
ticket.measurements = [TempMeasurement(...)]
db.session.add(ticket) # <-- error on this line
db.session.commit()
However, I get an obscure error deep in SQLAlchemy:
AttributeError: 'str' object has no attribute '_sa_instance_state'
with a full trace here.
I thought that it might be because the UUID ticket_id column has as_uuid, so I made it simply UUID (implicitly a str), but this did not solve my issue.
The error is too deep in SQLAlchemy for me to understand -- can anyone help?
Below I have a Flask-SQLAlchemy model for the table User.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
def add_user(self, name):
self.name = name
Here add_user is a custom method. So if I call the add_user method it should add the name to the User table.
Likewise how do I write custom methods for CRUD operations in that model itself?
You'll probably want to use a classmethod to accomplish this.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
This way you can use User.create(name="kumaran") to create a new user that will be committed to the database.
Better yet, it is a great idea to create a mixin for this method and others like it so that the functionality can be easily reused in your other models:
class BaseMixin(object):
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
You can then reuse this functionality in your models by using multiple inheritance, like so:
class User(BaseMixin, db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
Not sure this is relevant to Flask-SQLAlchemy, but basic SQLAlchemy has examples of creating Mixin classes or augmenting the Base class.
https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html
e.g.
from sqlalchemy.ext.declarative import declared_attr
class MyMixin(object):
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
__mapper_args__= {'always_refresh': True}
id = Column(Integer, primary_key=True)
class MyModel(MyMixin, Base):
name = Column(String(1000))
I would accomplish what you're after like this:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
#classmethod
def add_user(cls, session, name):
user = User(name)
session.add(user)
return User
Then in whatever context you're using it in, create a session, call your method, and commit it.
from .user import User
session = Session()
# or if using Flask SQLAlchemy
# session = db.session
User.add_user(session, 'Foo')
session.commit()
From the sqlalchemy docs:
Keep the lifecycle of the session (and usually the transaction) separate and external.
In contrast to one of the other answers, which assumes you are using this model in a Flask app with FlaskSQLAlchemy's global db.session object, parametrizing the session object like this keeps your model code separate from your session management code. This allows it to be used flexibly in many different contexts.
In my Flask-SQLAlchemy App I want to add a few fields (created(by|on), changed(by|on)) to every Model/Table
my code right now
from .. import db
class Brand(db.Model):
__tablename__ = 'md_brands'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False)
def __repr__(self):
return u'<Brand {}>'.format(self.name)
I am not sure if it's better to use Mixins or somehow extend the base db.Model (or if even there is a better way to do this).
What (and why) is the best way to add such fields (created(by|on), changed(by|on)) to all my models?
Using __abstract__.
How do I declare a base model class in Flask-SQLAlchemy?
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
class Base(db.Model):
__abstract__ = True
created_on = db.Column(db.DateTime, default=db.func.now())
updated_on = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now())
class User(Base):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String(255), unique = True)
Both are pretty much the same. Here is a Mixin that I use
class ModelMixin(object):
def __repr__(self):
return unicode(self.__dict__)
#property
def public_view(self):
"""return dict without private fields like password"""
return model_to_dict(self, self.__class__)
and then
class User(db.Model, ModelMixin):
""" attributes with _ are not exposed with public_view """
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
I am trying to create a relationship between the two tables.
When instantiating a model I get the following error:
from core.models import Activation
a = Activation()
ArgumentError: Class object expected, got 'Table(u'activations', MetaData(bind=Engine(postgresql+psycopg2://localhost:5432/mydb)), Column('id', Integer(), table=<activations>, primary_key=True, nullable=False), Column('valid_until', DateTime(), table=<activations>), Column('code', Unicode(length=30), table=<activations>, nullable=False), Column('created_by', Unicode(length=16), table=<activations>, nullable=False), schema=None)'.
core/models.py
class ActivationMixin(Base):
#declared_attr
def code(self):
return Column(Unicode(30), nullable=False, unique=True)
#declared_attr
def valid_until(self):
return Column(DateTime, nullable=True)
#declared_attr
def created_by(self):
return Column(Unicode(16), nullable=False)
#classmethod
def get_by_code(cls, request, code):
session = get_session(request)
return session.query(cls).filter(cls.code == code).first()
def __init__(self, created_by='web', valid_until=None):
"""Create a new activation. *valid_until* is a datetime.
It defaults to 3 days from current day.
"""
self.code = generate_random_string(24)
self.created_by = created_by
if valid_until:
self.valid_until = valid_until
else:
self.valid_until = datetime.utcnow() + timedelta(days=3)
class Activation(ActivationMixin):
pass
user/models.py
class User(Base):
email = Column(Unicode(256), nullable=False, unique=True)
status = Column(Boolean, default=False)
salt = Column(Unicode(32), nullable=False)
_password = Column('password', Unicode(256), nullable=False)
logins = Column(Integer, default=0)
last_login = Column(
TIMESTAMP(timezone=False),
default=func.now(),
server_default=func.now(),
nullable=False
)
account_type = Column(AccountType.db_type())
#declared_attr
def activation_id(self):
return Column(
Integer,
ForeignKey('%s.id' % (Activation.__tablename__))
)
#property
def is_activated(self):
if self.activation_id is None:
return True
return False
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
The error is occures with the following declaration:
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
There seems to be some code that you haven't posted since running just this code would give you other errors as well (e.g., related to the __tablename__ attributes not being properly set). However, the actual error you are getting is probably related to this code...
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
According to the documentation for relationship function, the first argument should be...
a mapped class, or actual Mapper instance, representing the target of
the relationship.
However, you are using the __tablename__ attribute, which should be just by the name of the table (a string).
So, try changing this to...
#declared_attr
def activation(self):
return relationship(
Activation,
backref=self.__tablename__
)