I have a many-to-many relationship similar to the one described here. Notice my Association table includes an extra_data field..
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary="association", back_populates="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Parent", secondary="association", back_populates="children")
If I want to fetch a particular parent object with its children, I can do
db_parent = db.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].id) # works fine
BUT, the extra_data field is not included as an attribute of the children.
print(db_parent.children[0].extra_data)
AttributeError: 'Child' object has no attribute 'extra_data'
How can I write fetch the children of a parent such that extra_data is included as an attribute?
Fully Working Example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary="association", back_populates="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Parent", secondary="association", back_populates="children")
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add parents
p1 = Parent()
session.add(p1)
p2 = Parent()
session.add(p2)
session.commit()
# add children
c1 = Child()
session.add(c1)
c2 = Child()
session.add(c2)
session.commit()
# map children to parents
a1 = Association(left_id=p1.id, right_id=c1.id, extra_data='foo')
a2 = Association(left_id=p1.id, right_id=c2.id, extra_data='bar')
a3 = Association(left_id=p2.id, right_id=c2.id, extra_data='baz')
session.add(a1)
session.add(a2)
session.add(a3)
session.commit()
with Session(bind=engine) as session:
db_parent = session.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].id)
print(db_parent.children[0].extra_data)
What you are asking can't be done exactly how you want using SQLAlchemy. Indeed, items in Parent.children whould be instances of Child class. If your child class has an extra_data property loaded from an association table, to which of its parent would it refer?
What I'm trying to explain is that this implicit reference to "extra_data" that you would like to have in Child, only makes sense if the Child object is referenced from a parent object.
As an example, imagine the following scenario
session.add_all(
Association(left=parent_a.id, right=child.id, extra_data="hello")
Association(left=parent_b.id, right=child.id, extra_data="world")
)
Which parent metadata would you expect in child.extra_data ?
Moreover most of the time, if you need an object as association table, it means that this object makes sense by itself. And so that you should not try to hide it. Have a look at the following concrete example
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True)
username = Column(String(10), nullable=False)
groups = relationship("Membership", back_populates="account")
class Group(Base):
__tablename__ = "groups"
id = Column(Integer, primary_key=True)
name = Column(String(10), nullable=False)
members = relationship("Membership", back_populates="group")
class Membership(Base):
"""Membership is our association table here"""
__tablename__ = "memberships"
id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey("accounts.id"))
account = relationship("Account", back_populates="groups")
group_id = Column(Integer, ForeignKey("groups.id"))
group = relationship("Group", back_populates="members")
# extra data embed in association table
role = Column(String(10), nullable=False)
Base.metadata.create_all()
# create user "toto" that belongs to group "Funny people" with role "joker"
toto = Account(username="toto")
funny_people = Group(name="Funny people")
session.add(Membership(account=toto, group=funny_people, role="joker"))
session.commit()
Notice the difference between the two approaches. Here, Account.groups contains memberships and not directly Group objects. Then you can use it this way :
toto = session.query(Account).first()
toto.username
toto.groups[0].group.name
toto.groups[0].role
I know this is not exactly what you asked, but this is probably the closest you can have without introducing weird logic that will interfere with the proper functioning of your application
Thanks to #van for introducing me to SQLAlchemy's AssociationProxy. With AssociationProxy, I can almost get what I want, but it's still not ideal.
The idea here is to create three tables / classes as usual:
left (Parent)
right (Child)
association (Association)
Then I give Parent a children relationship attribute. I also give Association a parent and a child relationship attribute.
Lastly, I set up association proxies inside Association so that it "carries" all the stuff its related child object has that I want. Here's a working example
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
from sqlalchemy.ext.associationproxy import association_proxy
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=True)
# Make the DeclarativeMeta
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
parent = relationship("Parent", back_populates="children")
child = relationship("Child")
extra_data = Column(String(50))
# Association proxies
child_name = association_proxy("child", "name")
child_weight = association_proxy("child", "weight")
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Association", back_populates="parent")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
weight = Column(Float, nullable=False)
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add parents
p1 = Parent()
session.add(p1)
p2 = Parent()
session.add(p2)
session.commit()
# add children
c1 = Child(name = "A", weight = 5)
session.add(c1)
c2 = Child(name = "B", weight = 3)
session.add(c2)
session.commit()
# map children to parents
a1 = Association(left_id=p1.id, right_id=c1.id, extra_data='foo')
a2 = Association(left_id=p1.id, right_id=c2.id, extra_data='bar')
a3 = Association(left_id=p2.id, right_id=c2.id, extra_data='baz')
session.add(a1)
session.add(a2)
session.add(a3)
session.commit()
Now if I fetch a parent instance, I can reference parent.children which returns a list of children with all the attributes I need.
with Session(bind=engine) as session:
db_parent = session.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].extra_data)
print(db_parent.children[0].child_name)
print(db_parent.children[0].child_weight)
Technically though, parent.children is returning a list of Associations where each association is acquiring attributes from its related Child instance via my association proxies. A drawback to this is that I have to label these attributes child_name and child_weight as opposed to simply name and weight, otherwise if I decided to set up the reverse relationship, it won't be obvious that name and weight are attributes of the child and not the parent.
Another solution I came up with is to define a read-only property of Parent called children which merely executes the SQL query required to fetch the exact data I need.
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey
from sqlalchemy.orm import declarative_base, Session, object_session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=True)
# Make the DeclarativeMeta
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
left_id = Column(ForeignKey('left.id'), primary_key=True)
right_id = Column(ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
#property
def children(self):
s = """
SELECT foo.* FROM (
SELECT
right.*,
association.extra_data,
association.left_id
FROM right INNER JOIN association ON right.id = association.right_id
) AS foo
INNER JOIN left ON foo.left_id = left.id
WHERE left.id = :leftid
"""
result = object_session(self).execute(s, params={'leftid': self.id}).fetchall()
return result
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
weight = Column(Float, nullable=False)
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add parents
p1 = Parent()
session.add(p1)
p2 = Parent()
session.add(p2)
session.commit()
# add children
c1 = Child(name = "A", weight = 5)
session.add(c1)
c2 = Child(name = "B", weight = 3)
session.add(c2)
session.commit()
# map children to parents
a1 = Association(left_id=p1.id, right_id=c1.id, extra_data='foo')
a2 = Association(left_id=p1.id, right_id=c2.id, extra_data='bar')
a3 = Association(left_id=p2.id, right_id=c2.id, extra_data='baz')
session.add(a1)
session.add(a2)
session.add(a3)
session.commit()
Usage
with Session(bind=engine) as session:
db_parent = session.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].extra_data) # foo
print(db_parent.children[0].name) # A
print(db_parent.children[0].weight) # 5.0
Related
Using SQLAlchemy I have three models: Parent1, Parent2, and Child, where Parent1 has one-to-one relationship with Parent2, and both of them has the same relationship with Child. Here are they:
from extensions import db_session
class Parent1(Base):
__tablename__ = 'parent1'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent2 = relationship("Parent2", backref="parent1", uselist=False)
child = relationship("Child", backref="parent1", uselist=False)
class Parent2(Base):
__tablename__ = 'parent2'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent1_id = Column(Integer, ForeignKey('parent1.id'))
child = relationship("Child", backref="parent2", uselist=False)
class Child(Base):
id = Column(Integer, primary_key=True)
parent1_id = Column(Integer, ForeignKey('parent1.id'))
parent2_id = Column(Integer, ForeignKey('parent2.id'))
What I am trying to achieve is to fill Child table with its parents' foreign keys.
So, when I execute this:
parent1 = Parent1(name="Adil")
parent1.parent2 = Parent2(name="Aisha")
parent1.child = Child()
db_session.add(parent1)
db_session.commit()
to the parents tables it inserts data as needed, however to the Child table it inserts data like this:
Child
id parent1_id parent2_id
1 1 NULL
How to properly set relationships, so that on any insert to the Parent1->Parent2 tables it also inserts its ids as foreign keys to Child table?
What I want to achieve is:
Child
id parent1_id parent2_id
1 1 1
I came up with this solution. I removed declared model Child and created association table between two models and added secondary parameter filling with assocation table name:
Base = declarative_base()
child = Table('child', Base.metadata,
Column('parent1_id', Integer, ForeignKey('parent1.id')),
Column('parent2_id', Integer, ForeignKey('parent2.id')))
class Parent1(Base):
__tablename__ = 'parent1'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent2 = relationship("Parent2", secondary='child', uselist=False)
class Parent2(Base):
__tablename__ = 'parent2'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent1_id = Column(Integer, ForeignKey('parent1.id'))
I have some models that I am trying to search, so I am looking to show return a result of all Parent objects, where either the Parent name is "foo" or the Child name is "foo".
I have the query:
parents = Session.query(Parent).\
join(Child_s3).\
filter(Parent.name.ilike("%foo%")).\
filter(Child_s3.name.ilike("%foo%")).\
order_by(asc(Product.name))
And the models:
class Parent(BaseSO):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(Unicode(100), nullable=False, unique=True)
colours = relationship('Child_s3', secondary=Parent_images, backref='Parentc')
class Child_s3(BaseSO):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(Unicode)
Parent_images = Table(
'Parent_images', BaseSO.metadata,
Column('parent_id', Integer, ForeignKey('parents.id')),
Column('child_id', Integer, ForeignKey('children.id'))
)
The query I have shows parents with the name of "foo" but does not show any parent objects, that also have children called "foo", can anyone help build this query to search both tables for the corresponding parent objects?
This code shows how to get the result using either an explicit join or a subquery:
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
Parent_images = sa.Table(
'Parent_images', Base.metadata,
sa.Column('parent_id', sa.Integer, sa.ForeignKey('parents.id')),
sa.Column('child_id', sa.Integer, sa.ForeignKey('children.id'))
)
class Parent(Base):
__tablename__ = 'parents'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode(100), nullable=False, unique=True)
colours = orm.relationship('Child_s3', secondary=Parent_images, backref='parents')
def __repr__(self):
return 'Parent(name=%s)' % self.name
__str__ = __repr__
class Child_s3(Base):
__tablename__ = 'children'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.Unicode)
def __repr__(self):
return 'Child_s3(name=%s)' % self.name
__str__ = __repr__
if __name__ == '__main__':
engine = sa.create_engine('sqlite:///')
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
Session = orm.sessionmaker(bind=engine)
session = Session()
for parent, child in [('boofoo', 'spam'), ('baz', 'foobar'), ('bar', 'quux')]:
p1 = Parent(name=parent)
session.add(p1)
p1.colours.append(Child_s3(name=child))
session.commit()
print('Join')
session = Session()
q = (session.query(Parent)
.join(Child_s3, Parent.colours)
.filter(sa.or_(Parent.name.ilike('%foo%'),
Child_s3.name.ilike('%foo%'))))
for p in q.all():
print(p, p.colours)
session.commit()
print()
print('Subquery')
session = Session()
q = (session.query(Parent)
.filter(sa.or_(Parent.name.ilike('%foo%'),
Parent.colours.any(Child_s3.name.ilike('%foo%')))))
for p in q.all():
print(p, p.colours)
session.commit()
print()
The join query
q = (session.query(Parent)
.join(Child_s3, Parent.colours)
.filter(sa.or_(Parent.name.ilike('%foo%'),
Child_s3.name.ilike('%foo%'))))
generates this SQL
SELECT parents.id AS parents_id, parents.name AS parents_name
FROM parents JOIN "Parent_images" AS "Parent_images_1" ON parents.id = "Parent_images_1".parent_id JOIN children ON children.id = "Parent_images_1".child_id
WHERE lower(parents.name) LIKE lower(?) OR lower(children.name) LIKE lower(?)
The subquery
q = (session.query(Parent)
.filter(sa.or_(Parent.name.ilike('%foo%'),
Parent.colours.any(Child_s3.name.ilike('%foo%')))))
generates this SQL:
SELECT parents.id AS parents_id, parents.name AS parents_name
FROM parents
WHERE lower(parents.name) LIKE lower(?) OR (EXISTS (SELECT 1
FROM "Parent_images", children
WHERE parents.id = "Parent_images".parent_id AND children.id = "Parent_images".child_id AND lower(children.name) LIKE lower(?)))
The script produces this output from the sample data:
Join
Parent(name=baz) [Child_s3(name=foobar)]
Parent(name=boofoo) [Child_s3(name=spam)]
Subquery
Parent(name=boofoo) [Child_s3(name=spam)]
Parent(name=baz) [Child_s3(name=foobar)]
I am trying to update a simple 3 layer relational set of tables.
They are
Parent
Child
GrandChild
The SQLAlchemy code for the model looks like
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship("Child", back_populates="parents")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parent.id'))
parents = relationship("Parent", back_populates="children")
grandchildren = relationship("GrandChild",
back_populates="grandparent",
)
class GrandChild(Base):
__tablename__ = 'grandchild'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parent.id'))
child_id = Column(Integer, ForeignKey('child.id'))
grandparent = relationship("Child", back_populates="grandchildren")
And the Insert code looks like this....
p3 = Parent(name="P3")
c5 = Child(name="C5")
c6 = Child(name="C6")
gc1 = GrandChild(name="gc1")
gc2 = GrandChild(name="gc2")
gc3 = GrandChild(name="gc3")
gc4 = GrandChild(name="gc4")
p3.children = [c5, c6]
c5.grandchildren = [gc1]
c6.grandchildren = [gc2, gc3, gc4]
session.add_all([p2, p3])
session.commit()
The record is added - and Parent/Child are correctly linked - but GrandChildren are missing the Parent foreign key.
I have struggled in finding the correct mechanism to add this - can anyone point me in the right direction ?
You don't create the relation between grandchilds and parents.The relationship between a grandchild and a parent isn't implicit in your data model; the parent of a child doesn't automatically become the parent of all the child's grandchildren.
You have to define that relationship explicitly, i.e. add it to the GrandChild:
class GrandChild(Base):
[...]
parent = relationship("Parent")
and then create the relation on the instances:
gc1.parent = p3
gc2.parent = p3
gc3.parent = p3
gc4.parent = p3
This will add the records accordingly:
sqlalchemy.engine.base.Engine INSERT INTO grandchild (name, parent_id, child_id) VALUES (?, ?, ?)
sqlalchemy.engine.base.Engine ('gc1', 1, 1)
[...]
However, since the parent-child relationship in your data model doesn't imply any grandchild-parent relationship, you can create a parent without children, that has grandchildren.
sink = Parent(name="SINK")
gc1.parent = sink
print("Name: {}, Parent: {}, Parent.children: {}, Child.parent: {}"
.format(gc1.name, gc1.parent.name, gc1.parent.children, gc1.grandparent.parents.name))
# Name: gc1, Parent: SINK, Parent.children: [], Child.parent: P3
Based on my understanding of a three-tier-relation, I can't think of a use case where sth. like this would find an application.
If you want an implicit and consistent relationship between an parent and a grandchild through a child, drop the direct relationship between parent and grandchild:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship("Child", back_populates="parent")
def __repr__(self):
return "{}(name={})".format(self.__class__.__name__, self.name)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="children")
children = relationship("GrandChild", back_populates="parent")
# same __repr__()
class GrandChild(Base):
__tablename__ = 'grandchild'
id = Column(Integer, primary_key=True)
name = Column(String)
child_id = Column(Integer, ForeignKey('child.id'))
parent = relationship("Child", back_populates="children")
# same __repr__()
p3 = Parent(name="P3")
c5 = Child(name="C5")
gc1 = GrandChild(name="gc1")
p3.children = [c5]
c5.children = [gc1]
You can access the grandchild's grandparent through:
print(gc1.parent.parent)
# Parent(name=P3)
The other way around is a bit more tedious though, due to the two one-to-many relationships in the hierarchy:
for child in p3.children:
for gc in child.children:
print(p3, child, gc)
# Parent(name=P3) Child(name=C5) GrandChild(name=gc1)
Two declarative classes which have a parent child relationship, the youngest child is the most important child and thus a youngest_child_id column would be useful.
In this there are two relationships - a one to one from parent to child and a one to many from parent to children, but this creates multiple join paths
Something like the below:
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
youngest_child_id = Column(Integer, foreign_key='Child.id')
youngest_child = relationship("Child", uselist=False, foreign_keys=[youngest_child_id])
children = relationship("Child", back_populates='parent')
Class Child(Base):
__tablename__ = 'children'
id = id = Column(Integer, primary_key=True)
parent_id = Column(Integer, foreign_key='Parent.id')
parent = relationship("Parent", back_populates='children')
This and a few other variations that I have created raise AmbiguousForeignKeysError:
Exception has occurred: sqlalchemy.exc.AmbiguousForeignKeysError
Could
not determine join condition between parent/child tables on
relationship Parent.children
Where is this going wrong and can this be achieved via the ORM?
You've defined foreign_keys for the youngest_child relationship, but you also have to define it for the children and parent relationships:
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
youngest_child_id = Column(Integer, ForeignKey('children.id'))
youngest_child = relationship("Child", uselist=False, post_update=True,
foreign_keys=[youngest_child_id])
# Pass foreign_keys= as a Python executable string for lazy evaluation
children = relationship("Child", back_populates='parent',
foreign_keys='[Child.parent_id]')
class Child(Base):
__tablename__ = 'children'
id = id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parents.id'))
parent = relationship("Parent", back_populates='children',
foreign_keys=[parent_id])
In addition you must define post_update=True on for example youngest_child in order to break the circular dependency between the models. Without it SQLAlchemy would have to insert both the parent and the child at the same time, if you do something like this:
p = Parent()
c1, c2 = Child(), Child()
p.children = [c1, c2]
p.youngest_child = c1
session.add(p)
session.commit()
With the post update in place SQLAlchemy first inserts to parents, then to children, and then updates the parent with the youngest child.
How to add objects in the constructor with relationship? The id is not yet ready when constructor is evaluated. In simpler cases it is possible to just provide a list, calculated beforehand. In the example below I tried to say there is a complex_cls_method, in a way it is more like black box.
from sqlalchemy import create_engine, MetaData, Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
DB_URL = "mysql://user:password#localhost/exampledb?charset=utf8"
engine = create_engine(DB_URL, encoding='utf-8', convert_unicode=True, pool_recycle=3600, pool_size=10)
session = sessionmaker(autocommit=False, autoflush=False, bind=engine)()
Model = declarative_base()
class User(Model):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
simple = Column(String(255))
main_address = Column(String(255))
addresses = relationship("Address",
cascade="all, delete-orphan")
def __init__(self, addresses, simple):
self.simple = simple
self.main_address = addresses[0]
return # because the following does not work
self.addresses = Address.complex_cls_method(
user_id_=self.id, # <-- this does not work of course
key_="address",
value_=addresses
)
class Address(Model):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
keyword = Column(String(255))
value = Column(String(255))
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
parent_id = Column(Integer, ForeignKey('address.id'), nullable=True)
#classmethod
def complex_cls_method(cls, user_id_, key_, value_):
main = Address(keyword=key_, value="", user_id=user_id_, parent_id=None)
session.add_all([main])
session.flush()
addrs = [Address(keyword=key_, value=item, user_id=user_id_, parent_id=main.id) for item in value_]
session.add_all(addrs)
return [main] + addrs
if __name__ == "__main__":
# Model.metadata.create_all(engine)
user = User([u"address1", u"address2"], "simple")
session.add(user)
session.flush()
# as it can't be done in constructor, these additional statements needed
user.addresses = Address.complex_cls_method(
user_id_=user.id,
key_="address",
value_=[u"address1", u"address2"]
)
session.commit()
The question is, is there syntactically elegant (and technically sound) way to do this with User's constructor, or is it safer to just call a separate method of User class after session.flush to add desired objects to relationships (as in the example code)?
Giving up on constructor altogether is still possible, but less desirable option as resulting signature change would require significant refactorings.
Instead of manually flushing and setting ids etc. you could let SQLAlchemy handle persisting your object graph. You'll just need one more adjacency list relationship in Address and you're all set:
class User(Model):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
simple = Column(String(255))
main_address = Column(String(255))
addresses = relationship("Address",
cascade="all, delete-orphan")
def __init__(self, addresses, simple):
self.simple = simple
self.main_address = addresses[0]
self.addresses = Address.complex_cls_method(
key="address",
values=addresses
)
class Address(Model):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
keyword = Column(String(255))
value = Column(String(255))
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
parent_id = Column(Integer, ForeignKey('address.id'), nullable=True)
# For handling parent/child relationships in factory method
parent = relationship("Address", remote_side=[id])
#classmethod
def complex_cls_method(cls, key, values):
main = cls(keyword=key, value="")
addrs = [cls(keyword=key, value=item, parent=main) for item in values]
return [main] + addrs
if __name__ == "__main__":
user = User([u"address1", u"address2"], "simple")
session.add(user)
session.commit()
print(user.addresses)
Note the absence of manual flushes etc. SQLAlchemy automatically figures out the required order of insertions based on the object relationships, so that dependencies between rows can be honoured. This is a part of the Unit of Work pattern.