SQLAlchemy - how to make column unique per ForeignKey relationship? - sqlalchemy

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.

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 one-to-one relation real example

I'm a novice in sqlalchemy. I read a lot of guidelines but failed to reproduce it.
I Need to make relation 1-to-1 and I'm failed (every user has a related meta table to distinguish between data and info).
This is my current broken code.
class UserData(Base):
__tablename__ = "users"
user_id = Column(Integer, primary_key=True, index=True)
age = Column(Integer, index=True)
first_name = Column(String, index=True)
last_name = Column(String, index=True)
country = Column(String, index=True)
city = Column(String, index=True)
comment = Column(String, index=True)
users_meta = relationship("UserMeta", uselist=False, back_populates="users")
class UserMeta(Base):
__tablename__ = "users_meta"
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
user_id = Column(Integer, ForeignKey('users.user_id'))
users = relationship("UserData", back_populates="users")
The code is almost correct, it needs two changes:
UserMeta needs a primary key column
id = Column(Integer,primary_key=True)
The relationship definition in UserMeta should refer to UserData's users_meta attribute, not users, which does not exist.
users = relationship("UserData", back_populates="users_meta")
One minor point - there's no need for index=True in UserData.id, primary keys will be indexed automatically.

SqlAlchemy error: Foreign key could not find table

So I'm building an app and I'm trying to save new changes to my database but when I try to commit the changes in the flask using db.session.commit() it returns me the following error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'products.country_id' could not find table 'countries' with which to generate a foreign key to target column 'id'
In my models.py I have the following:
from app import db
from . import db
from datetime import datetime
def now():
return datetime.now()
class Countries(db.Model):
__tablename__ = 'countries'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
code = db.Column(db.String(45))
def __repr__(self):
return f'Id {self.id}'
class Categories(db.Model):
__tablename__ = 'categories'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
def __repr__(self):
return f'Id {self.id}'
class Brands(db.Model):
__tablename__ = 'brands'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
logo = db.Column(db.String(5000))
feed = db.Column(db.String(5000))
feed_type = db.Column(db.String(45))
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
awinmid = db.Column(db.Integer)
def __repr__(self):
return f'Id {self.id}'
class Products(db.Model):
__tablename__ = 'products'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
url = db.Column(db.Text)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
price = db.Column(db.Float)
currency = db.Column(db.String(45))
discount_price = db.Column(db.Float)
shipping = db.Column(db.Float)
brand_id = db.Column(db.Integer, db.ForeignKey('brands.id'))
Am I doing anything wrong when associating a column in products with a foreign key? This is the first time I encounter this error so I'm really lost on what to do right now.
To fix I just added the schema to the db.ForeignKey and it worked
Example:
db.ForeignKey('products_data.countries.id')
PS:
Not my idea. Just wanted to post the answer in case someone visits the post later with the same problem.
Gord Thompson thanks for the help!
First of all, I don t see any table Categories. Secondly, you copy pasted your schema from the Products table into your Countries one.
PS: By default sqlalchemy gives the tables the name of the class (lower cased). So your __tablename__='products' does nothing actually.
EDIT:
The problem with your code lies in how you set the __table_args__ attribute. You assign an object to it, which by their specifications is wrong.
Take a look at the following example and modify your code accordingly
__table_args__ = ({'schema': 'products_data'})
Also for further reference, take a look at this https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/table_config.html

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]),)

In SQLAlchemy, how do I create a unique pair?

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'),
)