In SQLAlchemy, how do I create a unique pair? - mysql

class PostsSubscribe(Base):
__tablename__ = 'posts_subscribe'
id = Column(Integer, primary_key = True)
post_id = Column(Integer, ForeignKey('posts_posts.id'), nullable=False)
persona_id = Column(Integer, ForeignKey('personas_personas.id'), nullable=False)
UniqueConstraint('post_id', 'persona_id') #this doesn't work.
Base.metadata.create_all(engine)
This is my table so far. As you can see, I'm using the "Declorative" way of defining tables. I want to create a unique key , but my line doesn't work.
How do I create a unique pair?

UniqueConstraint should be not for a model class, but for its table. You can you __table_args__ to do that:
class PostsSubscribe(Base):
__tablename__ = 'posts_subscribe'
id = Column(Integer, primary_key = True)
post_id = Column(Integer, ForeignKey('posts_posts.id'), nullable=False)
persona_id = Column(Integer, ForeignKey('personas_personas.id'), nullable=False)
__table_args__ = (UniqueConstraint('post_id', 'persona_id', name='_person_post_uc'),
)

Related

Fastapi delete only one record in many to many table

I am building a Fastapi application that uses SQLAlchemy, and I am trying to implement a many-to-many relationship. My problem is when I try to delete a record in intermediate table it deletes all records.
Example of table 'device_protocol'
device_id
protocol_id
status_id
1
1
1
1
2
1
1
3
3
If I try to remove only device_id=1 with protocol_id=2 it actually removes all records with device_id=1
Models:
class DeviceProtocolAssociation(Base):
__tablename__ = "device_protocol"
device_id = Column(Integer, ForeignKey("device.id", ondelete="CASCADE"), primary_key=True)
device = relationship("Device", back_populates="device_protocols")
protocol_id = Column(Integer, ForeignKey("protocol.id"), primary_key=True)
protocol = relationship("Protocol", back_populates="device_protocols")
status_id = Column(Integer, ForeignKey("status.id"), nullable=True)
status = relationship("Status", back_populates="device_protocols")
class Device(Base):
__tablename__ = "device"
id = Column(Integer, primary_key=True, unique=True, index=True)
name = Column(String(255))
status_id = Column(Integer, ForeignKey('status.id'))
status = relationship("Status", back_populates="devices")
device_protocols = relationship(DeviceProtocolAssociation, back_populates="device")
protocols = association_proxy("device_protocols", "protocols")
class Protocol(Base):
__tablename__ = "protocol"
id = Column(Integer, primary_key=True, unique=True, index=True)
name = Column(String(255))
device_protocols = relationship(DeviceProtocolAssociation, back_populates="protocol")
devices = association_proxy("device_protocols", "devices")
class Status(Base):
__tablename__ = "status"
id = Column(Integer, primary_key=True, unique=True, index=True)
name = Column(String(255))
description = Column(String(255), nullable=True)
devices = relationship("Device", back_populates="status")
device_protocols = relationship(DeviceProtocolAssociation, back_populates="status")
Router:
#router.delete('/{device_id}/{protocol_id}')
async def delete_status(device_id: int, protocol_id: int, db:Session=Depends(get_db)):
relation_query = db.query(DeviceProtocolAssociation).filter(DeviceProtocolAssociation.device_id==device_id and DeviceProtocolAssociation.protocol== protocol_id)
db_relation = relation_query.first()
if not db_relation:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=f'No relation with this id: {id} found')
relation_query.delete(db_relation)
db.commit()
return {"relation": "deleted"}
How can I remove only a record?
You can't use python's and when filtering in SQLAlchemy. You can either pass several conditions to the filter
relation_query = db.query(DeviceProtocolAssociation).filter(
DeviceProtocolAssociation.device_id == device_id,
DeviceProtocolAssociation.protocol == protocol_id,
)
or use the binary and-operator &:
relation_query = db.query(DeviceProtocolAssociation).filter(
(DeviceProtocolAssociation.device_id == device_id)
& (DeviceProtocolAssociation.protocol == protocol_id)
)
Solution to my problem is:
relation_query = db.query(DeviceProtocolAssociation).filter(DeviceProtocolAssociation.device_id==device_id, DeviceProtocolAssociation.protocol_id==protocol_id)

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.

SQLAlchemy conditional many to many relationship with an additional column

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?

sqlalchemy filter by data from other relationship table

I have user table:
class User(db.Model):
__tablename__ = "users"
UserID = db.Column(db.Integer, primary_key=True, autoincrement=True)
FirstName = db.Column(db.String(255), nullable=False)
# relationships
Emails = db.relationship('Email', backref='user', lazy='dynamic')
with emails table:
class Email(db.Model):
__tablename__ = "user_emails"
Email = db.Column(db.String(255), unique=True, primary_key=True, nullable=False)
UserID = db.Column(db.Integer, db.ForeignKey('users.UserID'), nullable=False)
how do i filter or find user based on email?
i tried below but it doesnt work:
DBModel.query.filter(DBModel.Emails.any(Email=searchInput))
DBModel.query.filter_by(Emails=searchInput).first()
I solve it with this:
DBModel.query.join(DBModel.Emails).filter_by(Email=id).first()

sqlalchemy python update a primary key with foreign key restraints

I need to change a primary key in multiple tables that also have foreign key restraints. I was thinking to either do an update or to copy the entry with an updated primary key and delete the old entry. How can I accomplish either?
Below is a minimal working set up of my tables. P and S rely on the primary key of S and S relies on the primary key of H.
I need to update the a value of ever table for one entry. I am continually limited on changing the value because of the constraints. I would rather not remove the constraints so this can be a permanent solution for later use.
class H(Base):
__tablename__ = 'ch'
a = Column(String(255), nullable=False, primary_key=True)
other_columns = Column(String(255))
class S(Base):
__tablename__ = 'cs'
a = Column(String(255), ForeignKey('ch.a'), nullable=False, primary_key=True)
sn = Column(Integer, nullable=False, primary_key=True)
other_columns = Column(Date)
header = relationship("H", backref=backref('cs', order_by=a, cascade='delete, all, delete-orphan'), foreign_keys='S.a')
__table_args__ = (UniqueConstraint('a', 'sn', name='_a_sn_uc'),)
class P(Base):
__tablename__ = 'cs_p'
a = Column(String(255), nullable=False, primary_key=True)
sn = Column(Integer, nullable=False, primary_key=True)
ps = Column(Integer, nullable=False, primary_key=True)
other_columns = Column(String(255))
pa = relationship("S", backref=backref('cs_p', order_by=a, cascade='delete, all, delete-orphan'))
__table_args__ = (UniqueConstraint('a', 'sn', 'ps', name='_a_sn_ps_uc'),
ForeignKeyConstraint([a, sn], [S.a, S.sn]),)
class F(Base):
__tablename__ = 'cs_f'
a = Column(String(255), nullable=False, primary_key=True)
sn = Column(Integer, nullable=False, primary_key=True)
fs = Column(Integer, nullable=False, primary_key=True)
other_columns = Column(String(255))
fa = relationship("S", backref=backref('cs_f', order_by=a, cascade='delete, all, delete-orphan'))
__table_args__ = (UniqueConstraint('a', 'sn', 'fs', name='_a_sn_fs_uc'),
ForeignKeyConstraint([a, sn], [S.a, S.sn]),)