I have a nested schema in fast api below is the book model`
class Book(Base):
id = Column(Integer, primary_key=True, index=True)
title = Column(String(256), index=True)
subtitle = Column(String(256), index=True)
narrator = Column(String(128), index=True)
publisher_id = Column(Integer, ForeignKey("publisher.id"))
publishers = relationship("Publisher", back_populates="books")
length = Column(Integer, index=True) # length in minutes
release_date = Column(Date, index=True)
language = Column(String(32), index=True)
rating = Column(Float, index=True)
description = Column(ARRAY(String(1024)), index=True)
cover_image = Column(String(256), index=True)
owner_id = Column(Integer, ForeignKey("user.id"))
owner = relationship("User", back_populates="books")
That has a related to publisher model as below `
class Publisher(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), index=True, nullable=False)
books = relationship("Book", back_populates="publishers")
and their respective schema as below
class BookBase(BaseModel):
title: Optional[str] = Field(..., title="Book title", example="Atomic Habits")
subtitle: Optional[str] = Field(
None,
title="Book subtitle",
example="An Easy & Proven Way to Build Good Habits & Break Bad Ones",
)
narrator: Optional[str] = Field(..., title="Narrator", example="James Clear")
# publisher_id: Optional[int] = Field(None, title="Publisher ID")
publisher: Optional[PublisherBase] = Field(None, title="Publisher")
length: Optional[int] = Field(..., title="Length", example=335)
release_date: Optional[datetime.date] = Field(..., title="Release Date")
language: Optional[str] = Field(..., title="Language", example="English")
rating: Optional[float] = Field(..., title="Rating", example=4.8)
description: Optional[list] = Field(..., title="Description")
cover_image: Optional[HttpUrl] = Field(
...,
title="Book Cover Image",
example="https://m.media-amazon.com/images/I/513Y5o-DYtL._SL500_.jpg",
)
class Config:
orm_mode = True
and publisher schema
class PublisherBase(BaseModel):
name: Optional[str] = Field(..., title="Publisher name", example="Penguin Audio")
class Config:
orm_mode = True
based on the relationship above i have the below crud method that accepts book item with a nested publisher `
class CRUDBook(CRUDBase[Book, BookCreate, BookUpdate]):
def create_with_owner(
self, db: Session, *, obj_in: BookCreate, owner_id: int
) -> Book:
# check if the publisher exists
publisher = crud.publisher.show(db=db, name=obj_in.publisher.name)
if not publisher:
# create a new publisher
publisher = crud.publisher.create(
db=db, obj_in=PublisherCreate(name=obj_in.publisher.name)
)
obj_in_data = jsonable_encoder(obj_in_data)
db_obj = self.model(
**obj_in_data,
owner_id=owner_id,
publisher_id=publisher.id,
)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
That is consumed with the below API method
#router.post("/", response_model=schemas.Book)
def create_book(
*,
db: Session = Depends(deps.get_db),
book_in: schemas.BookCreate,
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
"""
Create new book.
"""
book = crud.book.create_with_owner(db=db, obj_in=book_in, owner_id=current_user.id)
return book
with the above, I have my schema as how I wanted that is
Example Value
Schema
{
"title": "Atomic Habits",
"subtitle": "An Easy & Proven Way to Build Good Habits & Break Bad Ones",
"narrator": "James Clear",
"publisher": {
"name": "Penguin Audio"
},
"length": 335,
"release_date": "2022-12-29",
"language": "English",
"rating": 4.8,
"description": [
"string"
],
"cover_image": "https://m.media-amazon.com/images/I/513Y5o-DYtL._SL500_.jpg"
}
But the issue comes when passing data for storage. I face a TypeError TypeError: 'publisher' is an invalid keyword argument for Book
I think that the type error might be due to the nested publisher object that I am accepting in my schema and possibly passing to the book.
Is there a better way to handle this scenario?
You accidentally named the parameter publishers (plural) in your Book model. Change it to publisher (and also the back_populates in Publisher.books), and you should be fine.
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 getting error related the role & I don't know What I mistake.
enter image description here
enter image description here
Database Models:
class Role(Base):
__tablename__ = "role"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), index=True,default="Product")
role_onwer = relationship("User",back_populates="role",uselist=False)
role_onwer1 = relationship("Customer",back_populates="role1",uselist=False)
def __repr__(self):
return f'<Role {self.name}>'
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50))
email = Column(String(255), index=True,unique=True)
password = Column(String(80))
created_at = Column(DateTime, default=datetime.datetime.utcnow)
updated_at = Column(
DateTime,
default=datetime.datetime.utcnow,
onupdate=datetime.datetime.utcnow,
)
last_login = Column(DateTime, nullable=True)
user = relationship("Customer",back_populates="user_onwer",uselist=False)
restaurant = relationship("Restaurant",back_populates="restaurant_onwer",uselist=False)
role_id = Column(Integer, ForeignKey('role.id'))
role = relationship("Role",back_populates="role_onwer",uselist=False)
def __repr__(self):
return f'<User {self.email}>'
class Customer(Base):
__tablename__ = "customer"
id = Column(Integer, primary_key=True)
name = Column(String(255))
phone = Column(String(255))
address = Column(String(255))
city = Column(String(255))
zipCode = Column(String(255))
created_at = Column(DateTime, default=datetime.datetime.utcnow)
updated_at = Column(
DateTime,
default=datetime.datetime.utcnow,
onupdate=datetime.datetime.utcnow,
)
user_id = Column(Integer, ForeignKey("user.id"))
role_id = Column(Integer, ForeignKey('role.id'))
role1 = relationship("Role",back_populates="role_onwer1",uselist=False)
user_onwer = relationship("User",back_populates="user",uselist=False)
orders = relationship("Order", back_populates="order_onwer",uselist=False)
def __repr__(self):
return f'<Customer {self.name}>'
pydantic Models
T = TypeVar('T')
class Response(GenericModel, Generic[T]):
code: str
status: str
message: str
result: Optional[T]
# Customer Request Schemas
class CustomerSchema(BaseModel):
username: str
email: EmailStr
password: str
name: str
phone: str
address: str
city: str
zipCode: str
Crud funtions: customerController
def get_customer_by_id(db: Session, customer_id: int):
return db.query(Customer).filter(Customer.id == customer_id).first()
# This one i have implemented for you
def create_customer(db: Session, customer: CustomerSchema):
new_role = Role()
db.add(new_role)
db.commit()
db.refresh(new_role)
role_ids = new_role.id
new_user = User(username=customer.username, email=customer.email, password=Hash.bcrypt(customer.password),role_id =role_ids)
# new_user = User(username=customer.username, email=customer.email, password=Hash.bcrypt(customer.password),
# role_id=2)
db.add(new_user)
db.commit()
db.refresh(new_user)
user_ids = new_user.id
print(user_ids)
_customer = Customer(name=customer.name, phone=customer.phone, address=customer.address,
city=customer.city, zipCode=customer.zipCode,user_id=user_ids,role_id =role_ids)
db.add(_customer)
db.commit()
db.refresh(_customer)
return _customer
def remove_customer(db: Session, customer_id: int):
_customer = get_customer_by_id(db=db, customer_id=customer_id)
db.delete(_customer)
db.commit()
def update_customer(db: Session, customer_id: int, name: str, email: str, phone: int, customerID: str, address: str,
city: str, zipCode: str):
_customer = get_customer_by_id(db=db, customer_id=customer_id)
_customer.name = name
_customer.email = email
_customer.phone = phone
_customer.customerID = customerID
_customer.address = address
_customer.city = city
_customer.zipCode = zipCode
db.commit()
db.refresh(_customer)
return _customer
customerRouter:
#router.get('/')
async def get(db: Session = Depends(get_db)):
_customer = customerController.get_customer(db, 0, 100)
return Response(code=200, status='OK', message='Success fetch all data', result=_customer).dict(exclude_none=True)
AuthController
class RoleChecker:
def __init__(self, allowed_roles: List):
self.allowed_roles = allowed_roles
def __call__(self, user: schemas.User = Depends(get_current_user)):
if user.role.name not in self.allowed_roles:
print(f"User with role {user.role.name} not in {self.allowed_roles}")
raise HTTPException(status_code=403, detail="Operation not permitted")
return user
customer_access = RoleChecker(['Customer', 'Admin'])
Thank you
I need to have so user cannot retrieve data without being logged in.
and a customer is a user who buys stuff and they may only have the following access:
/customer BY ID:
Read (only they own information)
Update:(only your own data)
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?
I am attempting to update an entry in my DB which should update the parent table projects as well as the child table rounds but I am getting this error 500:
sqlalchemy.exc.StatementError: (builtins.TypeError) unhashable type: 'list'
[SQL: UPDATE projects SET name = project_name=%(param_1)s, name=%(name)s, total_invested=%(total_invested)s, fee=%(fee)s, total_tokens=%(total_tokens)s, tge_date=%(tge_date)s WHERE projects.id = %(id_1)s]
[parameters: [{}]]
put request:
#router.put("/{id}")
def update_post(
id: int, updated_project: schemas.ProjectRound, db: Session = Depends(get_db)
):
project_query = db.query(models.Project).filter(models.Project.id == id)
project = project_query.first()
if project is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"project with id {id} not found",
)
project_query.update(updated_project.dict(), synchronize_session=False)
db.commit()
return project
schemas.py:
class Round(BaseModel):
round_name: str
token_price: float
vesting: str
class Config:
orm_mode = True
class ProjectBase(BaseModel):
name: str
total_invested: float
fee: int
total_tokens: int
tge_date: str
class ProjectRound(ProjectBase):
round: List[Round]
class Config:
orm_mode = True
models.py:
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String, nullable=False, unique=True)
total_invested = Column(Float, nullable=False)
fee = Column(Float, nullable=False)
total_tokens = Column(Integer, nullable=False)
tge_date = Column(String, nullable=False, server_default="TBC")
created_at = Column(
TIMESTAMP(timezone=True), nullable=False, server_default=text("now()")
)
round = relationship("Round", back_populates="projects", cascade="all,delete")
class Round(Base):
__tablename__ = "rounds"
id = Column(Integer, primary_key=True, nullable=False)
project_name = Column(
String, ForeignKey("projects.name", onupdate=("CASCADE"), ondelete=("CASCADE"))
)
round_name = Column(String, nullable=False)
token_price = Column(Float, nullable=False)
vesting = Column(String, nullable=False)
projects = relationship("Project", back_populates="round", cascade="all,delete")
I can seem to update the projects and rounds table. This is what I'm sending across from swagger:
{
"name": "project",
"total_invested": 0,
"fee": 0,
"total_tokens": 0,
"tge_date": "string",
"round": [
{
"round_name": "test",
"token_price": 0,
"vesting": "string"
},
{
"round_name": "test2",
"token_price": 0,
"vesting": "string"
}
]
}
I'm really newbie using SqlAlchemy, right now I'm using fastAPI, basically I need create an Indicator model which can have several nested parameters
this is my code
class Indicator(Base):
__tablename__ = "indicator"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String, unique=True, nullable=False)
indicator_lib_name = Column(String, unique=True, nullable=False)
properties = relationship("Property", backref="indicator", lazy=True,cascade="all, delete")
class Property(Base):
__tablename__ = "property"
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String, unique=True, nullable=False)
value = Column(sqlalchemy_utils.ScalarListType, nullable=True)
indicator_id = Column(Integer, ForeignKey("indicator.id", ondelete="CASCADE"))
# indicator = relationship("Indicator", back_populates="properties")
from pydantic import BaseModel
class Property(BaseModel):
id: Optional[int]
name: str
value: List[Any]
class IndicatorCreate(BaseModel):
name: str
indicator_lib_name: Optional[str]
properties: List[Property]
# https://stackoverflow.com/questions/61144192/insert-a-nested-schema-into-a-database-with-fastapi
# https://dev.to/brandonwallace/one-to-many-relationships-complete-how-to-2nbi
def create_new_indicator(db: Session, indicator: IndicatorCreate):
indicator_data = indicator.dict()
properties = []
if indicator.properties:
properties = indicator_data.pop("properties", None)
new_indicator = Indicator(**indicator_data)
new_indicator.indicator_lib_name = indicator.indicator_lib_name or indicator.name
db.add(new_indicator)
db.commit()
db.refresh(new_indicator)
for prpty in properties:
# prpty["indicator"] = new_indicator
property_model = Property(**prpty, indicator=new_indicator)
db.add(property_model)
db.commit()
db.refresh(property_model)
db.refresh(new_indicator)
return new_indicator
#indicators_router.post("/", response_model=IndicatorBase)
def create_indicator(indicator: IndicatorCreate, db: Session = Depends(get_db)):
indicator_new = create_new_indicator(db, indicator)
print("indicator ", indicator_new)
return indicator_new
data = {
"name": "adx",
"indicator_name": "adx",
"properties": [{"name": "range", "value": [2]}],
}
def test_create_indicator(client):
global data
response = client.post("/indicators/", json.dumps(data))
data = response.json()
print(data)
assert response.status_code == 200
assert data["name"] == "adx"
assert data["id"] == 1
assert len(data["properties"]) == 1
the error is
> raise ValidationError(errors, field.type_)
E pydantic.error_wrappers.ValidationError: 1 validation error for IndicatorBase
E response -> properties -> 0
E value is not a valid dict (type=type_error.dict)
when I debug the code I can see the properties filled correctly, it's getting the data fine but the structure is InstrumentedList instead of List, why? how can I convert this into a List or a better question: which is the better approach to achieve this?
the way how I create the indicator and parameters is the better one? or is there a more concise way?
thank you so much guys, hope you can help me in my first steps with SQLAlchemy
Your Property schema isn't defined as loadable from ORM (i.e. with .foo instead of ['foo'] in the source object).
You can change this by adding
class Config:
orm_mode = True
inside your schema class. Since this isn't present pydantic expects a dict-alike (i.e. that the properties can be resolved through a dictionary lookup).