can't insert into one-to-many relationship in sqlalchemy - python

I have a one to many relationship in sqlalchemy but I don't get the inserts to work properly. I have tried to make a minimal example here:
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
db = create_engine('sqlite://')
db.echo = True
metadata = MetaData(db)
Base = declarative_base()
Session = sessionmaker(bind=db)
session = Session()
class Child(Base):
__table__ = Table('child', Base.metadata,
Column('id', Integer, primary_key=True),
Column('parent_id', Integer),
Column('name', String(50))
)
class Parent(Base):
__table__ = Table('parent', Base.metadata,
Column('id', Integer, primary_key=True),
Column('name', String(50))
)
children = relationship(Child, primaryjoin="Parent.id == Child.parent_id",
foreign_keys=[__table__.c.id])
Base.metadata.create_all(db)
c = Child(id=1, name="C")
p = Parent(id=1, name="P", children=[c])
session.add(p)
session.commit()
Running this gives AttributeError: 'list' object has no attribute '_sa_instance_state' from session.add(p).
I tried changing the classes to this:
class Child(Base):
__tablename__ = 'child'
id = Column('id', Integer, primary_key=True)
parent_id = Column('parent_id', Integer, ForeignKey('parent.id'))
name = Column('name', String(50))
class Parent(Base):
__tablename__ = 'parent'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50))
children = relationship(Child, backref="parent")
and then it works. I specify that the parent_id is a foreign key there and use the backref syntax. However in my production code the Parent table is a temporary table so I can't directly reference it using a ForeignKey. So whats wrong with the first code block and how can it be fixed ?

From the SQLAlchemy relationship API in the docs:
That is, if the primaryjoin condition of this relationship() is a.id == b.a_id, and the values in b.a_id are required to be present in a.id, then the “foreign key” column of this relationship() is b.a_id.
In your example, child.parent_id is required to be present in parent.id. So your "foreign key" column is child.parent_id.
Therefore, changing:
foreign_keys=[__table__.c.id]
to this:
foreign_keys=[Child.__table__.c.parent_id]
should solve your problem.

Related

SqlAlchemy many to many relation with mm table and another relation using that mm table

in SqlAlchemy many to many relation with mm table you guys helped me with the many to many relation with a mm table.
Now I've got another table using the Category.uid_foreign for a relation. How to do that the right way?
I've tried it that way, with no succeed:
from sqlalchemy import (
create_engine,
Integer,
String,
ForeignKey,
)
from sqlalchemy.schema import (
Column,
)
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy.orm import Session
Base = declarative_base()
# This connection string is made up
engine = create_engine(
'postgresql+psycopg2://user:pw#/db',
echo=False)
class Category(Base):
__tablename__ = "categories"
uid = Column(
Integer,
primary_key=True,
autoincrement=True,
)
title = Column(String)
class Specification(Base):
__tablename__ = "specifications"
uid = Column(
Integer,
primary_key=True,
autoincrement=True,
)
title = Column(String)
class Product(Base):
__tablename__ = "products"
uid = Column(
Integer,
primary_key=True,
autoincrement=True,
)
title = Column(String)
class SysCategoryMM(Base):
__tablename__ = "categories_records"
uid_local = Column(Integer, ForeignKey("categories.uid"), primary_key=True)
uid_foreign = Column(Integer, foreign_keys=["Product.uid", "Specification.uid"], primary_key=True)
fieldname = Column(String)
product = relationship(
"Product",
backref="related_categories",
)
specification = relationship(
"Specification",
backref="related_specifications",
)
category = relationship(
"Category",
backref="related_products",
)
Results in:
sqlalchemy.exc.NoForeignKeysError: Can't find any foreign key relationships between 'categories_records' and 'products'.
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship SysCategoryMM.product - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
Could you elp me? Tried like hours...

Self-Referential Many-to-Many Relationship with Association Proxy in SQLAlchemy

I'm trying to create a Self-Referential Many-to-Many Relationship. The example outlined in the SQLAlchemy documentation works great. Here are the models I created:
from sqlalchemy import Integer, ForeignKey, String, Column, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
Table('NodeToNode', Base.metadata,
Column('leftNodeId', Integer, ForeignKey('Node.id'), primary_key=True),
Column('rightNodeId', Integer, ForeignKey('Node.id'), primary_key=True)
)
class Node(Base):
__tablename__ = 'Node'
id = Column(Integer, primary_key=True)
label = Column(String)
rightNodes = relationship('Node',
secondary='NodeToNode',
primaryjoin='Node.id==NodeToNode.c.leftNodeId',
secondaryjoin='Node.id==NodeToNode.c.rightNodeId',
backref='leftNodes'
)
And the script for adding data in:
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
engine = create_engine('sqlite:///practice.sqlite3')
session = Session(bind=engine)
nodes = [
Node(label='A'),
Node(label='B'),
Node(label='C'),
Node(label='D'),
Node(label='E'),
]
nodes[0].rightNodes = [nodes[1], nodes[3], nodes[2]]
nodes[0].leftNodes = [nodes[4]]
session.add_all(nodes)
session.commit()
I want to add a column to the association table so I'd assume I need to convert the association table to its own class:
class NodeToNode(Base):
__tablename__ = 'NodeToNode'
leftNodeId = Column(Integer, ForeignKey('Node.id', onupdate='CASCADE'), primary_key=True)
rightNodeId = Column(Integer, ForeignKey('Node.id', onupdate='CASCADE'), primary_key=True)
sortOrder = Column(Integer, nullable=False)
This, however, results in the following error:
sqlalchemy.exc.InvalidRequestError: Class <class 'models.node.NodeToNode'> does not have a mapped column named 'c'
Any idea what I'm doing wrong here?

SQLAlchemy ORM: proxy attribute pointing to the first element of a relation?

Starting from the many-to-many relationship example from the SQLAlchemy documentation, I want to add an attribute first_child that will return the first child of children defined by the relationship. The first_child attribute needs to be useable in an association_proxy attribute definition such as first_child_id below.
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary=association_table)
first_child = ???
first_child_id = association_proxy('first_child', 'id')
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
I'm thinking I need a to declare first_child as either a hybrid_property or a column_property, but I don't know how to return the first element.
In addition to first_child, I also need last_child and an associated last_child_id attribute.
I'm using SQLAlchemy with a MySQL database.
If what you need is to have minimum start_time and maximum end_time, then I would use column_property just for these columns:
association_table = Table(
'association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
start_time = Column(DateTime)
end_time = Column(DateTime)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
backref="parents",
)
min_start_time = column_property(
select([Child.start_time.label("min_start_time")])
.where(Child.id == association_table.c.right_id)
.where(id == association_table.c.left_id)
.order_by(Child.start_time.asc())
.limit(1)
)
max_end_time = column_property(
select([Child.end_time.label("max_end_time")])
.where(Child.id == association_table.c.right_id)
.where(id == association_table.c.left_id)
.order_by(Child.end_time.desc())
.limit(1)
.as_scalar()
)
But if you need more than one such special column from the relationship, probably it would be more efficient to use hybrid_property.
The problem with association_proxy is that you cannot use it on hybrid_properties (or at least not in a direct manner).
A simple solution might be to use a property decorator, which would be evaluated on an already-loaded instance:
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary=association_table)
#property
def first_child(self):
return children[0]
However, you will not be able to use it on queries and it is a "read only" property. More information on SQLAlchemy plain-descriptor.

How to query for all groups of type 'foo' that contain user_x? (many-to-many table)

Given the following tables below, how do I query for all groups of grouptype 'foo' for user_x?
The equivalent SQL would be something like:
SELECT * FROM users_to_groups
LEFT JOIN users ON user_id=users.id
LEFT JOIN groups ON group_id=groups.id
WHERE groups.type='type1' AND user_id=1;
I was thinking the SQLAlchemy query would look something like:
session.query(UserGroup).filter(UserGroup.user==user_x,
UserGroup.group.grouptype=='foo')
but I don't know how to specify the grouptype (the above query raises this exception: AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with UserGroup.group has an attribute 'grouptype')
users_to_groups = Table(
'users_to_groups', Base.metadata,
Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
Column('group_id', Integer, ForeignKey('groups.id'), primary_key=True),
)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
groups = relationship('Group',
secondary=users_to_groups,
backref=backref('users',
collection_class=set),
collection_class=set)
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
grouptype = Column(String)
Base.metadata.create_all(engine)
class UserGroup(object):
pass
mapper(UserGroup, users_to_groups,
properties={
'group' : relationship(Group),
'user' : relationship(User),
})
I'm using sqlalchemy 0.8.2 with Postgres 9.2.4.
You can use RelationshipProperty.Comparator.has():
session.query(UserGroup).filter(UserGroup.user == user_x,
UserGroup.group.has(Group.grouptype == 'foo'))
You may find it more natural to query for Group directly:
session.query(Group).filter(Group.users.any(User.id == user_x),
Group.grouptype == 'foo')

Many-to-many relationship traversal with SQLAlchemy

I'm quite new to SQLAlchemy (and I do not have much experience with databases in general). I'm trying to traversere two many-to-many relationships. Given a parent, how can I get all unique grandchildren?
parent_child_table = Table('parent_child', Base.metadata,
Column('parent_id', Integer, ForeignKey('parent.id')),
Column('child_id', Integer, ForeignKey('child.id'))
)
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=parent_child_table,
backref="parents")
child_grandchild_table = Table('child_grandchild', Base.metadata,
Column('child_id', Integer, ForeignKey('child.id')),
Column('grandchild_id', Integer, ForeignKey('grandchild.id'))
)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
grandchildren = relationship("Grandchild",
secondary=child_grandchild_table,
backref="children")
class Grandchild(Base):
__tablename__ = 'grandchild'
id = Column(Integer, primary_key=True)
Thanks! This problem is giving me a headache...
The most straight-forard way:
# my_parent = ... (instance of Parent)
q = (session.query(Grandchild)
.join(Child, Grandchild.children)
.join(Parent, Child.parents)
.filter(Parent.id == my_parent.id)
)
sqlalchemy will return only unique Grandchild instances (although the SQL query does not filter duplicates out).

Categories

Resources