FASTAPI adn SQLAlchemy relationship schemas - sqlalchemy

I need a little help on formatting. I got my code to work, however, working on output formatting. I'd like it to look a little cleaner. Here's my code:
Models:
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
class Manufacturer(Base):
__tablename__ = "manufacturers"
id = Column(Integer, primary_key=True, index=True)
manufacturer = Column(String, unique=True, index=True, nullable=False)
class Reagent_Type(Base):
__tablename__ = "reagent_types"
id = Column(Integer, primary_key=True, index=True)
type = Column(String, unique=True, index=True, nullable=False)
class Reagent(Base):
__tablename__ = "reagents"
id = Column(Integer, primary_key=True, index=True)
manufacturer_id = Column(Integer, ForeignKey("manufacturers.id"))
reagent_type_id = Column(Integer, ForeignKey("reagent_types.id"))
lot = Column(String, nullable = False)
manufacturer = relationship("Manufacturer", backref="reagent_lots")
reagent_type = relationship("Reagent_Type", backref="reagents")
user = relationship("User", back_ref="reagents_created")
Schemas:
class UserEmail(BaseModel):
email: str
class Config:
orm_mode = True
class ManufacturerName(BaseModel):
manufacturer: str
class Config:
orm_mode = True
class Reagent(BaseModel):
id: int
manufacturer: ManufacturerName
reagent_type: ReagentTypeBase
user: UserEmail
class Config:
orm_mode = True
my output is:
{
"id": 1,
"manufacturer": {
"manufacturer": "Foo"
},
"user": {
"email": "User X"
}
}
How can I get it to output this instead?
{
"id": 1,
"manufacturer": "Foo",
"user": "User X"
}

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)

Pydantic how to get relationship value

I need to get one value from relationship Many-to-one model in pydantic BaseModel.
How can I do this?
My children class
class Picnic(Base):
__tablename__ = 'picnic'
id = Column(Integer, primary_key=True, autoincrement=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
city = relationship('City', backref='picnics')
class City(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True, nullable=False, index=True)
I need to get city name value :
class Picnics(BaseModel):
id: int
# city: str[CityBaseInDB.name] not working
# city: str = Field(source='city.name') not working
# city_name: str not working
class Config:
orm_mode: bool = True
from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship, sessionmaker, Query
from pydantic import BaseModel
Base = declarative_base()
engine = create_engine("sqlite://", echo=True)
class Picnic(Base):
__tablename__ = 'picnic'
id = Column(Integer, primary_key=True, autoincrement=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
city = relationship('City', backref='picnics')
class City(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True, nullable=False, index=True)
class PicnicModel(BaseModel):
id: int
# city: str[CityBaseInDB.name] not working
# city: str = Field(source='city.name') not working
city_name: str
class Config:
orm_mode: bool = True
picnic = Picnic(city=City(name='Shenzhen'))
Base.metadata.create_all(engine)
LocalSession = sessionmaker(bind=engine)
db: Session = LocalSession()
db.add(picnic)
db.commit()
q: Query = db.query(Picnic.id, City.name.label('city_name'))
q = q.select_from(Picnic).join(City)
row = q.one_or_none()
model = PicnicModel.from_orm(row)
print(model)
You need to have a pydantic model for your parent model too and put it as the type in the relationship field.
from pydantic import BaseModel
class City(BaseModel):
id: int
name: str
class Config:
orm_mode: bool = True
class Picnics(BaseModel):
id: int
city: City
class Config:
orm_mode: bool = True

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 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()

Abstract Table Concrete Inheritance. Could not determine join condition between parent/child tables

I've got following example code:
models.py
class CadastralObject(Base):
__tablename__ = 'cadastral_object'
def __init__(self, cadastral_region, cadastral_district, cadastral_block, cadastral_object):
self.cadastral_region = cadastral_region
self.cadastral_district = cadastral_district
self.cadastral_block = cadastral_block
self.cadastral_object = cadastral_object
# this is a combined PK
cadastral_region = Column(Integer, primary_key=True, index=True)
cadastral_district = Column(Integer, primary_key=True, index=True)
cadastral_block = Column(Integer, primary_key=True, index=True)
cadastral_object = Column(Integer, primary_key=True, index=True)
encumbrances = relationship("Encumbrance")
class Encumbrance(Base):
__tablename__ = 'encumbrance'
id = Column(Integer, primary_key=True, index=True)
def __init__(self, cadastral_object):
self.parent_cadastral_region = cadastral_object.cadastral_region
self.parent_cadastral_district = cadastral_object.cadastral_district
self.parent_cadastral_block = cadastral_object.cadastral_block
self.parent_cadastral_object = cadastral_object.cadastral_object
# FK fields
parent_cadastral_region = Column(Integer, nullable=False)
parent_cadastral_district = Column(Integer, nullable=False)
parent_cadastral_block = Column(Integer, nullable=False)
parent_cadastral_object = Column(Integer, nullable=False)
parent_object = relationship(CadastralObject)
__table_args__ = (ForeignKeyConstraint(
[
parent_cadastral_region,
parent_cadastral_district,
parent_cadastral_block,
parent_cadastral_object],
[
CadastralObject.cadastral_region,
CadastralObject.cadastral_district,
CadastralObject.cadastral_block,
CadastralObject.cadastral_object]),
{}
)
this code works as intended:
main.py
c = CadastralObject(1, 2, 3, 4)
session.add(c)
e = Encumbrance(c)
session.add(e)
session.commit()
print(c.encumbrances)
print(e.parent_object)
results:
[<app.models.Encumbrance object at 0x000001C9B820BCC0>]
<app.models.CadastralObject object at 0x000001C9B820BB00>
however, when I'm trying convert my code to Concrete Inheritance:
imodels.py
class iCadastralObject(AbstractConcreteBase, Base):
def __init__(self, cadastral_region, cadastral_district, cadastral_block, cadastral_object):
self.cadastral_region = cadastral_region
self.cadastral_district = cadastral_district
self.cadastral_block = cadastral_block
self.cadastral_object = cadastral_object
# this is a combined PK
cadastral_region = Column(Integer, primary_key=True, index=True)
cadastral_district = Column(Integer, primary_key=True, index=True)
cadastral_block = Column(Integer, primary_key=True, index=True)
cadastral_object = Column(Integer, primary_key=True, index=True)
#declared_attr
def encumbrances(self):
return relationship("iEncumbrance")
class Building(iCadastralObject):
__tablename__ = 'building'
__mapper_args__ = {
'polymorphic_identity': 'building',
'concrete': True
}
#declared_attr
def encumbrances(self):
return relationship("iEncumbrance")
class Flat(iCadastralObject):
__tablename__ = 'flat'
__mapper_args__ = {
'polymorphic_identity': 'flat',
'concrete': True
}
#declared_attr
def encumbrances(self):
return relationship("iEncumbrance")
class Construction(iCadastralObject):
__tablename__ = 'construction'
__mapper_args__ = {
'polymorphic_identity': 'construction',
'concrete': True
}
class iEncumbrance(Base):
__tablename__ = 'iencumbrance'
id = Column(Integer, primary_key=True, index=True)
def __init__(self, cadastral_object):
self.parent_cadastral_region = cadastral_object.cadastral_region
self.parent_cadastral_district = cadastral_object.cadastral_district
self.parent_cadastral_block = cadastral_object.cadastral_block
self.parent_cadastral_object = cadastral_object.cadastral_object
# FK fields
parent_cadastral_region = Column(Integer, nullable=False)
parent_cadastral_district = Column(Integer, nullable=False)
parent_cadastral_block = Column(Integer, nullable=False)
parent_cadastral_object = Column(Integer, nullable=False)
parent_object = relationship(iCadastralObject)
__table_args__ = (ForeignKeyConstraint(
[
parent_cadastral_region,
parent_cadastral_district,
parent_cadastral_block,
parent_cadastral_object],
[
iCadastralObject.cadastral_region,
iCadastralObject.cadastral_district,
iCadastralObject.cadastral_block,
iCadastralObject.cadastral_object]),
{}
)
I'm getting an error on "from app.imodels import Building"
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship iCadastralObject.encumbrances - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.