Multiple relationships on composite foreign keys in sqlalchemy - python

I am trying to create an edge type class to link two nodes. Each of the nodes is a class by itself and both nodes and edge should be part of the same graph. I keep getting
Could not determine join condition between parent/child tables on relationship TypeiNode.edges - there are no foreign keys linking these tables.
even though the foreign keys are defined. I tried defining foreign keys on the node table edges relationship but got the same result. Can someone tell me what is wrong with this example.
class Edge(Base):
__tablename__ = 'edge'
id = Column(Integer, primary_key=True)
graph_id = Column(Integer, ForeignKey('graph.id'))
graph= relationship('Graph', back_populates='graph_edges')
type_1_node_id = Column(Integer, ForeignKey('type_1_node.id'))
type_1_node = relationship('Type1Node',
back_populates='edges',
foreign_keys=[type_1_node_id, graph_id])
type_2_node_id = Column(Integer, ForeignKey('type_2_node.id'))
type_2_node = relationship('Type2Node',
back_populates='edges',
foreign_keys=[type_2_node_id, graph_id ])
__table_args__ = (
ForeignKeyConstraint(
['type_1_node_id', 'graph_id'],
['type_1_node.id','type_1_node.graph_id']),
ForeignKeyConstraint(
['type_2_node_id', 'graph_id'],
['type_2_node.id','type_2_node.graph_id']),
)
class Type1Node(Base):
__tablename__ = 'type_1_node'
id = Column(Integer, primary_key=True)
graph_id = Column(Integer, ForeignKey('graph.id'))
graph = relationship('Graph', back_populates='graph_1_nodes')
edges = relationship('Edge', back_populates='type_1_node')
class Type2Node(Base):
__tablename__ = 'type_2_node'
id = Column(Integer, primary_key=True)
graph_id = Column(Integer, ForeignKey('graph.id'))
graph = relationship('Graph', back_populates='graph_2_nodes')
edges = relationship('Edge', back_populates='type_2_node')

If you read the error carefully, you'll notice it's not complaining about the relationships that you added the foreign_keys argument to, but the ones you didn't:
Could not ... on relationship **TypeiNode.edges** ...
I take
I tried defining foreign keys on the node table edges relationship but got the same result.
to mean that you did not try defining both ends at the same time. So the solution is to define the foreign keys in both ends:
class Type1Node(Base):
__tablename__ = 'type_1_node'
id = Column(Integer, primary_key=True)
graph_id = Column(Integer, ForeignKey('graph.id'))
graph = relationship('Graph')
edges = relationship('Edge', back_populates='type_1_node',
foreign_keys=[Edge.type_1_node_id, Edge.graph_id])

Related

SQLAlchemy Adjacency List - Constrain parent_id not equal to ID

I'm implementing an Adjacency List in SQL Alchemy which I have working. It's the basic example here of Node. I have it working.
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
data = Column(String(50))
children = relationship("Node")
But, I want to enforce a constraint where the parent_id != id. That is, a row can not be its own parent. I am not sure how to enforce this. Do I need to use a #validates or is there a DB constraint I can set up on the column(s).
You could use either #validates or a db constraint. The constraint would look like this:
import sqlalchemy as sa
class Node(Base):
__tablename__ = 'node'
id = sa.Column(sa.Integer, primary_key=True)
parent_id = sa.Column(sa.Integer, sa.ForeignKey('node.id'))
data = sa.Column(sa.String(50))
children = orm.relationship("Node")
__table_args__ = (sa.CheckConstraint('parent_id != id'),)

SQLAlchemy: Joined Inheritance with One-to-One Relationship

I'm trying to accomplish the following joined inheritance with one-to-one relationship structure with SQLAlchemy: Box and Item are both Elements, and Box has an Item.
The class definitions are as follows:
class Element(Base):
__tablename__ = 'element'
id = Column(Integer, primary_key=True)
el_type = Column(String)
__mapper_args__ = dict(
polymorphic_identity='element',
polymorphic_on=el_type,
)
class Box(Element):
# Joined table inheritance: Element.
__tablename__ = 'box'
id = Column(Integer, ForeignKey('element.id'), primary_key=True)
__mapper_args__ = dict(polymorphic_identity='box')
# One-to-one relationship: Item.
item = relationship('Item',
back_populates='box', uselist=False)
def __init__(self, item_name=None):
self.item = Item(item_name)
class Item(Element):
# Joined table inheritance: Element.
__tablename__ = 'item'
id = Column(Integer, ForeignKey('element.id'), primary_key=True)
__mapper_args__ = dict(polymorphic_identity='item')
# One-to-one relationship: Box.
box_id = Column(Integer, ForeignKey('box.id'))
box = relationship('Box',
back_populates='item')
name = Column(String)
def __init__(self, name):
self.name = name
When I try to instantiate b = Box('rock'), I get:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Box.item - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Now I have gone to SQLAlchemy's page on this issue (great docs, I might add), but I'm not sure how to bridge the gap between their example and my situation. I thought this might fix it:
class Item(Element):
# ...
box = relationship('Box',
back_populates='item', foreign_keys=[id, box_id])
# ...
...but I get the same error.
What am I doing wrong?
UPDATE:
I have now tried using ForeignKeyConstraint constructs in an effort to more explicitly describe the desired behavior to SQLAlchemy; to no avail:
class Box(Element):
# ...
id = Column(Integer, primary_key=True)
# ...
ForeignKeyConstraint([id], ['element.id'])
# ...
class Item(Element):
# ...
id = Column(Integer, primary_key=True)
# ...
# One-to-one relationship: Box.
box_id = Column(Integer)
box = relationship('Box',
back_populates='item')
ForeignKeyConstraint([id, box_id], ['element.id', 'box.id'])
# ...
A simple AssertionError is thrown back at me (no description). Given that SQLAlchemy has a good track record of producing meaningful error messages, could this suggest that this is an unanticipated situation?

SqlAlchemy can't determine join condition

I have 2 tables defined:
class TCableSet(Base):
__tablename__ = 'tCableSet'
ixCableSet = Column(Integer, primary_key=True)
decCableSetOne = Column(Numeric(8, 2))
decCableSetTwo = Column(Numeric(8, 2))
decCableSetThree = Column(Numeric(8, 2))
class TStepVoltage(Base):
__tablename__ = 'tStepVoltage'
ixStepVoltage = Column(Integer, primary_key=True)
ixSubReport = Column(Integer, ForeignKey('tSubReport.ixSubReport'), nullable=False)
iVoltage = Column(Integer)
ixPhaseA = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
ixPhaseB = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
ixPhaseC = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
sub_report = relationship('TSubReport',
backref=backref('step_voltage'))
I understand why I am getting this error but can't figure out a proper way (yet).
When the table gets saved, I store the values in the tCableSet table and then use the id as a foreign key in my tStepVoltage table. The problem I have is when I go to retrieve the data, I want to be able to get the values(tCableSet row) along with the rest of my tStepVoltage table via a relationship, however I'm not sure how to go about this since I don't have a field in my tCableSet that can directly be linked via relationship to my tStepVoltage. I basically just needed the tCableSet for normalization
Since you have more than one foreign key that points to the same table, you have to tell SQLAlchemy which foreign key to use.
For example:
sub_report_a = relationship('TSubReport',
backref=backref('step_voltage'),
foreign_keys=[ixPhaseA])

Self referential relationship including a relationship attribute

Situation
I have the Self-Referential Many-to-Many Relationship (almost identical to the sqlalchemy manual entry of the same heading). This relationship is governed by the table entity_weights. This code works!
Question
How do I include an attribute in class Entity representing the table column entity_weights.weight. Lets say the attribute would be called Entity.child_weights. It is important that the rank of Entity.child_entities and Entity.child_weights are identical.
entity_weights = Table('entity_weights', Base.metadata,
Column('id',Integer, primary_key=True),
Column('parent_entity_id',Integer, ForeignKey('entity.id')),
Column('entity_id',Integer, ForeignKey('entity.id')),
Column('weight',Float))
class Entity(Base):
__tablename__ = 'entity'
id = Column(Integer, primary_key=True)
name = Column(String)
domicile_id = Column(Integer, ForeignKey('domicile.id'))
entity_type = Column('type',Enum('asset','institution','model'))
source_table_id = Column(Integer)
child_entities = relationship('Entity',
secondary=entity_weights,
primaryjoin=id==entity_weights.c.parent_entity_id,
secondaryjoin=id==entity_weights.c.entity_id,
backref='parent_entity_id'
)
The cleanest solution I've found for this scenario is to break up the child_entities relationship by adding entity_weights as a one-to-many relationship on Entity and use an association proxy to proxy the weight value as well as the remote side of the many-to-many relationship:
class EntityWeight(Base):
__tablename__ = "entity_weights"
id = Column(Integer, primary_key=True)
parent_entity_id = Column(Integer, ForeignKey('entity.id'))
entity_id = Column(Integer, ForeignKey('entity.id'))
weight = Column(Float)
entity = relationship("Entity", primaryjoin=lambda: EntityWeight.entity_id == Entity.id)
class Entity(Base):
...
_child_weights = relationship(EntityWeight, primaryjoin=id == EntityWeight.parent_entity_id)
child_weights = association_proxy("_child_weights", "weight")
child_entities = association_proxy("_child_weights", "entity")

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

Categories