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'),)
Related
I try to set fk which parent_id contains id of a person in People table in orm manner and backpopulate between them but it does not work.
class People(Base):
__tablename__ = "people"
id = Column(Integer, primary_key=True)
name = Column(String(20), nullable=False, unique=True)
parent_id = Column(Integer, ForeignKey('people.id'))
parent = relationship("People", back_populates="parent", uselist=False)
engine = create_engine(
f'mssql://{username}:{password}#{server_name}/{db_name}?driver=SQL+Server&trusted_connection=yes')
Session = sessionmaker(bind=engine)
session = Session()
session.add(People(name='me'))
raise sa_exc.ArgumentError(
sqlalchemy.exc.ArgumentError: People.parent and back-reference People.parent are both of the same direction symbol('ONETOMANY'). Did you mean to set remote_side on the many-to-one side ?
You can use the remote_side argument.
Here's code I'm using adapted to your example:
class People(Base):
__tablename__ = "people"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('people.id'))
parent = relationship('People', foreign_keys=parent_id, remote_side=id)
children = relationship('People', back_populates='parent')
I have a model in SQLAlchemy which defines a many-to-many relationship using an association table (automap is being used here because I'm using an existing database):
from sqlalchemy import (Column, Table, MetaData, Integer, Text, LargeBinary,
ForeignKey, Float, Boolean, Index)
from sqlalchemy.ext.automap import automap_base, AutomapBase
from sqlalchemy.orm import Session, deferred, relationship
Base: AutomapBase = automap_base()
class VariantAssociation(Base):
__tablename__ = "sample_variant_association"
vid = Column(Integer, ForeignKey("variants.variant_id"),
primary_key=True)
sid = Column(Integer, ForeignKey("samples.sample_id"),
primary_key=True)
vdepth = Column(Integer)
valt_depth = Column(Integer)
gt = Column(Text)
gt_type = Column(Integer)
fraction = Column(Float)
variant = relationship("Variant", back_populates="samples")
sample = relationship("Sample", back_populates="variants")
__table_args__ = (Index('ix_sample_variant_association_valt_depth',
"valt_depth"),
Index('ix_sample_variant_association_vdepth',
"vdepth"),
Index('ix_sample_variant_association_vid', 'vid'),
Index('ix_sample_variant_association_sid', 'sid'),
Index('ix_sample_variant_association_fraction',
'fraction')
)
class Variant(Base):
__tablename__ = "variants"
variant_id = Column(Integer, primary_key=True)
info = deferred(Column(LargeBinary))
samples = relationship("VariantAssociation",
back_populates="variant")
class Sample(Base):
__tablename__ = "samples"
sample_id = Column(Integer, primary_key=True, index=True)
name = Column(Text, index=True)
variants = relationship("VariantAssociation",
back_populates="sample")
class SampleGenotypeCount(Base):
__tablename__ = 'sample_genotype_counts'
sample_id = Column(Integer, primary_key=True)
num_hom_ref = Column(Integer)
num_het = Column(Integer)
num_hom_alt = Column(Integer)
num_unknown = Column(Integer)
class DataMigration(Base):
__tablename__ = "datamigration"
done = Column(Boolean, primary_key=True)
On querying, this eventually generates this warning:
Query:
query = session.query(Variant).join(
Variant.samples).join(Sample)
Warning:
/usr/local/lib/python3.9/site-packages/sqlalchemy/orm/relationships.py:3441: SAWarning:
relationship 'Variant.variantassociation_collection' will copy column variants.variant_id to
column sample_variant_association.vid, which conflicts with relationship(s): 'Variant.samples'
(copies variants.variant_id to sample_variant_association.vid). If this is not the intention,
consider if these relationships should be linked with back_populates, or if viewonly=True
should be applied to one or more if they are read-only. For the less common case that foreign
key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate
the columns that should be written towards. The 'overlaps' parameter may be used to remove
this warning. (Background on this error at: http://sqlalche.me/e/14/qzyx)
I've been looking through SO and the SQLAlchemy documentation but I was unable to find what could cause this issue since (in my view) the back_populates parameters are in the right places.
Where would the error in the model be? SQLAlchemy 1.3.23 did not generate one, FTR.
In order to set your own relationship names, you need to prevent Automap from generating relationships by iteself. You can achieve this by setting 'generate_relationship' to a function that returns None.
def generate_relationships(base, direction, return_fn, attrname, local_cls, referred_cls, **kw):
return None
Base.prepare(generate_relationship=generate_relationships)
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])
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")
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