SQLAlchemy Polymorphic Relationship with Concrete Inheritance - python

I am using the concrete table inheritance with SQLAlchemy. In declartive style model class, I have configured it successfully.
My code just like:
class Entry(AbstractConcreteBase, db.Model):
"""Base Class of Entry."""
id = db.Column(db.Integer, primary_key=True, nullable=False)
created = db.Column(db.DateTime, nullable=False)
post_id = declared_attr(lambda c: db.Column(db.ForeignKey("post.id")))
post = declared_attr(lambda c: db.relationship("Post", lazy="joined"))
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
#declared_attr
def __mapper_args__(cls):
# configurate subclasses about concrete table inheritance
return {'polymorphic_identity': cls.__name__,
'concrete': True} if cls.__name__ != "Entry" else {}
class TextEntry(Entry):
"""Text and Article Entry."""
text = db.deferred(db.Column(db.Text, nullable=False))
class PhotoEntry(Entry):
"""Photo Entry."""
path = db.deferred(db.Column(db.String(256), nullable=False))
It works fine while testing it in the shell:
>>> from models.entry import Entry
>>>
>>> Entry.query.all()
[<PhotoEntry 'Title' created by tonyseek>,
<PhotoEntry 'TITLE 2' created by tonyseek>,
<PhotoEntry 'Title 3' created by tonyseek>,
<PhotoEntry 'Title 4' created by tonyseek>,
<TextEntry 'Title' created by tonyseek>]
Then I fall into trouble while setting the relationship in other models. Each entry has a foreign key post_id to join Post model, but I could not define the back reference in Post. That can't work:
class Post(db.Model):
"""An Post."""
id = db.Column(db.Integer, primary_key=True, nullable=False)
description = db.Column(db.Unicode(140), nullable=False)
entries = db.relationship(Entry, lazy="dynamic")
It raised a Exception and said:
InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Original exception was: Class 'models.entry.Entry' is not mapped.
Obvious the Entry is a abstract class, which couldn't be mapped to a real exist table. The document in official website has a example but its base class is not abstract. Now how should I do to set the polymorphic relationship with a abstract model?

I have found the reason of the problem and its solution.
According to the document of sqlalchemy offical website, the abstract class could be a mapped class, because the polymorphic_union function could create a virtual table.
I am using the declartive style model, not build mapper by hand, so the virtual table pjoin should not be created by hand. The base class AbstractConcreteBase has a method __delcare_last__ would create the pjoin with polymorphic_union function, but it would be called while the event after_configured triggering.
The relationship with Entry in Post would be created after the Post class be generated, in this time the event after_configured have not been triggered, so __delcare_last__ function have not created the virtual table pjoin and mapped it into Entry. So the exception "Class 'models.entry.Entry' is not mapped." will be raised.
Now, I refactor the Post model, let it create the relationship with Entry in __delcare_last__ function, then it will be success because of the triggered event and the mapped Entry.
My new implemented class like this:
class Post(db.Model):
"""An Post."""
id = db.Column(db.Integer, primary_key=True, nullable=False)
description = db.Column(db.Unicode(140), nullable=False)
#classmethod
def __declare_last__(cls):
cls.entries = db.relationship(Entry, viewonly=True)
def attach_entries(self, entries):
"""Attach Entries To This Post.
Example:
>>> post = Post("An Interesting News", "Wow !!!")
>>> text_entry = TextEntry(*t_args)
>>> photo_entry = PhotoEntry(*p_args)
>>> post.attach_entries([text_entry, photo_entry])
>>> len(post.entries)
2
>>> db.session.commit()
>>>
"""
for entry in entries:
self.entries.append(entry)
entry.post = self
db.session.add(entry)

Related

How to create a one to many relationship based on one to one (three tables)?

I'm learning backend development on python, using flask and SQLachemy. I have understood the way to do one-to-one, one-to-many and many-to-many relationships. And now I'm trying to do a more tricky relationship.
There are 3 models class, with these relationships :
The class A and B have one-to-one relationship
The class B and C have many(B)-to-one(C) relationship
The class A and C have many(C)-to-one(A) relationship
And now I want to create a relationship between C and A passing though B relationship (abstract problem formulation, continue the reading for the concrete formulation)
The documentation speaks about join, it might be a way to do it, but I'm not able to understand the example
https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#composite-secondary-joins
from backend import db # db = SQLAlchemy(app)
class User(db.Model): # class A
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
# ...
albums = db.relationship('Album', backref='auteur')
# pictures taken as a photograph (pictures_posted may exist, but
# that's still the same difficulty than with photographer, "circular"
# relationships
pictures_taken = db.relationship('Picture', backref='photographe')
class Picture(db.Model): # class B
__tablename__ = 'Picture'
id = db.Column(db.Integer, primary_key=True)
# ...
album_id = db.Column(db.Integer, db.ForeignKey('Album.id'))
photographe_id = db.Column(db.Integer, db.ForeignKey('User.id'))
class Album(db.Model): # class C
__tablename__ = 'Album'
id = db.Column(db.Integer, primary_key=True)
# ...
pictures = db.relationship('Picture', backref='album')
auteur_id = db.Column(db.Integer, db.ForeignKey('User.id'))
To be less abstract, I want to access (all) photographes who are represented in an album, directly from albums instances. As i can access to auteur and pictures.
PS : Should I use polymorphism to handle the problem (sub classes of User : Lambda ; Photographe ; Author) ?
If you want reverse access (from albums to author's all images):
album = Album.query.filter_by(id=111).first()
then:
images = album.auteur.images
Actually the most convenient answer is to use a property inside Album's class.
class Album(db.Model):
# ...
#property
def photographes(self):
photographes = set()
for img in self.pictures:
if img.photographe not in photographes:
photographes.add(img.photographe)
return photographes
I have shared whole my backend on git, it could be useful !

In SQLAlchemy can `backref` param inside a `relationship` be assigned a variable?

I'm using joind table inheritance here.
class BaseEntity(Base):
some_col = Column(String)
base_relationship = relationship("some_relationship", backref="depends_on_who_inherits_me")
class SubEntity(BaseEntity):
some_unique_col = Column(String)
Because the specific backref name will only become known in the run-time(in this case, it should be SubEntity, but it should be able to be inheritable by unlimited subclasses), I need depends_on_who_inherits_me part a variable, or more specifically, the inheriting sub-class's name, instead of a string. So each sub-class will have a relationship referring to a third-party class, while having it referring back to that particular sub-class by its appropriate name.
However, because this is outside of any method, I can't use self to flexibly refer to the instances.
How to implement this idea? Thanks.
One way you might achieve this is with a Mixin that uses declared_attr.cascading.
Here's the mixin class:
class Mixin:
#declared_attr.cascading
def related_entity(cls):
if has_inherited_table(cls):
return relationship(
'RelatedEntity',
backref=cls.__name__.lower(),
uselist=False
)
The cascading flag on declared_attr will make sqlalchemy attempt to render the 'mixed in' attribute on every class in the hierarchy. Or as the docs put it:
This is a special-use modifier which indicates that a column or
MapperProperty-based declared attribute should be configured
distinctly per mapped subclass, within a mapped-inheritance scenario.
The has_inherited_table() function, allows us to determine within the mixin if we are dealing with the BaseEntity or a subclass, so that we only add the relationships on to the subclasses.
The mixin is then inherited into the BaseEntity model:
class BaseEntity(Base, Mixin):
id = sa.Column(sa.Integer, primary_key=True)
related_id = sa.Column(
sa.Integer, sa.ForeignKey('relatedentity.id'))
discriminator = sa.Column(sa.String)
#declared_attr
def __mapper_args__(cls):
if has_inherited_table(cls):
args = {'polymorphic_identity': cls.__name__.lower()}
else:
args = {'polymorphic_on': cls.discriminator}
return args
As you mentioned in your question that you are using joined table inheritance, I've defined the __mapper_args__ on BaseEntity using a #declared_attr method so that the polymorphic_identity also can be automatically generated from the class name for the subclasses.
So with this configuration, every subclass of BaseEntity will apply a relationship attribute on RelatedEntity named after the subclass. Here's the full working example:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import (declarative_base, declared_attr,
has_inherited_table)
from sqlalchemy.orm import relationship, sessionmaker
class BaseClass:
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
Base = declarative_base(cls=BaseClass)
engine = sa.create_engine('sqlite://', echo=False)
Session = sessionmaker(bind=engine)
class Mixin:
#declared_attr.cascading
def related_entity(cls):
if has_inherited_table(cls):
return relationship(
'RelatedEntity',
backref=cls.__name__.lower(),
uselist=False
)
class BaseEntity(Base, Mixin):
id = sa.Column(sa.Integer, primary_key=True)
related_id = sa.Column(
sa.Integer, sa.ForeignKey('relatedentity.id'))
discriminator = sa.Column(sa.String)
#declared_attr
def __mapper_args__(cls):
if has_inherited_table(cls):
args = {'polymorphic_identity': cls.__name__.lower()}
else:
args = {'polymorphic_on': cls.discriminator}
return args
class RelatedEntity(Base):
""" Class that is related to all `BaseEntity` subclasses"""
id = sa.Column(sa.Integer, primary_key=True)
class SubEntity(BaseEntity):
""" Will generate `RelatedEntity.subentity`"""
id = sa.Column(sa.Integer, sa.ForeignKey('baseentity.id'),
primary_key=True)
class OtherEntity(BaseEntity):
""" Will generate `RelatedEntity.otherentity`"""
id = sa.Column(sa.Integer, sa.ForeignKey('baseentity.id'),
primary_key=True)
if __name__ == '__main__':
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
s = Session()
rel_inst = RelatedEntity()
s.add(rel_inst)
rel_inst.subentity.append(SubEntity())
rel_inst.otherentity.append(OtherEntity())
s.commit()
print(rel_inst.subentity, rel_inst.otherentity)
# [<__main__.SubEntity object at 0x0000023487D42C18>] [<__main__.OtherEntity object at 0x0000023487D60278>]
The reason we can't define the related_entity() declared_attr method in BaseModel is because SQLAlchemy will not honor the cascade, and there will be no relationships generated (becuase the if has_inherited_table(cls): block prevents BaseModel from generating one). From the docs:
The flag only applies to the use of declared_attr on declarative mixin
classes and __abstract__ classes; it currently has no effect when used
on a mapped class directly.

Get inheriting class name in base class' class method

I have Models in my Flask app. I create tables from those models, and insert data into those tables. However, when I insert into those tables I want to log what I am inserting and which table I am inserting into. I have a "create" class method in my BaseModel which all other Models inherit from. I want to log the class name of the inherited Model in this "create" method. When I do so using
cls.__class__.__name__
it gives me the value "DefaultMeta". I want it to give me the Model name, for example, User.
Here is my create method in my BaseModel:
class BaseModel(db.Model):
__abstract__ = True
__table_args__ = {"extend_existing": True}
id = db.Column(
db.Integer,
primary_key=True
)
create_date = db.Column(
db.DateTime,
default=datetime.utcnow()
)
update_date = db.Column(
db.DateTime,
default=datetime.utcnow(),
onupdate=db.func.now()
)
#classmethod
def create(cls, **kwargs):
current_app.logger.info('Creating {} with args: {}'.format(cls.__class__.__name__, kwargs))
obj = cls(**kwargs)
db.session.add(obj)
db.session.commit()
return obj
How can I achieve this? Is it possible?
cls.__class__.__name__ is the class's class's name. You want the class's name. That's cls.__name__.

SQLAlchemy - accessing columns of child model through parent model (one-to-one relation)

I have two models and one-to-one relationship between them. I would like to access columns of ItemData through Item (e.g. item = Item(); item.value). I tried to overwrote __getattr__, but this method is intensively used by SQLAlachemy base model. Any help appreciated.
class Item(Model):
__tablename__ = "item_data"
id = Column(Integer, primary_key=True)
data = relationship("ItemData", back_populates="_item", uselist=False,
foreign_keys="ItemData._item_id")
class ItemData(Model):
__tablename__ = "items_data"
id = Column(Integer, primary_key=True)
_item_id = Column(Integer, ForeignKey("items.id"))
_item = relationship("Item", back_populates="data",
foreign_keys=[_item_id])
value = Column(Integer)
I solved the problem by writing class decorator which should be apply to Item.
from sqlalchemy.ext.hybrid import hybrid_property
def direct_data_getter(cls):
'''Provide direct access to attributes of data.'''
fields = set(cls.data_cls.__mapper__.columns.keys()) - set(dir(cls))
for field in fields:
method = hybrid_property(
lambda self, field=field: getattr(self.data, field)
)
setattr(cls, field, method)
return cls

dynamic __tablename__ in flask-sqlalchemy models

Is there a possibility to make the __tablename__ in flask-sqlalchemy models dynamic with the declarative base approach?
Usually you set it as this one:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
email = Column(String(120), unique=True)
def __init__(self, name=None, email=None):
self.name = name
self.email = email
def __repr__(self):
return '<User %r>' % (self.name)
I would like to change it through a parameter (maybe in the constructor?), so that I can have a table per user.
I found some other approaches in this guide here
Approaches
but I would like to use the session for that as I am already using it for the other models.
You can utilize python's type() function to dynamically build SQLAlchemy models.
Here's a example:
# define columns in an abstract model class
class Log(Base):
__abstract__ = True # this line is necessary
# the columns id, content and user_id are just examples, just ignore it.
id = Column(BIGINT(64), primary_key=True)
content = Column(VARCHAR(200), nullable=False)
user_id = Column(INTEGER(unsigned=True))
# build a model class with a specific table name
def get_log_model(year):
tablename = 'logs_%s' % year # dynamic table name
Model = type('Model', (Log,), {
'__tablename__': tablename
})
return Model
# Log2022 correspond to table "logs_2022"
Log2022 = get_step_model(2022)
# use the dynamically built model in the same way as regular models
print(session.query(Log2022).count()) # row count of table "logs_2022"
I also wrote an article about it on my website, it may help you too: https://easydevguide.com/posts/dynamic_table

Categories