Mapping multi-level class inheritance hierarchies in SqlAlchemy - python

Background
I have an existing database with 3 levels of dependent tables. Here I give an example of a 3-level, joined table, inheritance mapping problem I cannot solve. Unfortunately I cannot change the database design.
Problem
If I execute:
entities = session.query(Entity)
for ent in entities:
print ent.isin
I get the error:
AttributeError: 'Asset' object has no attribute 'isin'
However the following code executes fine:
entities = session.query(Entity)
for ent in entities:
print ent.composition
Question
So the attribute composition is accessible but not isin !?!?
It seems obvious that the classes Entity and Asset are being mapped, but not Listed. Why would this be so?
I would have believed that sqlalchemy would be able to map to an arbitrary depth of inheritance.
I have read the docs, tried everything and I am stumped. Please help.
Inheritance mapping
class Entity(Base):
__tablename__ = 'entity'
id = Column(Integer, primary_key=True)
name = Column(String)
entity_type = Column('type',String)
source_table_id = Column(Integer)
__mapper_args__ = {
'polymorphic_identity':'entity',
'polymorphic_on':entity_type,
}
class Asset(Entity):
__tablename__ = 'asset'
id = Column('entity_id',Integer, ForeignKey('entity.id'), primary_key=True)
asset_type = Column('type',String)
asset_class = Column('class',String)
composition = Column(String)
__mapper_args__ = {
'polymorphic_identity':'asset',
'polymorphic_on':asset_type,
}
class Listed(Asset):
__tablename__ = 'listed'
id = Column('asset_entity_id',Integer, ForeignKey('asset.entity_id'), primary_key=True)
ticker = Column(String)
isin = Column(String)
__mapper_args__ = {
'polymorphic_identity':'listed',
}

I think maybe this is just a coincidence with your data.
You should be either iterating over the particular type of object you want to look at e.g.
entities = session.query(Asset)
for ent in entities:
print ent.composition
and
entities = session.query(Listed)
for ent in entities:
print ent.isin
or checking the type of object before you access an attribute on that type of object
entities = session.query(Entity)
for ent in entities:
if isinstance(ent, Asset):
print ent.composition
else isinstance(ent, Listing):
print ent.isin
Otherwise what's going to happen is you're going to get an Entity object which will be either a Listing or a Asset object. If you hardcode the attribute isin then it's going to break when dealing with an Asset object because the Asset object does not have an isin property.

Related

Create non strait forward SqlAlchemi relationship

I have 2 SqlAlchemy entities: study and series with 1 to many relation (study has many series). I crearted a relationship that way:
class SeriesDBModel(Base):
__tablename__ = "series"
series_uid = Column(String, primary_key=True)
study_uid = Column(String, ForeignKey(StudyDBModel.study_uid))
study = relationship("StudyDBModel", lazy="subquery", cascade="all")
class StudyDBModel(Base):
__tablename__ = "study"
study_uid = Column(String, primary_key=True)
My question is:
Do I have to create the entities like this:
series = SeriesDBModel()
study = StudyDBModel()
series.study = study
Or
Can I just create both entities independently but with the same study_uid?
I'm asking because the second way i how I save my entities but using series.study returns None.
BTW, it's not a "sessio.commit()" issue. I'm verifing both entities exist in the DB before trying series.study..
I tried to fetch series.study as above.

AttributeError: 'Query' object has no attribute 'is_clause_element' when joining table with query

AttributeError: 'Query' object has no attribute 'is_clause_element' when joining table with query
I have a query that counts the amount of keywords a company has and then sorts them by the amount of keywords they have.
query_company_ids = Session.query(enjordplatformCompanyToKeywords.company_id.label("company_id"),func.count(enjordplatformCompanyToKeywords.keyword_id)).group_by(enjordplatformCompanyToKeywords.company_id).order_by(desc(func.count(enjordplatformCompanyToKeywords.keyword_id))).limit(20)
I then want to get information about these companies like image, title, info etc and send it to the frontend (this is done later by looping through companies_query).
Though I have trouble in building the connection between the query_company_ids query and enjordplatformCompanies table.
I have tried two ways of doing this:
companies_query = Session.query(enjordplatformCompanies, query_company_ids).filter(enjordplatformCompanies.id == query_company_ids.company_id).all()
companies_query = Session.query(enjordplatformCompanies, query_company_ids).join( query_company_ids, query_company_ids.c.company_id == enjordplatformCompanies.id).all()
But both of them result in the error: AttributeError: 'Query' object has no attribute 'is_clause_element'
Question
How can I join the query_company_ids query and enjordplatformCompanies table?
Thanks
Here are the table definitions
class enjordplatformCompanies(Base):
__tablename__ = "enjordplatform_companies"
id = Column(Integer, primary_key=True, unique=True)
name = Column(String)
about = Column(String)
image = Column(String)
website = Column(String)
week_added = Column(Integer)
year_added = Column(Integer)
datetime_added = Column(DateTime)
created_by_userid = Column(Integer)
company_type = Column(String)
contact_email=Column(String)
adress=Column(String)
city_code=Column(String)
city=Column(String)
class enjordplatformCompanyToKeywords(Base):
__tablename__ = "enjordplatform_company_to_keywords"
id = Column(Integer, primary_key=True, unique=True)
company_id = Column(Integer,ForeignKey("enjordplatform_companies.id"))
keyword_id = Column(Integer,ForeignKey("enjordplatform_keywords.id"))
I copied your example query above and was getting a lot of weird errors until I realized you use Session instead of session. I guess make sure you are using an instance instead of the class or sessionmaker.
Below I create an explicit subquery() to get the company id paired with its keyword count and then I join the companies class against that, applying the order and limit to the final query.
with Session(engine) as session, session.begin():
subq = session.query(
enjordplatformCompanyToKeywords.company_id,
func.count(enjordplatformCompanyToKeywords.keyword_id).label('keyword_count')
).group_by(
enjordplatformCompanyToKeywords.company_id
).subquery()
q = session.query(
enjordplatformCompanies,
subq.c.keyword_count
).join(
subq,
enjordplatformCompanies.id == subq.c.company_id
).order_by(
desc(subq.c.keyword_count)
)
for company, keyword_count in q.limit(20).all():
print (company.name, keyword_count)
This isn't the exact method but explains the intention of calling .subquery() above:
subquery

Filter rows in child table using parent table having one to many relationship sqlalchemy

I have two tables having one to many relationship.
I want to find all rows of child where type is "abc"
class Parent(base):
__tablename__ = "parent"
id = Column("id", String, primary_key=True)
parent = relationship("Child", back_populates="child")
class Child(base):
__tablename__ = "child"
id = Column("id", Integer, primary_key=True)
name = Column(String)
tid = Column(String, ForeignKey("parent.id"))
child = relationship(Tenant, back_populates="parent")
type = Column(String)
return self.session.query(Parent.parent).filter_by(Parent.parent.type == "abc").all()
It gives me error as InstrumentedAttribute' object nor 'Comparator' object associated with Parent.parent has an attribute 'type'
And if i do
return self.session.query(Parent).filter(Parent.parent.any(Child.type == type)).all()
It gives me all rows with other types as well
This would do what you want:
from sqlalchemy.orm import contains_eager
q = (session.query(Parent)
.join(Child)
.options(contains_eager(Parent.parent))
.filter(Child.type == 'abc'))
for p in q:
print(p.id, [c.name for c in p.parent])
The contains_eager function tells sqlalchemy that only the rows of the referenced collection specified in the query should be eagerly loaded: in the above example, only Child instances with a type of 'abc'.
The linked docs warn that by returning only a selection of a related collection can lead to problems: it's worth reading the warning and bearing it in mind when designing your application.

Update Order of Existing Object Using SQLAlchemy OrderingList

I am trying to update the order of an existing object when using the SQLAlchemy Ordering List. Here's the example model used in the SQLAlchemy docs:
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
class Bullet(Base):
__tablename__ = 'bullet'
id = Column(Integer, primary_key=True)
slide_id = Column(Integer, ForeignKey('slide.id'))
position = Column(Integer)
text = Column(String)
I understand how to call append() with new objects and that works fine.
But how do I update the order of an existing object?
I have tried this and get some hard to understand results.
existing_item = session.query(Bullet).filter(Bullet.id == bullet_id).one()
slide.bullets.insert(new_order, existing_item)
slide.bullets.reorder()
session.commit()
There doesn't seem to be any documentation that explains how to update an existing object. Am I missing something?
I think I figured this out. I needed to call remove() on the object in question before re-inserting it.
Not sure if there's a better way, but this works.
existing_item = session.query(Bullet).filter(Bullet.id == bullet_id).one()
slide.bullets.remove(existing_item)
slide.bullets.insert(new_order, existing_item)
slide.bullets.reorder()
session.commit()
Also worth noting: I am using count_from=1 so that requires that I subtract 1 from new_order so that the end-user isn't confused by an order that starts at 0.

Cannot access column in PostgreSQL table from Python model

I have defined a python class "Students", like this:
class Students(DeclarativeBase):
__tablename__ = 'students'
id_ = Column('id', Integer, primary_key=True)
name = Column('nombre', Unicode(50))
date_of_birth = Column(Date)
If I do select * from students, I can see all of these columns plus a few more, namely: _created and _updated.
I need to use the values stored in the columns _created and _updated. So I try to access them like this:
#get student with id = 1
>>> s = dbs.query(Students).get(1)
# print its name
>>> print(s.name)
Richard
# try to print when it was created
>>> print (s._created)
AttributeError: 'Students' object has no attribute '_created'
Of course I get that message because the attribute _created is not defined in the model.
How can I access the value stored in the table Students even though it is not an attribute of the class Student?
SQLAlchemy needs the definition of each column it will access. (There are ways to auto-discover by reflecting the database, but explicit is better than implicit.) Add the column definitions to the model. I'm assuming they're DateTimes. You can use default= and onupdate= to provide new values when a row is inserted or updated.
class Student(Base):
__tablename__ = 'student'
id = Column('id_', Integer, primary_key=True)
# other columns...
created = Column('_created', DateTime, nullable=False, default=datetime.utcnow)
updated = Column('_updated', DateTime, onupdate=datetime.utcnow)

Categories

Resources