SQLAlchemy how to update children when old = [1, 2] new = [2, 3] - python

I have two models:
class Person(Model):
id
name
skills = relationship(Skill)
class Skill(Model):
id
skill
person_id
At the beginning, for example:
jack = Person(name='jack')
jack.skills = [Skill(s) for s in ['python', 'ruby']]
jack.save()
Then, one day, jack lost his skill 'ruby' but earned 'swift'
so his skill is ['python', 'swift'].
My current way of doing this update is:
look for existing skills, i get old = ['python', 'ruby']
get the new list new = ['python', 'swift']
make old, new to set(old), set(new)
unchanged = old.intersection(new), so i get the skill that does not change
I add every skill in set(new - unchanged)
I delete every skill in set(old-unchanged)
Is there a easier way to do this?

Use collection_class=set on the relationship to treat it as a set instead of a list.
Here's a working example of how to relate people with skills. This is a many-to-many relationship, instead of each skill being related to one person_id, each skill can be related to many people through the person_skill table. The relationship collection is a set, and Skill has a __hash__ function to make skills with the same name hash the same.
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base(bind=engine)
Session = sessionmaker(bind=engine)
session = Session()
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
# many-to-many relation, as a set
skills = relationship('Skill', 'person_skill', collection_class=set)
class Skill(Base):
__tablename__ = 'skill'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
def __hash__(self):
# so that the set collection will handle duplicate entries
return hash((self.__class__, self.name))
# many-to-many table, relate a person to a skill
person_skill = Table(
'person_skill', Base.metadata,
Column('person_id', Integer, ForeignKey(Person.id), primary_key=True),
Column('skill_id', Integer, ForeignKey(Skill.id), primary_key=True)
)
# create the tables
Base.metadata.create_all()
# populate some skills and people
s1 = Skill(name='python')
s2 = Skill(name='sqlalchemy')
s3 = Skill(name='questions')
s4 = Skill(name='ruby')
p1 = Person(name='davidism', skills={s1, s2, s4})
p2 = Person(name='user2653947', skills={s3})
session.add_all([p1, p2])
session.commit()
# change some skills on people
p1.skills.discard(s4)
p2.skills.add(s2)
session.commit()
This is not a complete solution. You could for instance plug in the unique object pattern demonstrated in this answer to make sure the skills you create are never duplicated.

Related

Self referencing many-to-many relationship with extra column in association object

I am new in Sqlalchemy and trying to achieve the following goal with relationship():
There is an User table which stores user data.
Every user is able to invite other user with an invite_code.
Every user keeps a list of invitation, every invitation includes the invite_code and the invitee User
I think the relationship between User and Invitation is one-to-many. Since Invitation contains User, then I think it is probably better to use self-referential relationship to represent the inviter-to-invitaions(invitees) relationship and use an association object to store the invite_code.
I checked the sqlalchemy documentation and the question, tried to implement the classed like this:
from sqlalchemy import Column, Integer, ForeignKey, create_engine, String
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Invitation(Base):
__tablename__ = 'invitation'
invite_code = Column(Integer)
inviter_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
invitee_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
invitee = relationship('User') #Need HELP here
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
inviters = relationship('User',
secondary='invitation',
primaryjoin=id==Invitation.invitee_id,
secondaryjoin=id==Invitation.inviter_id,
backref='invitees')
invitations = relationship('Invitation')# Need HELP here
def __repr__(self):
return f'User: {self.name}'
if __name__ == '__main__':
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(engine)
db = Session()
inviter1 = User(name='inviter1')
inviter2 = User(name='inviter2')
invitee1= User(name='invitee1')
invitee2 = User(name='invitee2')
inviter1.invitees = [invitee1, invitee2]
inviter2.invitees = [invitee1]
db.add(inviter1)
db.add(inviter2)
db.add(invitee1)
db.add(invitee2)
db.commit()
users = db.query(User).all()
for user in users:
print(user)
print(' Inviter: ', user.inviters)
print(' Invitee: ', user.invitees)
print()
If the lines with comment #Need HELP here are deleted, I can get the corresponding inviters and invitees, but cannot get the invite_code. If the #Need HELP here code are added, the error is:
Exception has occurred: AmbiguousForeignKeysError
Could not determine join condition between parent/child tables on relationship Invitation.invitee - 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.
Is there a way to add extra data column in association object like association object for many-to-many relationship for self referential table?
Sorry for the too much text, I didn't find any reference document on the web.
Finally, I figured it out with the help of foreign_keys:
from sqlalchemy import Column, Integer, ForeignKey, create_engine, String
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
sent_invitations = relationship('Invitation', foreign_keys='Invitation.inviter_id', back_populates='inviter', cascade='all, delete')
received_invitations=relationship('Invitation', foreign_keys='Invitation.invitee_id', back_populates='invitee', cascade='all, delete')
def __repr__(self):
return f'User: {self.name}'
class Invitation(Base):
__tablename__ = 'invitation'
id = Column(Integer, primary_key=True)
invite_code = Column(Integer)
inviter_id = Column(Integer, ForeignKey('user.id'))
invitee_id = Column(Integer, ForeignKey('user.id'))
inviter=relationship('User', foreign_keys=[inviter_id], back_populates='sent_invitations')
invitee=relationship('User', foreign_keys=[invitee_id], back_populates='received_invitations')
def __repr__(self):
return f'Invitation: {self.inviter} invited {self.invitee} with {self.invite_code}'
if __name__ == '__main__':
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(engine)
db = Session()
inviter1 = User(name='inviter1')
inviter2 = User(name='inviter2')
invitee1= User(name='invitee1')
invitee2 = User(name='invitee2')
invitation1 = Invitation(invite_code=50, inviter=inviter1, invitee=invitee1)
invitation2 = Invitation(invite_code=20, inviter=inviter2, invitee=invitee2)
invitation3 = Invitation(invite_code=22, inviter=inviter1, invitee=inviter2)
invitation4 = Invitation(invite_code=44, inviter=invitee1, invitee=inviter2)
db.add(inviter1)
db.add(inviter2)
db.add(invitee1)
db.add(invitee2)
db.commit()
users = db.query(User).all()
for user in users:
print(user)
print(' sent_invitation: ', user.sent_invitations)
print(' received_invitation: ', user.received_invitations)
print()
invitations = db.query(Invitation).all()
for invitation in invitations:
print(invitation)
db.delete(inviter1)
db.delete(invitee2)
db.commit()

sqlalchemy, select objects that has all tags

I have sqlalchemy models:
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, and_
from sqlalchemy.orm import sessionmaker, relationship
engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
Base = declarative_base()
class TopicToPizzaAssociation(Base):
__tablename__ = 'association'
pizza_id = Column(Integer, ForeignKey('pizza.id'), primary_key=True)
topic_id = Column(Integer, ForeignKey('topic.id'), primary_key=True)
topic = relationship("Topic")
pizza = relationship("Pizza")
class Pizza(Base):
__tablename__ = 'pizza'
id = Column(Integer, primary_key=True)
topics = relationship("TopicToPizzaAssociation")
def add_topics(self, topics):
used_topics = {t.topic.product for t in self.topics}
associations = []
for topic in topics:
if topic.product not in used_topics:
associations.append(TopicToPizzaAssociation(pizza=self, topic=topic))
used_topics.add(topic.product)
p1.topics.extend(associations)
class Topic(Base):
__tablename__ = 'topic'
id = Column(Integer, primary_key=True)
product = Column(String(), nullable=False)
I need to select all pizza objects which have the required set of topics:
t1 = Topic(product='t1')
t2 = Topic(product='t2')
t3 = Topic(product='t3')
session = Session()
session.add_all([t1, t2, t3])
p1 = Pizza()
p2 = Pizza()
p1.add_topics([t1, t2, t1])
p2.add_topics([t2, t3])
Base.metadata.create_all(engine)
session.add_all([p1, p2])
session.commit()
values = ['t1', 't2']
topics = session.query(Topic.id).filter(Topic.product.in_(values))
pizza = session.query(Pizza).filter(Pizza.topics.any(TopicToPizzaAssociation.topic_id.in_(
topics
))).all()
This returns all pizza that have one of topics. If I try to replace any with all, it doesn't work.
I've found that it is possible to make a query with JOIN and COUNT, but I couldn't build sqlalchemy query. Any possible solution will suit me.
Firstly, there is a stack of reading you can do about SQLAlchemy relationships in the docs.
Your code closely matches the Association Object pattern which is (from the docs):
...used when your association table contains additional columns beyond those which are foreign keys to the left and right tables
I.e., if there was something specific about the individual relationship between a Pizza and Topic, you would store that information in line with the relationship between foreign keys in the association table. Here's the example that the docs give:
class Association(Base):
__tablename__ = 'association'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
extra_data = Column(String(50))
child = relationship("Child", back_populates="parents")
parent = relationship("Parent", back_populates="children")
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)
parents = relationship("Association", back_populates="child")
Note the column extra_data defined on the Association object.
In your example, there is no such need for an extra_data type field in Association so you can simplify expressing the relationship between Pizza and Topic by using the Many to Many Pattern outlined in the docs.
The main benefit that we can get from that pattern is that we can directly relate the Pizza class to the Topic class. The new models look something like this:
class TopicToPizzaAssociation(Base):
__tablename__ = 'association'
pizza_id = Column(Integer, ForeignKey('pizza.id'), primary_key=True)
topic_id = Column(Integer, ForeignKey('topic.id'), primary_key=True)
class Pizza(Base):
__tablename__ = 'pizza'
id = Column(Integer, primary_key=True)
topics = relationship("Topic", secondary='association') # relationship is directly to Topic, not to the association table
def __repr__(self):
return f'pizza {self.id}'
class Topic(Base):
__tablename__ = 'topic'
id = Column(Integer, primary_key=True)
product = Column(String(), nullable=False)
def __repr__(self):
return self.product
The differences to your original code are:
No relationships defined on the TopicToPizzaAssociation model. With this pattern we can directly relate Pizza to Topic without having relationships on the association model.
Added __repr__() methods to both models so they print nicer.
Removed the add_topics method from Pizza (will explain that more later).
Added the secondary='association' argument to Pizza.topics relationship. This tells sqlalchemy that the foreign key path needed for the relationship to Topic is through the association table.
Here is the testing code and I've put some comments in there:
t1 = Topic(product='t1')
t2 = Topic(product='t2')
t3 = Topic(product='t3')
session = Session()
session.add_all([t1, t2, t3])
p1 = Pizza()
p2 = Pizza()
p1.topics = [t1, t2] # not adding to the pizzas through a add_topics method
p2.topics = [t2, t3]
Base.metadata.create_all(engine)
session.add_all([p1, p2])
session.commit()
values = [t2, t1] # these aren't strings, but are the actual objects instantiated above
# using Pizza.topics.contains
print(session.query(Pizza).filter(*[Pizza.topics.contains(t) for t in values]).all()) # [pizza 1]
values = [t2, t3]
print(session.query(Pizza).filter(*[Pizza.topics.contains(t) for t in values]).all()) # [pizza 2]
values = [t2]
print(session.query(Pizza).filter(*[Pizza.topics.contains(t) for t in values]).all()) # [pizza 2, pizza 1]
So this only returns pizzas that have all of the prescribed topics, but not only the prescribed topics.
The reason I left out your add_topics method is that you used that method to check for duplicate Topics added to a given Pizza. That's fine, but the primary key of the association table won't let you add a duplicate topic for a pizza anyway, so I think it's better to let the database layer manage that and just handle the exception that occurs in application code.
A query to fetch all Pizza with given Topics (and possibly more) can be expressed using a slightly hard to read double negation:
session.query(Pizza).\
filter(~session.query(Topic).
filter(Topic.product.in_(values),
~session.query(TopicToPizzaAssociation).
filter(TopicToPizzaAssociation.topic_id == Topic.id,
TopicToPizzaAssociation.pizza_id == Pizza.id).
correlate(Pizza, Topic).
exists()).
exists())
In English it reads along the lines of "Fetch pizza where not exists a given topic [sic] that is not in this pizza."
This returns all pizza that have one of topics. If I try to replace any with all, it doesn't work.
SQL does not have universal quantification and so there is no all() operator for relationships the way that any() maps to EXISTS. But
FORALL x ( p ( x ) )
is logically equivalent to
NOT EXISTS x ( NOT p ( x ) )
which the above query takes advantage of. It is also described as how to perform relational division in SQL:
https://www.red-gate.com/simple-talk/sql/t-sql-programming/divided-we-stand-the-sql-of-relational-division/
https://www.jooq.org/doc/3.11/manual/sql-building/table-expressions/relational-division/
http://www.inf.usi.ch/faculty/soule/teaching/2016-fall/db/division.pdf

Python / SQLAlchemy: How to delete records IN allocation tables?

below I have brought you an executable program. There are comments in this program to make the situation easier to understand. Please read the comments. Well What I want? I want the porgram to delete/remove only record(s) in an allocation table (Allocation_Film_Genre) - not rows in the tables like Film or Genre. As you can see, in the example I assigned the movie "Saw" to the genre "Comedy". A (deliberate) mistake. Now I want to resolve this constellation, but I don't want to delete "comedy" and "Saw" from the database, but only the assignment. But how?
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship, backref
sqlite_url = 'sqlite:///test.sqlite'
engine = sqlalchemy.create_engine(sqlite_url, echo = True)
Base = declarative_base()
class Allocation_Film_Genre(Base):
__tablename__ = 'allocation_film_genre'
genre_id = Column(Integer, ForeignKey('genre.id'), primary_key=True)
film_id = Column(Integer, ForeignKey('film.id'), primary_key=True)
genre = relationship("Genre", backref=backref("allocation_film_genre", lazy='dynamic', cascade="all, delete-orphan" ))
film = relationship("Film", backref=backref("allocation_film_genre", lazy='dynamic', cascade="all, delete-orphan" ))
class Film(Base):
__tablename__ = 'film'
id = Column(Integer, primary_key=True, unique=True)
name = Column(String(80))
class Genre(Base):
__tablename__ = 'genre'
id = Column(Integer, primary_key=True, unique=True)
name = Column(String(80))
# Let us create all tables with certain columns
Base.metadata.create_all(engine)
# Now we have to create a session to work with.
Session = sessionmaker(bind=engine)
session = Session()
# We want to save some movies
film1 = Film(name="Saw")
film2 = Film(name="Amageddon")
film3 = Film(name="Little Stuart")
film4 = Film(name="Doom Day")
session.add_all([film1, film2, film3, film4])
session.commit()
# By the way we also want to save some genres
genre1 = Genre( name = "Horror")
genre2 = Genre( name = "Comedy")
genre3 = Genre( name = "Psycho")
genre4 = Genre( name = "Thriller")
session.add_all([genre1, genre2, genre3, genre4])
session.commit()
# Hold on, we know we created an allocation table, because
# one movie can contains one or more genre, otherwise, one genre
# also can contains one or more movie, right? Let take us a look.
# For simulate we use the movie named 'Saw".
film_obj1 = session.query(Film).filter(Film.name=="Saw").one()
genre_obj1 = session.query(Genre).filter(Genre.name=="Horror").one()
film_obj2 = session.query(Film).filter(Film.name=="Saw").one()
genre_obj2 = session.query(Genre).filter(Genre.name=="Psycho").one()
film_obj3 = session.query(Film).filter(Film.name=="Saw").one()
genre_obj3 = session.query(Genre).filter(Genre.name=="Comedy").one()
allocation1 = Allocation_Film_Genre(film=film_obj1, genre=genre_obj1)
allocation2 = Allocation_Film_Genre(film=film_obj2, genre=genre_obj2)
allocation3 = Allocation_Film_Genre(film=film_obj3, genre=genre_obj3)
session.add_all([allocation1, allocation2, allocation3])
session.commit()
# Ok, we are done. Alle movies and genre are saved, and we also saved all
# allocation records. But wait! There is a mistake. Saw isn't a comedy. Damn!
# Shame on me!
# And now, I don't know what I have to do.
from sqlalchemy import and_
allocation_obj_to_delete _list=session.query(Allocation_Film_Genre).join(Film).join(Genre).filter(and_(Film.name=='Saw',Genre.name=="Comedy")).all()
for obj in allocation_obj_to_delete:
session.delete(obj)
session.commit()
Joined query and delete the objects

Flask-SqlAlchemy Many-To-Many relationship with duplicates allowed

I have an Order - FoodItem Many-To-Many relationship that is as follows:
association_table = db.Table('association', db.Model.metadata,
db.Column('left_id', db.Integer, db.ForeignKey('orders.order_id')),
db.Column('right_id', db.Integer, db.ForeignKey('fooditems.fooditem_id'))
)
class OrderModel(ReviewableModel):
__tablename__ = 'orders'
order_id = db.Column(db.Integer, db.ForeignKey('reviewables.id'), primary_key=True)
food_items = db.relationship("FoodItemModel", secondary = association_table)
__mapper_args__ = {'polymorphic_identity':'orders'}
class FoodItemModel(ReviewableModel):
__tablename__ = 'fooditems'
fooditem_id = db.Column(db.Integer, db.ForeignKey('reviewables.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity':'fooditems'}
The user can request an order with duplicate foodItems. This is created properly, but when I save the changes to the database, the duplicates are removed. For e.g., I order 3 Pizzas:
def save_to_db(self):
print('before: '+str(self.food_items))
db.session.add(self)
db.session.commit()
print('after: '+str(self.food_items))
The output is like this:
before: [<FoodItemModel u'Pizza'>, <FoodItemModel u'Pizza'>, <FoodItemModel u'Pizza'>]
after: [<FoodItemModel u'Pizza'>]
The association table is updated properly:
"left_id" "right_id"
"6" "3"
"6" "3"
"6" "3"
However, the food_items in the OrderModel only contains 1 item
What Juan Mellado was getting at in his answer is that the relational data (RD) and object relational mapping (ORM) clash: the ORM cannot distinguish separate objects that have the same data. To solve this, simply add an id column as primary key to the association_table - that way the ORM has something to distinguish different records with the same left_id and right_id.
But that would be a workaround and not a solution.
The solution is in thinking about what it means when "The user can request an order with duplicate foodItems". The relation from the order to the food is not direct, it is indirect via an order-item. Each order-item belongs to an order (which in turn belongs to a customer or a dining-table) and each order-item can have a relation with a food item. By making each order-item unique, the problem of "duplicate food-items" disappears. At the same time, we can now have an infinite amount of variations of the food-item by adding an optional "customer request" to each order item. E.g. "food: fries, request: easy on the salt".
Below a demonstration in code where customer "I scream" places 1 order with 3 portions of "ice cream" of which 1 portion is "with sprinkles on top".
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.pool import StaticPool
Base = declarative_base()
class Order(Base):
__tablename__ = 'order'
id = Column(Integer, primary_key=True)
customer = Column(String(127))
items = relationship("OrderItem")
def __repr__(self):
return "<Order(id='{}', customer='{}', items='{}')>".format(self.id, self.customer, self.items)
class Food(Base):
__tablename__ = 'food'
id = Column(Integer, primary_key=True)
name = Column(String(127))
def __repr__(self):
return "<Food(id='{}', name='{}')>".format(self.id, self.name)
class OrderItem(Base):
__tablename__ = 'order_item'
id = Column(Integer, primary_key=True)
order_id = Column(Integer, ForeignKey(Order.id))
order = relationship(Order)
food_id = Column(Integer, ForeignKey(Food.id))
food = relationship(Food)
comment = Column(String(127))
def __repr__(self):
return "<OrderItem(id='{}', order_id='{}', food_id='{}, comment={}')>" \
.format(self.id, self.order_id, self.food_id, self.comment)
def orderFood():
engine = create_engine('sqlite:///:memory:', echo=True, connect_args={'check_same_thread':False}, poolclass=StaticPool)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
food = Food(name='ice cream')
session.add(food)
order = Order(customer='I scream')
session.add(order)
session.commit()
print("Food: {}".format(food))
print("Order: {}".format(order))
order.items = [OrderItem(order=order, food=food), OrderItem(order=order, food=food), \
OrderItem(order=order, food=food, comment='with sprinkles on top')]
session.merge(order)
session.commit()
print("Order: {}".format(order))
print("Order.items")
for item in order.items:
print(item)
print("OrderItems for order")
orderFilter = OrderItem.order_id == order.id
for order_item in session.query(OrderItem).filter(orderFilter).all():
print(order_item)
print("Food in order")
for row in session.query(Food).join(OrderItem).filter(orderFilter).all():
print(row)
session.close();
if __name__ == "__main__":
orderFood()
You must declare a primary key for the association table.
Flask-SQLALchemy is an ORM, and it needs a series of columns that uniquely identify a row.
Take a look to this part of the documentation, a bit outdated, but still valid:
http://docs.sqlalchemy.org/en/rel_1_1/faq/ormconfiguration.html#faq-mapper-primary-key
Flask-SQLALchemy is using all the fields (left_id, right_id) to identify the rows, and all the rows have the same values (6, 3). So, all the rows are stored in the database (as there are not any declared constraint on it), but only one is retained in the context (memory).

Populating a SQLAlchemy many-to-many relationship using ID's instead of objects

The situation:
So, I have a basic many-to-many relationship in SQLAlchemy using an association table.
For example, a person can attend many parties, and a party can have many persons as guests:
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = db.Column(db.String(50))
class SexyParty(Base):
__tablename__ = 'sexy_party'
id = Column(Integer, primary_key=True)
guests = relationship('Person', secondary='guest_association',
lazy='dynamic', backref='parties')
guest_association = Table(
'guest_association',
Column('user_id', Integer(), ForeignKey('person.id')),
Column('sexyparty.id', Integer(), ForeignKey('sexyparty.id'))
)
Normally if I wanted to add a list of guests to a party, I would do something like this:
my_guests = [prince, olivia, brittany, me]
my_party.guests = guests
db.session.commit()
...where prince, olivia and brittany are all <Person> instances, and my_party is a <SexyParty> instance.
My question:
I'd like to add guests to a party using person ID's rather than instances.
For example:
guest_ids = [1, 2, 3, 5]
my_party.guests = guest_ids # <-- This fails, because guest_ids
# are not <Person> instances
I could always load the instances from the databases, but that would entail an unnecessary DB query just to set a simple many-to-many relationships.
How would I go about setting the .guests attribute using a list of person_id's?
There has to be a simple way to do this since the association table ultimately represents the many-to-many relationship using ID's anyway...
thanks in advance, hope the question is clear.
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String(50))
class SexyParty(Base):
__tablename__ = 'sexy_party'
id = Column(Integer, primary_key=True)
guests = relationship('Person', secondary='guest_association',
lazy='dynamic', backref='parties')
guest_association = Table(
'guest_association', Base.metadata,
Column('user_id', Integer(), ForeignKey('person.id'), primary_key=True),
Column('sexyparty_id', Integer(), ForeignKey('sexy_party.id'), primary_key=True)
)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
sess = Session(e)
p1 = Person(id=1, name='p1')
p2 = Person(id=2, name='p2')
p3 = Person(id=3, name='p3')
p4 = Person(id=4, name='p4')
sp1 = SexyParty(id=1)
sess.add_all([sp1, p1, p2, p3, p4])
sess.commit()
# method one. use insert()
sess.execute(guest_association.insert().values([(1, 1), (2, 1)]))
# method two. map, optional association proxy
from sqlalchemy.ext.associationproxy import association_proxy
class GuestAssociation(Base):
__table__ = guest_association
party = relationship("SexyParty", backref="association_recs")
SexyParty.association_ids = association_proxy(
"association_recs", "user_id",
creator=lambda uid: GuestAssociation(user_id=uid))
sp1.association_ids.extend([3, 4])
sess.commit()

Categories