How can I make a relationship attribute be sorted by the child tables primary key? I have a schema where many of the tables have a common parent and the parent has a list of each of its children using the declaritive system like this
class Scenario(Base):
__tablename__ = 'scenarios'
id = Column(Integer, primary_key=True, autoincrement=True)
class ScenarioMixin(object):
#declared_attr
def scenario_id(cls):
return Column(None, ForeignKey(Scenario.id),
primary_key=True,
index=True)
#declared_attr
def scenario(cls):
return relationship('Scenario',
backref=backref(cls.__tablename__))
# example scenario-specific table
class Child(Base, ScenarioMixin):
__tablename__ = 'children'
id1 = Column(String, primary_key=True)
id2 = Column(String, primary_key=True)
So the scenario object has an attribute children which is a list of children. This works fine, except that the order of the Child objects in the list is arbitrary. This is fine for the semantics of the application, it leads to nondeterministic behavior. What's the best way to configure the mixin so that every child list will be sorted by the primary key of its table, even if it's a compound primary key?
Specify order_by argument in your relationship definition as in the code below.
#declared_attr
def scenario(cls):
return relationship(
'Scenario',
backref=backref(
cls.__tablename__,
order_by=lambda: cls.__table__.primary_key.columns
),
)
In you case you cannot return cls.__table__.primary_key.columns straight away because at the definition time the __table__ does not exist yet. But returning a callable works.
See Relationships API for more information.
Related
I have a model, where Edge is an Association Object connecting Nodes, and is being subclassed in order to create different types of Edges. My ultimate goal is to create all fields of the subclasses dynamically in the Edge class itself (or later with a mixin).
Edge has a composite primary key consisting of the two foreign keys to the Node table, and a edge_type field, that also acts as the discriminator for the subclasses.
For this to work I need to connect the subclasses with a ForeignKeyConstraint to the composite primary key of the Edge class. Now, to create this ForeignKeyConstraint dynamically I am defining it with #declared_attr, but cannot reference the edge_type column that should be inherited from the Edge table to the subclasses, but I am getting the following exceptions:
File "/data/foo/venv/lib/python3.5/site-packages/sqlalchemy/util/_collections.py", line 194, in __getitem__
return self._data[key]
KeyError: 'edge_type'
During handling of the above exception, another exception occurred:
<snip>
sqlalchemy.exc.ArgumentError: Can't create ForeignKeyConstraint on table 'hierarchy': no column named 'edge_type' is present.
My model looks like this:
class Edge(HasTablename, Base):
from_node_id = Column(Integer, ForeignKey(Node.id), primary_key=True)
to_node_id = Column(Integer, ForeignKey(Node.id), primary_key=True)
from_node = relationship(Node, foreign_keys=from_node_id,
backref=backref('from_edges')
to_node = relationship(Node, foreign_keys=to_node_id,
backref=backref('to_edges')
edge_type = Column(String, primary_key=True)
#declared_attr
def __mapper_args__(cls):
if has_inherited_table(cls):
return {'polymorphic_identity': cls.__name__.lower()}
else:
return {'polymorphic_identity': cls.__name__.lower(),
'polymorphic_on': cls.edge_type}
#declared_attr
def __table_args__(cls):
if has_inherited_table(cls):
return (ForeignKeyConstraint(
['from_node_id', 'to_node_id', 'edge_type'],
[cls.__base__().__table__.c.from_node_id,
cls.__base__().__table__.c.to_node_id,
cls.__base__().__table__.c.edge_type]), )
class Hierarchy(Edge):
from_node_id = Column(Integer, primary_key=True)
to_node_id = Column(Integer, primary_key=True)
class History(Edge):
from_node_id = Column(Integer, primary_key=True)
to_node_id = Column(Integer, primary_key=True)
This works fine if I explicitly define edge_type = Column(String, primary_key=True) in the subclasses, but I don't know why it is not inherited, or cannot be accessed from the ForeignKeyConstraint in #declared_attr?
Let's say I have a relationship as follows:
parent_child = db.Table('parent_child',
db.Column('parent_id',
db.Integer,
db.ForeignKey('parent.id', ondelete='CASCADE')),
db.Column('child_id',
db.Integer,
db.ForeignKey('child_instance.id', ondelete='CASCADE')))
class Parent(db.Model):
id = db.Column(db.Integer, primary_key=True)
child_instances = db.relationship(
'ChildInstance', secondary=parent_child, backref=backref('parents', lazy='joined'), lazy=False)
class ChildInstance(db.Model):
id = db.Column(db.Integer, primary_key=True)
Assuming we have a child instance object, we could just use append to add it to the child_instances relationship.
However if I want to implement creation of a parent given the child numeric ids (let's say because I already have them cached from prior sessions):
def create_parent_with_children_ids(children_ids):
returned = Parent()
db.session.add(returned)
#????
How can this be done? From simple attempts it seems that append on the relationship only works for ChildInstance objects, and I want to avoid querying for them just for this purpose...
Thanks in advance!
My table relationships in SQLAlchemy have gotten quite complex, and now I'm stuck at this error no matter how I configure my relationship.
I'm a bit new to SQLAlchemy so I'm not sure what I'm doing wrong, but I keep getting the same error no matter what I do.
I have a hierarchy of tables, all inheriting from 'Node', which is a table with self-referential id and parent_id columns. All of this works. Now I have another Node table 'Information', which contains a composite primary key that is referenced by a different Node table, 'Widget'.
Base = declarative_base()
class Node(Base):
__tablename__ = 'Node'
id = Column(Integer, primary_key=True)
parentid = Column(Integer, ForeignKey('Node.ID')
type = Column(Text(50))
children = relationship('Node')
__mapper_args__ = {
'polymorphic_identity': 'Node',
'polymorphic_on': type
}
class Information(Node):
__tablename__ = 'Information'
id = Column(Integer, ForeignKey('Node.ID'), primary_key=True)
Name = Column(Text, primary_key=True)
Number = Column(Float, primary_key=True)
Widgets = relationship('Widget', backref='ThisInformation')
__mapper_args__ = {'polymorphic_identity': 'Information'}
class Widget(Node):
__tablename__ = 'Widget'
id = Column(Integer, ForeignKey('Node.ID'), primary_key=True)
Name = Column(Text)
UnitType = Column(Text)
Amount = Column(Float)
_Number = Column('Number', Float)
__table_args__ = (ForeignKeyConstraint(
['Name', 'Number'],
['Information.Name', 'Information.Number']),
{})
__mapper_args__ = {'polymorphic_identity': 'Widget'}
I was worried this would give me circular reference issues, but instead it gives me this error:
InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Original exception was: Could not determine join condition between parent/child tables on relationship Widget.Information - 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.
I have tried adding foreign_keys arguments to the relationship, both on Widget and Information side, but I get the exact same error. Can anyone help?
After a lot of searching I finally found a really simple answer in this answer to another question.
All I did was add 'inherit_condition': (id == Node.id) to the mapper in the classes that inherit from Node so that it looked like this:
__mapper_args__ = {'polymorphic_identity': 'Information',
'inherit_condition': (id == Node.id)}
And it works perfectly.
I have a basic one to many relationship:
class Term(Base):
__tablename__ = 'term'
id = Column(Integer, primary_key=True)
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
term = Column(Integer, ForeignKey('term.id'))
But when I load the Node object, access the "term" property, I just get the numeric term id, not the Term object.
node = session.query(Node).filter(Node.id == 1).one()
print node.term # 123
How do I get Foreign Key fields to lazy load the object?
Thanks very much.
Ben
because your term attribute is a Column, sqlalchemy maps it as that column's value. You can get sqlalchemy to actually load the referent row by using relationship:
from sqlalchemy.orm import relationship
class Term(Base):
__tablename__ = 'term'
id = Column(Integer, primary_key=True)
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
term = Column(Integer, ForeignKey('term.id'))
related_term = relationship(Term, backref="nodes")
Because my_node.related_term looks a bit odd, I tend to prefer a naming convention of having the column called table_column instead of just table, so that I can also name the relationship attribute after the table, instead of inventing some other, odd name.
Use the returned value of node.term for a new query, to get the related objects:
node = session.query(Node).filter(Node.id == 1).one()
related_term = session.query(Term).filter(Term.id == node.term).one()
I'm trying to get my head around the best way to construct a relationship that maps many Constants to many Items.
My initial relationship, an Item has a Constant, looked like this.
class Constant(Base):
__tablename__ = "Constant"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constantId = Column(Integer, ForeignKey("Constant.id"))
constant = relationship("Constant")
However, I really need my item to have more than one constant, something like this...
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constant1Id = Column(Integer, ForeignKey("Constant.id"))
constant1 = relationship("Constant")
constant2Id = Column(Integer, ForeignKey("Constant.id"))
constant2 = relationship("Constant")
My first attempt was to use an association table...
item_to_constant_assoc = Table("itemToConstantAssoc", Base.metadata, Column("constantId", Integer, ForeignKey("Constant.id"), Column("itemId", Integer, ForeignKey("Item.id")))
while updating the Item class accordingly:
Class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constant1 = relationship("Constant", secondary=item_to_constant_assoc, uselist=False)
constant2 = relationship("Constant", secondary=item_to_constant_assoc, uselist=False)
This failed (understandably when looking at the MySQL tables that were created) because Item.constant1 and Item.constant2 referred to the same entry in the association table.
My next step is to add another association table for the second constant but I have to wonder whether I'm barking up the wrong tree as I seem to be creating a large number of tables for a relatively simple mapping. I've read the documentation. It is detailed and substantial (thanks Michael Bayer!) and I may have just overlooked a section. Could anyone provide me with a few pointers either to this problem, or what I should be looking for in the docs?
Thanks!
Phil
Couldn't see the wood for the trees. This is easily accomplished by using the primaryjoin argument on the relationship.
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
constant1Id = Column(Integer, ForeignKey("Constant.id"))
constant1 = relationship("Constant", primaryjoin="Constant.id==Item.constant1Id")
constant2Id = Column(Integer, ForeignKey("Constant.id"))
constant2 = relationship("Constant", primaryjoin="Constant.id==Item.constant2Id")
A many-to-many association already allows each Item to have an unlimited number of constants. You don't need anything more than this as your two base tables.
class Constant(Base):
__tablename__ = "Constant"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
class Item(Base):
__tablename__ = "Item"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
item_to_constant_assoc = Table("itemToConstantAssoc", Base.metadata, Column("constantId", Integer, ForeignKey("Constant.id"), Column("itemId", Integer, ForeignKey("Item.id")))
At this point, every Item has an unlimited number of Constants. When you want a specific constant, you have to query the constant by the name attribute in the Constant table. Your association table is merely a list of key pairs: (itemID, constantId).
The set of all Constants for an Item is a three-table join for all association rows joined with matching Constant rows for a given Item.
The set of all Items for a Constant is a three-table join for all association rows join with match Item rows for a given Constant.
A specific Constant for an Item needs to be retrieved via a join. You think of it like the the set of all Constants for a given Item where both the Item and the Constant name are given. The SQL involves a join even though only a single row is retrieved.
I think your generic query to associate a constant with all relevant items or an item with all relevant constants will look something like this.
query(Item). join(item_to_constant_assoc.itemId==Item.itemId). join(item_to_constant_assoc.contantId==Constant.constantId