SQLAlchemy conditional many to many relationship with an additional column - sqlalchemy

This is the way that I usually use for m2m relationship implementation.
(Brought from docs.sqlalchemy.org)
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,
backref="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
Is there any way for using additional columns at the association_table table?
So it should be like
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id')),
Column('is_valid', Boolean, default=True) # Add the validation column
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")
# How can I do implement this??
valid_children = relationship("Child",
secondary="and_(association_table.left_id == Parent.id, association_table.right_id == Child.id)"
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
I want to do query depends on is_valid column. How can I modify "secondary" attr in Parent table? Or should I fix the other part?
In this question, time_create column has the same value for all children. But in this case, I need a flag that makes able to retrieve whether this connection is still alive or not.
For example, if you implement a one-on-one chatting, there will be a chatting room consist of two-person, right?
And the table should be like as below:
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id')),
Column('is_left', Boolean, default=False) # Whether the user left or not
)
class Match(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
user = relationship("User",
secondary=association_table,
backref="matches")
# How can I do implement this??
exist_user = relationship("User",
secondary="and_(association_table.left_id == Parent.id, association_table.right_id == Child.id)"
class User(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
nickname = Column(String, unique=True)
How can I do for this?

Related

How to use Sqlalchemy ORM to just get related ids not the full record of an association in a many-to-many?

Is there a way to just get the related category ids of a many-to-many association - without getting the full related category record?
Code being used currently is:
products = session.query(Product).join(association_table).filter(association_table.c.category_id == category)
for product in products:
print(product.product_id, [item.id for item in product.categories])
With the models:
association_table = Table(
"product_category_association_table",
Base.metadata,
Column("product_id", ForeignKey("product.id"), primary_key=True),
Column("category_id", ForeignKey("category.id"), primary_key=True),
)
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True)
categories = relationship(
"Category", secondary=association_table, back_populates="products"
)
product_id = Column(String(length=50), unique=True)
label = Column(String, nullable=False)
class Category(Base):
__tablename__ = "category"
id = Column(Integer, primary_key=True)
products = relationship(
"Product", secondary=association_table, back_populates="categories"
)
active = Column(Boolean, default=True)
label = Column(String, nullable=False)
This emits the initial SQL to get all products.
Then for each loop is does a query to the related category table (which is not needed as all that is wanted is the category ids).
Is this possible to achieve without the join over to the category table?

NotNullViolation when appending to relationship using an Association Object in SQLAlchemy?

Background
The documentation gives the following example of a parent-child-association being added by appending that association to p.children. The child is then accessed via p.children and I assume the other way is possible as well, i.e accessing the parent via c.parents.
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))
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")
# create parent, append a child via association
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
# iterate through child objects via association, including association
# attributes
for assoc in p.children:
print(assoc.extra_data)
print(assoc.child)
Problem
I have three classes that are almost identical to those in the example:
class TopicSubcription(Base):
__tablename__ = 'topic_subscription'
topic_id = Column(ForeignKey('topic.id'), primary_key=True)
user_id = Column(ForeignKey('user.id'), primary_key=True)
subscription_date = Column('subscription_date', DateTime, nullable=True)
topic = relationship("Topic", back_populates="subscribed_users")
user = relationship("User", back_populates="followed_topics")
class Topic(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
subscribed_users = relationship("Association", back_populates="topic")
class User(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
followed_topics = relationship("Association", back_populates="user")
but when I run the following I get a NotNullViolation error stating that user_id violates not-null constaint.
u: User = session.query(User).filter(User.id == 'example_id1').one()
t: Topic = session.query(Topic).filter(Topic.id == 'example_id2').one()
sub = TopicSubscription(func.now())
sub.topic = t
u.followed_topics.append(sub)
session.add(u)
session.commit()
Question
Am I misinterpreting the documentation or is it wrong?
I had something quite similar just now and was going crazy trying to work it out. Abstracting to the parent/child example from the docs, what worked in my case was:
p = Parent()
a = Association(parent=p, extra_data="some data")
a.child = Child()
p.children.append(a)

SQLAlchemy - how to make column unique per ForeignKey relationship?

In the Todo model below there is a unique constraint put on text column.
How can I narrow this constraint to validate uniqueness per "foreign-keyed" user only, not per all users as it is now?
I use SQLite.
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Todo(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, index=True)
text = Column(String, unique=True)
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="todos")
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
todos = relationship("Todo", back_populates="user")
If you use postgresql, you can use the Partial Index to implement this:
class Todo(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, index=True)
text = Column(String) # ! removed the unique from here
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="todos")
# ! added unique index with
__table_args__ = (
Index(
"todo_text_uc",
"text",
unique=True,
postgresql_where=(user_id != None),
# postgresql_where=(~user_id.is_(None)), # equivalent to the row above, but no linting warnings
),
)
For sqlite just replace postgresql_where with sqlite_where.

In SQLAlchemy, what is the difference between these two models?

#models1.py
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship("Address", backref="user")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey('user.id'))
#models2.py
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
email = Column(String)
user_id = Column(Integer, ForeignKey('user.id'))
addresses = relationship("Address", backref="user")
As you can tell, the only difference is that the "relationship" is placed in a different position. I'm confused, because in the SQLAlchemy documentation, it places it in two different places. First here, then here.
Which is the correct position of "relationship"? And is it even required? What if I leave it out...?
Both are semantically identical.
SA uses the ForeignKeys to infer the many side of one-to-many relationship.
Read Linking Relationships with Backref section which explains the bidirectional relationships. Relevant extract:
... In fact, the backref keyword is only a common shortcut for placing a
second relationship onto the Address mapping, including the
establishment of an event listener on both sides which will mirror
attribute operations in both directions. ...

Many-to-many self-referential relationship in sqlalchemy

I'm trying to make a self-referential many-to-many relationship (it means that Line can have many parent lines and many child lines) in sqlalchemy like this:
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
prev_id = Column(Integer, ForeignKey('line.id'), primary_key=True)
next_id = Column(Integer, ForeignKey('line.id'), primary_key=True)
class Line(Base):
__tablename__ = 'line'
id = Column(Integer, primary_key = True)
text = Column(Text)
condition = Column(Text)
action = Column(Text)
next_lines = relationship(Association, backref="prev_lines")
class Root(Base):
__tablename__ = 'root'
name = Column(String, primary_key = True)
start_line_id = Column(Integer, ForeignKey('line.id'))
start_line = relationship('Line')
But I get the following error:
sqlalchemy.exc.ArgumentError: Could not determine join condition between parent/
child tables on relationship Line.next_lines. Specify a 'primaryjoin' expressio
n. If 'secondary' is present, 'secondaryjoin' is needed as well.
Do you know how I could remedy this?
You should just need:
prev_lines = relationship(
Association,
backref="next_lines",
primaryjoin=id==Association.prev_id)
Since this specifies the next_lines back reference there is no need to have a next_lines relationship.
You can also do this using the remote_side parameter to a relationship: http://www.sqlalchemy.org/trac/browser/examples/adjacency_list/adjacency_list.py