Combining relationships - python

I have a model Zone, a model Entity and a model Transit. Transit is minimally defined as:
class Entity(db.Model):
__tablename__ = 'entities'
id = db.Column(db.Integer, primary_key=True)
contained_by = db.Column(db.Integer, db.ForeignKey('entities.id'))
contains = db.relationship('Entity', backref='container')
discriminator = db.Column('type', db.String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
class Zone(Entity):
__mapper_args__ = {'polymorphic_identity': 'zones'}
routes = db.relationship('Transit')
(stuff goes here)
class Transit(db.Model):
__tablename__ = "transits"
start = db.Column(db.Integer, db.ForeignKey('zones.id'))
end = db.Column(db.Integer, db.ForeignKey('zones.id'))
Zone also has a couple of bits about distance and how defensible it is, but that is irrelevant for this.
First off, due to the fact that Zone is subclassed from Entity using single-table inheritance can I reference zones.id?
Secondly, will the Zone.routes property merge Transit.start and Transit.end?

no, you need to use the table name, which in your case (Single-table inheritance) is entities
no, these will not be merged. You can create two relationships, and have a (hybrid) property which would combine both, but this will only be for reading purposes, as when you would like to modify this property (for example, add Transits), you would still need to specify both sides (start and end).
I am not sure I understand the question here
update-1: as requested in comment, Concrete-Table inheritance code below:
class Zone(Entity):
__mapper_args__ = {'polymorphic_identity': 'zones'}
__tablename__ = "zones"
id = Column(Integer, ForeignKey('entities.id'), primary_key=True)
#property
def transits(self):
return self.transits_from_here + self.transits_to_here
class Transit(Base):
__tablename__ = "transits"
id = Column(Integer, primary_key=True)
start = Column(Integer, ForeignKey('zones.id'))
end = Column(Integer, ForeignKey('zones.id'))
s_zone = relationship(Zone, primaryjoin=(start==Zone.id), backref="transits_from_here")
e_zone = relationship(Zone, primaryjoin=(end==Zone.id), backref="transits_to_here")

Related

Ordering relationship by property of child elements

I use flask-sqlalchemy on a Flask project to model my database.
I need to sort the elements of a many-to-many relationship based on properties of different child elements of one side.
I have "Work" (the parent element), "Tag" (the children), "Type" (a one-to-many relationship on Tag) and "Block" (a one-to-many relationship on Type). Tags and Works are joined with a mapping table "work_tag_mapping".
In essence, each tag has exactly one type, each type belongs to exactly one block, and many tags can be added on many works.
I now want the list of tags on a work be sorted by block first and type second (both have a "position" column for that purpose).
Here are my tables (simplified for the sake of the question):
class Work(db.Model):
__tablename__ = 'work'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
tags = db.relationship('Tag', order_by="Tag.type.block.position, Tag.type.position", secondary=work_tag_mapping)
class Tag(db.Model):
__tablename__ = 'tag'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
type_id = db.Column(db.Integer, db.ForeignKey('type.id'), nullable=False)
type = db.relationship('Type')
work_tag_mapping = db.Table('work_tag_mapping',
db.Column('id', db.Integer, primary_key=True),
db.Column('work_id', db.Integer, db.ForeignKey('work.id'), nullable=False),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), nullable=False)
)
class Type(db.Model):
__tablename__ = 'type'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
block_id = db.Column(db.Integer, db.ForeignKey('block.id'), nullable=False)
block = db.relationship('Block')
class Block(db.Model):
__tablename__ = 'block'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
Now, it is the "order_by" in the "tags" relationship that doesn't work as I initially hoped.
The error I get is "sqlalchemy.exc.InvalidRequestError: Property 'type' is not an instance of ColumnProperty (i.e. does not correspond directly to a Column)."
I am new to SQLalchemy, Flask and indeed Python, and none of the ressources or questions here mention a case like this.
While this appears not to be possible directly, adding a getter and performing the sorting on retrieval does the trick. Adding lazy='dynamic' ensures the collection behaves as a query, so joins can be performed.
_tags = db.relationship('Tag', lazy='dynamic')
#hybrid_property
def tags(self):
return self._tags.join(Type).join(Block).order_by(Block.position, Type.position)

SQLAlchemy: Override relationship-defined "order_by" in a query

So, I have a model that is something like:
class Foo(model):
__tablename__ = "foo"
id = Column(Integer, primary_key=True)
data = relationship(
"FooData",
cascade="all, delete-orphan",
backref="foo",
lazy="dynamic",
order_by="desc(FooData.timestamp)"
)
#property
def first_item(self):
# the problem is here:
return self.data.order_by(asc("timestamp")).first()
#property
def latest_item(self):
return self.data.first()
class FooData(Model):
__tablename__ = "foo_data"
foo_id = Column(Integer, ForeignKey("foo.id"), primary_key=True)
timestamp = Column(DateTime, primary_key=True)
actual_data = Column(Float, nullable=False)
So, the problem is with the first_item method there: when it is defined as above, the SQL looks like this:
SELECT foo_data.timestamp AS foo_data_timestamp, foo_data.actual_data AS foo_data_actual_data, foo_data.foo_id AS foo_data_foo_id
FROM foo_data
WHERE :param_1 = foo_data.foo_id ORDER BY foo_data.timestamp DESC, foo_data.timestamp ASC
-- ^^^^^^^^^^^^^^^^^^^^^^
Obviously, the order_by specified in the query is being appended to the one specified in the relationship definition, instead of replacing it; is there a way for a query to override the original order_by? I know I could specify a separate query directly on the FooData class, but I would like to avoid that if possible.
According to documentation:
All existing ORDER BY settings can be suppressed by passing None - this will suppress any ORDER BY configured on mappers as well.
So the simple solution is to reset ORDER BY clause and then apply the one you need. Like:
self.data.order_by(None).order_by(asc("timestamp")).first()
In case you don't want to reset whole ORDER BY clause, but only want to override one column order, AFAIK there is no built-in way for it.
I know this is an old post, but it showed up when I was searching, so maybe this will be useful to someone else
class Foo(model):
__tablename__ = "foo"
id = Column(Integer, primary_key=True)
data = relationship(
"FooData",
cascade="all, delete-orphan",
backref="foo",
lazy="dynamic",
order_by=lambda: FooData.__table__.columns.timestamp.desc()
)
...
class FooData(Model):
__tablename__ = "foo_data"
foo_id = Column(Integer, ForeignKey("foo.id"), primary_key=True)
timestamp = Column(DateTime, primary_key=True)
actual_data = Column(Float, nullable=False)

SQLAlchemy - Define a model subclass as a subset of the table

SQLAlchemy newbie here.
I'm trying to define a model subclass that represents a subset of table data. Specifically, I want the subclass to map the most recent row for a given ID.
For example, suppose I have the following model:
class AddressHistory(Base):
__table__ = 'address_table'
date = Column(Date, index=True, nullable=False)
id = Column(BigInteger, primary_key=True)
street = Column(String(2000))
city = Column(String(2000))
state = Column(String(2000))
zip = Column(Integer)
What I want to do is define a subclass of this model which represents the most recent address record for a given id:
class MostRecentAddress(Address):
“””
Represents a row in AddressHistory with the most recent date for a given id.
”””
Is there some sort of subquery I can pass to the mapper_args ? Or is there a way to define the table as a select statement?
You're looking for single table inheritance.
https://docs.sqlalchemy.org/en/13/orm/inheritance.html#single-table-inheritance
Your code sample is very nearly exactly how to go about doing this. You just need to add the mapper.
class Person(Base):
__tablename__ = 'people'
id = Column(Integer, primary_key=True)
discriminator = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
class Engineer(Person):
__mapper_args__ = {'polymorphic_identity': 'engineer'}
primary_language = Column(String(50))

query to hierarchical mapper

I have two models, related with many-to-many, one of them is hierarchical model:
#hierarchical model
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String)
Tag.parent_id = Column(Integer, ForeignKey(Tag.id, ondelete='CASCADE'))
Tag.childs = relationship(Tag, backref=backref('parent', remote_side=[Tag.id]),
cascade="all, delete")
class Subject(Base):
__tablename__ = "subjects"
id = Column(Integer, primary_key=True, doc="ID")
name = Column(String)
tags = relationship(Tag, secondary="tags_subjects", backref="subjects")
#many-to-many relations model
class TagsSubjects(Base):
__tablename__ = "tags_subjects"
id = Column(Integer, primary_key=True)
tag_id = Column(Integer, ForeignKey("tags.id"))
subject_id = Column(Integer, ForeignKey("subjects.id"))
So, I'll try to explain what I want to do... I want to make one (or several) query, for search all Subject's objects,
that have 'name' field value like 'foo' OR that has related tags having names with values like 'foo'
OR that has related tags, that has one or more parents (or above by hierarchy) tag with 'name' value like 'foo'
I've tried to do somethis like this:
>>> subjects = session.query(Subject).filter(or_(
Subject.name.ilike('%{0}%'.format('foo')),
Subject.tags.any(
Tag.name.ilike('%{0}%'.format('foo')))
)).order_by(Subject.name).all()
But it isn't correct and "flat" query, without hierarchical feature :(
How to do this by SQLAlchemy's API?
Thanks!
P.S. I'm using SQLite backend

Creating container relationship in declarative SQLAlchemy

My Python / SQLAlchemy application manages a set of nodes, all derived from a base class Node. I'm using SQLAlchemy's polymorphism features to manage
the nodes in a SQLite3 table. Here's the definition of the base Node class:
class Node(db.Base):
__tablename__ = 'nodes'
id = Column(Integer, primary_key=True)
node_type = Column(String(40))
title = Column(UnicodeText)
__mapper_args__ = {'polymorphic_on': node_type}
and, as an example, one of the derived classes, NoteNode:
class NoteNode(Node):
__mapper_args__ = {'polymorphic_identity': 'note'}
__tablename__ = 'nodes_note'
id = Column(None,ForeignKey('nodes.id'),primary_key=True)
content_type = Column(String)
content = Column(UnicodeText)
Now I need a new kind of node, ListNode, that is an ordered container of zero or more Nodes. When I load a ListNode, I want it to have its ID and title
(from the base Node class) along with a collection of its contained (child) nodes. A Node may appear in more than one ListNode, so it's not a proper hierarchy. I would create them along these lines:
note1 = NoteNode(title=u"Note 1", content_type="text/text", content=u"I am note #1")
session.add(note1)
note2 = NoteNode(title=u"Note 2", content_type="text/text", content=u"I am note #2")
session.add(note2)
list1 = ListNode(title=u"My List")
list1.items = [note1,note2]
session.add(list1)
The list of children should only
consist of Node objects -- that is, all I need is their base class stuff. They shouldn't be fully realized into the specialized classes
(so I don't get the whole graph at once, among other reasons).
I started along the following lines, cobbling together bits and pieces I found in various places without a complete understanding of
what was going on, so this may not make much sense:
class ListNode(Node):
__mapper_args__ = {'polymorphic_identity': 'list', 'inherit_condition':id==Node.id}
__tablename__ = 'nodes_list_contents'
id = Column(None, ForeignKey('nodes.id'), primary_key=True)
item_id = Column(None, ForeignKey('nodes.id'), primary_key=True)
items = relation(Node, primaryjoin="Node.id==ListNode.item_id")
This approach fails in several ways: it doesn't appear to allow an empty ListNode, and setting the items attribute to a list results
in SQLAlchemy complaining that 'list' object has no attribute '_sa_instance_state'. Not surprisingly, hours of random mutations on this theme haven't given any
good results,
I have limited experience in SQLAlchemy but really want to get this working soon. I'd very much appreciate any advice or direction you can
offer. Thanks in advance!
You need an additional table for many-to-many relation:
nodes_list_nodes = Table(
'nodes_list_nodes', metadata,
Column('parent_id', None, ForeignKey('nodes_list.id'), nullable=False),
Column('child_id', None, ForeignKey(Node.id), nullable=False),
PrimaryKeyConstraint('parent_id', 'child_id'),
)
class ListNode(Node):
__mapper_args__ = {'polymorphic_identity': 'list'}
__tablename__ = 'nodes_list'
id = Column(None, ForeignKey('nodes.id'), primary_key=True)
items = relation(Node, secondary=nodes_list_nodes)
Update: below is an example for ordered list using association_proxy:
from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy.ext.associationproxy import association_proxy
class ListNodeAssociation(Base):
__tablename__ = 'nodes_list_nodes'
parent_id = Column(None, ForeignKey('nodes_list.id'), primary_key=True)
child_id = Column(None, ForeignKey(Node.id), primary_key=True)
order = Column(Integer, nullable=False, default=0)
child = relation(Node)
__table_args__ = (
PrimaryKeyConstraint('parent_id', 'child_id'),
{},
)
class OrderedList(InstrumentedList):
def append(self, item):
if self:
item.order = self[-1].order+1
else:
item.order = 1
InstrumentedList.append(self, item)
class ListNode(Node):
__mapper_args__ = {'polymorphic_identity': 'list'}
__tablename__ = 'nodes_list'
id = Column(None, ForeignKey('nodes.id'), primary_key=True)
_items = relation(ListNodeAssociation,
order_by=ListNodeAssociation.order,
collection_class=OrderedList,
cascade='all, delete-orphan')
items = association_proxy(
'_items', 'child',
creator=lambda item: ListNodeAssociation(child=item))

Categories