So I want to execute a filter on all Columns of my Database Model which uses table inheritance. I am by no means sure if this is actually do-able or not.
To get started let's use the same inheritance example from the SQLAlchemy Doc just slightly modified. I've omitted the imports here.
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
name = Column(String(50))
type = Column(String(50))
__mapper_args__ = {
'polymorphic_identity':'employee',
'polymorphic_on':type
}
#classmethod
def get_all(cls, session, query):
_filters = []
for prop in class_mapper(cls).iterate_properties:
if isinstance(prop, ColumnProperty):
_col = prop.columns[0]
_attr = getattr(cls, _cls.name)
_filters.append(cast(_attr, String).match(query))
result = session.query(cls)
result = result.filter(or_(*_filters))
return result.all()
class Engineer(Employee):
__tablename__ = 'engineer'
id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
engineer_name = Column(String(30))
foo = Column(String(10))
__mapper_args__ = {
'polymorphic_identity':'engineer',
}
class Manager(Employee):
__tablename__ = 'manager'
id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
manager_name = Column(String(30))
bar = Column(String(20))
__mapper_args__ = {
'polymorphic_identity':'manager',
}
Now let's say I would like to query all Employee where some of the fields matches a query. The method get_all shown above will only query in Columns known to the class Employee.
Is there some way to query in all columns of the entire inheritance chain?
It's pretty ugly, but one way would be to find all the subclasses that inherit from Employee, then left join those tables and add their columns to the query.
How to get subclasses:
https://stackoverflow.com/a/5883218/443900
Have not tested this, but something like this should work.
#classmethod
def get_all(cls, session, query):
_filters = []
for prop in class_mapper(cls).iterate_properties:
if isinstance(prop, ColumnProperty):
_col = prop.columns[0]
_attr = getattr(cls, _cls.name)
_filters.append(cast(_attr, String).match(query))
result = session.query(cls)
result = result.filter(or_(*_filters))
# get the subclasses
subclasses = set()
for child in cls.__subclasses__():
if child not in subclasses:
subclasses.add(child)
# join the subclass
result = result.outerjoin(child)
# recurse to get the columns from the subclass
result = subclass.get_all(session, result)
# return a query, not a result to allow for the recursion.
# you might need to tweak this.
return result
Related
class MyClass(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
type_id = Column(String(50))
# map value
type_name = {
"type_id_1":"name_1",
"type_id_2":"name_2"
}
Is there a way to return "type name" when doing query type_id from table Myclass ?
by using #hybrid_property, I got really close to the goal
class Myclass(Base):
...
#hybrid_property
def type_name(self):
# The goal is get type_name, but it's not working since self.type_id is not a String object
name = type_name[self.type_id]
return cast(name, String)
reading your question I interpreted it this way. Do you want this?
class MyClass(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
type_id = Column(String(50))
def __repr__(self):
return "<my_table(id='%s', type_id='%s')>" % (self.id, self.type_id)
I have 3 classes;
'Company' top class its subclass 'Department' its subclass 'DepartmentalUnit'
I can access the values of all classes from the 'DepartmentalUnit' class to the top class 'Company'
What I could not do and understand despite reading the document is that;;
I cannot access other subclasses from the 'company' class
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
departments = relationship('Department',backref='company')
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
department_name = Column(String)
company_id = Column(Integer, ForeignKey('company.id'))
departmentalunits = relationship('DepartmentalUnit', backref='department')
class DepartmentalUnit(Base):
__tablename__ = 'departmentalunit'
id = Column(Integer, primary_key=True,nullable=False)
departmental_unit_name = Column(String)
departments_id = Column(Integer, ForeignKey('department.id'))
The code from which I access the upper classes from the subclasses:
query = session.query(DepartmentalUnit)
instance = query.all()
for i in instance:
print(i.department.company.name)
print(i.department.department_name)
print(i.departmental_unit_name)
The code I can't access other subclasses from the company class:
query = session.query(Company)
instance = query.all()
for i in instance:
print(i.department.department_name)
Your last query should be used differently:
there is a typo in the name of the relationship: should be departments instead of department
given that the relationship is 1-N, the result is a list, so you should iterate over children.
This should work:
query = session.query(Company)
for company in query.all():
print(company.name)
for dep in company.departments:
print(" ", dep.department_name)
for dep_unit in dep.departmentalunits:
print(" ", dep_unit.departmental_unit_name)
I solved the problem. I added a backref to relationships and now I can access all of them from the company. Not sure if it's a correct method? However, I am currently getting the return I want. I have no unanswered request yet.
Example solved:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
departments = relationship('Department',backref='company',uselist=False)
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
department_name = Column(String)
company_id = Column(Integer, ForeignKey('company.id'))
departmentalunits = relationship('DepartmentalUnit', backref='department',uselist=False)
class DepartmentalUnit(Base):
__tablename__ = 'departmentalunit'
id = Column(Integer, primary_key=True,nullable=False)
departmental_unit_name = Column(String)
departments_id = Column(Integer, ForeignKey('department.id'))
query = session.query(Company)
instance = query.all()
for i in instance:
print(f"Company: {i.name}")
print(f"Department: {i.departments.department_name}")
print(f"Department Unit: {i.departments.departmentalunits.departmental_unit_name}")
print( f"Report Category : {i.departments.departmentalunits.reportcategoryoftheunit.report_category_name}")
I have three classes in my model, which one class inherited by the other two:
class Item(Base):
__tablename__ = 'item'
id = Column(Integer, primary_key=True)
title = Column(Unicode(300))
type = Column(Unicode(50))
__mapper_args__ = {
'polymorphic_on': type
}
class Note(Item):
__tablename__ = 'note'
id = Column(Integer, ForeignKey('item.id'), primary_key=True)
extra = Column(Text)
__mapper_args__ = {
'polymorphic_identity': 'note'
}
class Task(Item):
__tablename__ = 'task'
id = Column(Integer, ForeignKey('item.id'), primary_key=True)
another_extra = Column(Text)
__mapper_args__ = {
'polymorphic_identity': 'task'
}
So, when I execute session.query(Item).all() I get a list that includes both Note and Task objects, but I don't want that, I want my objects to be the instance of Item class and just have id, title, type, not those extra fields. how should I write the query?
to clarify more, currently, I get:
[
<models.Note object at 0x7f25ac3ffe80>,
<models.Task object at 0x7f25ac3ffe80>,
<models.Task object at 0x7f25ac3ffe80>,
...
]
But I want to get:
[
<models.Item object at 0x000000000000>,
<models.Item object at 0x000000000000>,
<models.Item object at 0x000000000000>,
...
]
NOTE: This may be problematic in a multi-threaded application.
You could use a context manager to temporarily block the polymorphism:
from contextlib import contextmanager
from sqlalchemy import inspect
#contextmanager
def no_poly(class_):
mapper = inspect(class_).mapper
polycol = mapper.polymorphic_on
mapper.polymorphic_on = None
yield class_
mapper.polymorphic_on = polycol
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
task = Task(title='Task Title', another_extra='something')
s = Session()
s.add(task)
s.commit()
# opening a new session as if the pk already exists in the
# identity map it will return whatever type that pk is
# pointing at.
s = Session()
with no_poly(Item) as class_:
inst = s.query(class_).all()
print(inst) # [<__main__.Item object at 0x000001443685DDD8>]
s = Session() # new session again.
inst = s.query(Item).all()
print(inst) # [<__main__.Task object at 0x00000144368770B8>]
Something to be mindful of and as noted in the comments in my example, if the identity of the object is already referenced in the Identity Map, then you will get back whatever type is held in there, regardless of the class that you query on.
In SQLAlchemy, I'd like to have a class that automatically creates hybrid properties to expose attributes from a specific child table. Consider this structure:
class Address(Model):
id = Column(Integer, primary_key=True)
street = Column(String)
number = Column(Integer)
valid_from = Column(DateTime)
valid_to = Column(DateTime)
person_id = Column(Integer, ForeignKey('person.id'))
person = relationship('Person', backref=backref('addresses', lazy='dynamic')
class Person(db.Model, HybridPropertyGeneratorMixin):
data_class = Address
id = Column(Integer, primary_key=True)
#property
def current_address(self):
return self.addresses.order_by(desc(Address.valid_from))[0]
#hybrid_property
def city(cls):
return self.current_address.city
#city.expression
def city(cls):
return select([Address.name]). \
where(cls.id==Address.person_id). \
where(Address.valid_to == None).as_scalar()
What I'm trying to do is define a mixin that would automatically look at the attributes of data_class and generate hybrid attributes and expressions from the data_class's attributes. For example, I want to automatically define the hybrid property and expression for city, state, street, etc.
UPDATE
Wasn't clear enough in what I originally wanted to do. See above for an update as to why I want to automatically generate hybrid properties and expressions.
You can override the special method __getattr__ to get the attribute from current_address if it's not an attribute of person.
class Person(db.Model):
# ...
def __getattr__(self, item):
return getattr(self.current_address, item)
I want to make a property for model which contains count.
Since I always need the property, I want to make query with JOIN like sqlalchemy.orm.relationship with lazy='joined'
For example, I defined models like following
import sqlalchemy as s, func
from sqlalchemy.orm import relatioship
# ...
class Foo(Base):
__tablename__ = 'foo'
id = s.Column(s.Integer, primary_key=True)
bar_id = s.Column(s.Integer, s.ForeignKey('bar.id'))
bar = relationship('Bar')
class Bar(Base):
__tablename__ = 'bar'
id = s.Column(s.Integer, primary_key=True)
#property
def foo_count(self):
return Foo.query.filter_by(bar=self).count()
When I access to property foo_count, then it will send query to DBMS.
Since I always access to this property, I want to load it eagerly the counting property like this
# Not session.query(Bar, func.count(Foo.id)).join(Foo) ...
bar = Bar.query.first()
SQL will be like this
SELECT id, COUNT(Foo.id)
FROM bar
INNER JOIN foo
ON bar.id = foo.id
Then bar.foo_count will not occur SQL query.
How can I make a property like foo_count?
I solved it by using sqlalchemy.orm.column_property
I replaced the foo_count by following
import sqlalchemy as s, func, select
from sqlalchemy.orm import relationship, column_property
# ...
class Foo(Base):
__tablename__ = 'foo'
id = s.Column(s.Integer, primary_key=True)
bar_id = s.Column(s.Integer, s.ForeignKey('bar.id'))
bar = relationship('Bar')
class Bar(Base):
__tablename__ = 'bar'
id = s.Column(s.Integer, primary_key=True)
foo_count = column_property(
select([func.count(Foo.id)])
.where(Foo.bar_id == id)
)
Please take a look at the Hybrid Attribute extension.
Your object model will look similar to the below:
class Foo(Base):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
bar_id = Column(Integer, ForeignKey('bar.id'))
bar = relationship('Bar')
class Bar(Base):
__tablename__ = 'bar'
id = Column(Integer, primary_key=True)
#hybrid_property
def foo_count(self):
return object_session(self).query(Foo).filter(Foo.bar==self).count()
#foo_count.expression
def foo_count(cls):
return select([func.count(Foo.id)]).where(Foo.bar_id == cls.id).label('foo_count')
foo_count will not be eagerly loaded, but you can use it in queries like below (both in SELECT and in WHERE clause:
qry = session.query(Bar, Bar.foo_count).filter(Bar.foo_count > 0)
for (bar, bar_foo_count) in qry:
print bar, bar_foo_count
As you can see, the query will return tuples of (Bar, foo_count) in just one query, and now you can do what you wish with that.