I want to map backer_id which is a primary key in my model to all the information related to that user (as defined in UserInProject schema) using Pydantic.
Pydantic file:
class UserInProject(BaseModel):
email: EmailStr
full_name: str
id: int
class Config:
orm_mode = True
class TransactionBase(BaseModel):
quantity: int
amount: float
currency: Currency
class TransactionIn(TransactionBase):
project_id: int
class TransactionOut(BaseModel):
id: int
date_ordered: datetime
backer: "UserInProject"
My model:
class BackerProjectOrder(Base):
__tablename__ = "backers_projects_orders"
id = Column(
Integer, primary_key=True, index=True, autoincrement=True, nullable=False
)
backer_id = Column(ForeignKey("users.id"), index=True, primary_key=True)
...
My API:
#router.post(
"/", status_code=status.HTTP_201_CREATED, response_model=schema.TransactionOut
)
def create_transaction(
transaction: schema.TransactionIn,
db: Session = Depends(get_db),
current_user: models.User = Depends(get_current_user),
):
new_transaction = models.BackerProjectOrder(**transaction_dict, backer_id = current_user.id)
db.add(new_transaction)
db.commit()
db.refresh(new_transaction)
And it currently gives me this error:
pydantic.error_wrappers.ValidationError: 1 validation error for TransactionOut
response -> backer
field required (type=value_error.missing)
How can I ask Pydantic to map the backer_id field to the UserInProject schema? Is it possible at all?
Related
when I try to get organization by id (inn) in devices_list field is null (device is exist):
{
inn: 8481406044,
organization_name: "slava bebrow",
devices_list: null
}
models.py:
class Organization(Base):
__tablename__ = "organizations_table"
inn = Column(BigInteger, primary_key=True, index=False)
organization_name = Column(String, nullable=False, unique=True)
devices = relationship("Device", backref="organizations_table")
class Device(Base):
__tablename__ = "devices_table"
uuid = Column(String, primary_key=True, index=False)
device_name = Column(String, nullable=False, unique=True)
organization_id = Column(BigInteger, ForeignKey("organizations_table.inn"), nullable=True)
pydantic_models.py:
class OrganizationBase(BaseModel):
inn: int
organization_name: str
class Config:
orm_mode = True
class Organization(OrganizationBase):
devices_list: list['DeviceBase'] = None
class DeviceBase(BaseModel):
uuid: str
device_name: str
organization_id: int | None
class Config:
orm_mode = True
functions to get organization:
def get_organization(db: Session, organization_id: int):
db_organization = db.query(models.Organization).filter(models.Organization.inn == organization_id).first()
if db_organization is None:
raise HTTPException(status_code=404, detail="Organization not found")
return db_organization
when i try to print(db_organization.devices[0].uuid) i get a list with objects
#app.get("/organizations/{organization_id}", response_model=pydantic_models.Organization)
def get_organization(organization_id, db: Session = Depends(get_db)):
return al.get_organization(db=db, organization_id=organization_id)
I think problem is in pydantic model, but i don't know how to fix it.
I expect a list of devices in field, not null
I am trying to figure out how to use typehints properly with sqlalchemy.
I have 2 Models:
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class Person(Base):
__table__ = "persons"
id: int = Column(Integer, primary_key=True)
name: str = Column(String, nullable=False)
age: str | None = Column(String)
pets: list[Animal] = relationship(
"Animal",
back_populates="persons",
lazy="selectin",
)
class Animal(Base):
__table__ = "animals"
id: int = Column(Integer, primary_key=True)
weight: float | None = Column(Float)
max_age: int | None = Column(Integer)
owner: Person = relationship(
"Person",
back_populates="animals",
lazy="selectin",
)
Now I want to mark both of them as dataclass to have an __init__ method with typehints. Now my question is if that is a good idea, because both (Base and dataclass) do some “magic” stuff underneath, and I want to know if this could cause any Issues.
Also, the default parameters would now be Column, relationship etc.
Maybe there are other (simpler) options to achieve this?
There is no Base when registering dataclasses, it uses the mapper_registry.mapped decorator (docs):
Here are your classes as declaratively mapped dataclasses, however I corrected the relationships which did not work as you wrote them.
from __future__ import annotations
from dataclasses import dataclass, field
from sqlalchemy import Column, Float, ForeignKey, Integer, String
from sqlalchemy.orm import registry, relationship
mapper_registry = registry()
#mapper_registry.mapped
#dataclass
class Person:
__tablename__ = "persons" # NOTE: __tablename__ not __table
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
name: str = field(init=False, metadata={"sa": Column(String, nullable=False)})
age: str | None = field(init=False, metadata={"sa": Column(String)})
pets: list[Animal] = field(
default_factory=list,
metadata={
"sa": relationship(
"Animal",
back_populates="owner",
lazy="selectin",
)
},
)
#mapper_registry.mapped
#dataclass
class Animal:
__tablename__ = "animals"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
weight: float | None = field(init=False, metadata={"sa": Column(Float)})
max_age: int | None = field(init=False, metadata={"sa": Column(Integer)})
owner_id: id = field(init=False, metadata={"sa": Column(ForeignKey("persons.id"))})
owner: Person = field(
init=False,
metadata={
"sa": relationship(
"Person",
back_populates="pets",
lazy="selectin",
)
},
)
Getting the following error when I launch a query against a table defined with dataclasses using the full declarative method (https://docs.sqlalchemy.org/en/14/orm/declarative_styles.html#example-two-dataclasses-with-declarative-table):
sqlalchemy.exc.DBAPIError: (sqlalchemy.dialects.postgresql.asyncpg.Error) <class 'asyncpg.exceptions.DataError'>: invalid input for query argument $1: Field(name='ticker',type=<class 'str'>,d... (expected str, got Field)
I am using the column_property as follows:
average_price:float = field(metadata={'sa':column_property(
select(
func.avg(
Transaction.price)
)
.where(
(Transaction.user_id==user_id)
)
.correlate_except(Transaction)
.scalar_subquery())})
Full code including table definitions:
import uuid, enum, datetime
from dataclasses import dataclass, field
from sqlalchemy import Column, DateTime, Float, String, Integer, ForeignKey, Numeric, Enum, Date, select, func, exists
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import registry, column_property
Base = registry()
#Base.mapped
#dataclass
class User:
__tablename__ = "user_model"
__sa_dataclass_metadata_key__ = "sa"
id: uuid.UUID = field(
init=False,
default_factory=uuid.uuid4,
metadata={"sa": Column(UUID(as_uuid=True), primary_key=True)},
)
email: str = field(
metadata={"sa": Column(String(254), nullable=False, unique=True, index=True)}
)
user_name: str = field(
metadata={"sa": Column(String(254), nullable=True, unique=True, index=True)}
)
hashed_password: str = field(metadata={"sa": Column(String(128), nullable=False)})
#Base.mapped
#dataclass
class Transaction:
__tablename__ = "transactions_model"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={'sa': Column(Integer, primary_key=True)})
user_id: uuid.UUID = field(
metadata={"sa": Column(ForeignKey("user_model.id", ondelete="CASCADE"))}
)
item: str = field(metadata={'sa': Column(String(50), nullable=False)})
number: int = field(metadata={'sa': Column(Integer, nullable=False)})
price: Numeric(10,2) = field(metadata={'sa': Column(Numeric(10,2), nullable=False)})
transact_time: DateTime = field(metadata={'sa': Column(DateTime, nullable = False)})
#Base.mapped
#dataclass
class UserItems:
__tablename__ = "owner_model"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={'sa': Column(Integer, primary_key=True)})
user_id: uuid.UUID = field(
metadata={"sa": Column(ForeignKey("user_model.id", ondelete="CASCADE"))}
)
item: str = field(metadata={'sa': Column(String(50), nullable=False)})
number_owned: int = field(metadata={'sa': Column(Integer, nullable=False)})
average_price:float = field(metadata={'sa':column_property(
select(
func.avg(
Transaction.price)
)
.where(
(Transaction.user_id==user_id)
)
.correlate_except(Transaction)
.scalar_subquery())})
Querying the table returns the error shown. I tried querying the table as:
items = await session.execute(
select(UserItems)
.where(UserItems.user_id == current_user.id)
return items.scalars().all()
I have a suspicion SQLAlchemy is having trouble when I pass the column defined by field, but I have no idea how else to do the same.
Thanks in advance.
I have a User table and I want to link users together to create a relationship.
A user can be link to many other users (I need to show current and old relationships).
So my first try is to create a table called 'relationship' with 2 columns (user1 and user2) and a column that tell us if the relationship is still active.
This is the SQL schemas
USER
id
firstname
gender
1
John
M
2
Marie
F
3
Jane
F
Relationship
user1
user2
active
1
2
0
1
3
1
Is it the best solution ? Can you explain me how to set it up with SQLAlchemy, and how to set up models and shema in FastAPI ? Especially the relations that allow me to get all relationships at once for a user for example
This is my first try
Models
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
firstname = Column(String)
gender = Column(String, nullable=True)
relationships = relationship('Relationship') #What Here ??
class Relationship(Base):
__tablename__ = "relationship"
user1 = relationship("User") #back_populates ?
user2 = relationship("User") #back_populates ?
active = Column(Boolean, default="true")
Schemas
from pydantic import BaseModel
class Relationship(BaseModel):
user1: int
user2: int
active: bool
class Config:
orm_mode = True
class User(BaseModel):
id: int
firstname: str
gender: str
relationships: List[Relationship] = [] #Is it good ?
class Config:
orm_mode = True
you doesn't need add multiple user into your relationship model
just make a relation to user model
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
firstname = Column(String)
gender = Column(String, nullable=True)
relationship = relationship('Relationship', back_populates="user")
class Relationship(Base):
__tablename__ = "relationship"
user_id = Column(Integer, ForeignKey("user.id"))
user = relationship("User", back_populates='relationship')
active = Column(Boolean, default="true")
from pydantic import BaseModel
class Relationship(BaseModel):
user_id: int
active: bool
class Config:
orm_mode = True
class User(BaseModel):
id: int
firstname: str
gender: str
relationships: List[Relationship] = [] #Is it good ?
class Config:
orm_mode = True
now in your orm just filter which users are active as True
I have a following models with many-to-many relations:
dashboard_customer_association = Table(
"entry_customer",
Base.metadata,
Column("entry_id", ForeignKey("entry.id"), primary_key=True),
Column("customer_id", ForeignKey("customer.id"), primary_key=True),
)
class Customer(Base):
__tablename__ = "customer"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String(64), unique=True, index=True)
class Entry(Base):
__tablename__ = "entry"
id = Column(String(16), primary_key=True, index=True)
customer = relationship("Customer", secondary=dashboard_customer_association)
Here's my pydantic schema.
class Entry(BaseModel):
id: str
customer: List[str] = []
class Config:
orm_mode = True
I've managed to insert the data and create the customers alongside,
but the problem is when I'm trying to retrieve data:
pydantic.error_wrappers.ValidationError: 2 validation errors for Entry
response -> customer -> 0
str type expected (type=type_error.str)
response -> customer -> 1
str type expected (type=type_error.str)
I understand that the Customer object is not a string, so customer
field cannot be directly serialized as List[str], but I fail to see
how am I supposed to do the conversion.
I return the data with the following function:
def get_data(item_id):
instance = db.query(models.Entry).filter(models.Entry.id == item_id).first()
return instance
I was trying to set instance.customer = [customer.name for customer in instance.customer],
but SQLalchemy prevents that. What is the right way to do that?
The best way would be to simply match the schema to the returned data and have a Customer object as well.
However, if that is not an option, you can use a validator to change the content when it's being populated - i.e. just return a single value from your Customer object.
#validator('customer')
def customer_as_string(cls, v):
return v.name