FastAPI - ENUM type models not populated - python

Below is my fastAPI code
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl, Field
from enum import Enum
app = FastAPI()
class Status(Enum):
RECEIVED = 'RECEIVED'
CREATED = 'CREATED'
CREATE_ERROR = 'CREATE_ERROR'
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
status: Status = None
#app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Below is the swagger doc generated. The Status is not shown. I am new to pydantic and i am not sure on how to show status in the docs

create the Status class by inheriting from both str and Enum
class Status(str, Enum):
RECEIVED = 'RECEIVED'
CREATED = 'CREATED'
CREATE_ERROR = 'CREATE_ERROR'
References
Working with Python enumerations--(FastAPI doc)
[BUG] docs don't show nested enum attribute for body--(Issue #329)

Related

Pydantic model inside model

So i am trying to use an existing pydantic model in another pydantic model, as i've seen on the examples.
Those are my pydantic models:
class DriverCategoryOut(BaseModel):
internal_id: int
category: str
class Config:
orm_mode = True
class DocListOut(BaseModel):
driver_categories: DriverCategoryOut
class Config:
orm_mode = True
This is my route code:
#router.get('/document', response_model=shemas.DocListOut)
def get_doc_list(db: Session = Depends(get_db)):
driver_categories = db.query(DriverCategory).first()
return driver_categories
I'm getting the error:
pydantic.error_wrappers.ValidationError: 1 validation error for DocListOut
response -> driver_categories
field required (type=value_error.missing)
If I change to response_model=shemas.DriverCategoryOut it works just fine. What is wrong with my DocListOut model?
This line driver_categories = db.query(DriverCategory).first() retrieves (and parses a record as a) DriverCategory. Assuming that maps perfectly to DriverCategoryOut, why would this be automatically parsed to an object that has a property that is of type DriverCategoryOut?
You might want to try the following:
#router.get('/document', response_model=shemas.DocListOut)
def get_doc_list(db: Session = Depends(get_db)):
driver_categories = db.query(DriverCategory).first()
return {'driver_categories': driver_categories}
Now, that JSON will be parsed to your response_model (in this case, a DocListOut).
there is another way to inheritance your model
class DocListOut(DriverCategoryOut):
class Config:
orm_mode = True
# if you want to exclude some fields
fields = {
"internal_id": {'exclude': True},
}
if your output are lists
class DocListOut(BaseModel):
driver_category: List[DriverCategoryOut] = []
class Config:
orm_mode=True

pydantic + fastapi response schema failed to be validated

schemas.py
class ExpenseBase(BaseModel):
Fixed: float
Variable: float
Salary: float
month: int
year: int
class Expense(ExpenseBase):
class Config:
arbitrary_types_allowed = True
orm_mode = True
class ExpenseSingle(BaseModel):
__root__: Expense
class Config:
arbitrary_types_allowed = True
orm_mode = True
In my main.py:
from fastapi import Depends, FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from . import api, models, schemas
from .database import SessionLocal, engine
[...]
#app.get('/expense/{id}/', response_model=schemas.ExpenseSingle)
def read_expense(id: int, db: Session = Depends(get_db)):
if id is None or isinstance(id, int) is not int:
raise HTTPException(status_code=400, detail="ID is not int")
expense = api.get_expense_by_id(db, id=id)
return expense
but I get the following error when I try to get that API endpoint:
pydantic.error_wrappers.ValidationError: 1 validation error for ExpenseSingle
response
none is not an allowed value (type=type_error.none.not_allowed)
I have tried to change schemas.py, to this:
class ExpenseSingle(BaseModel):
reponse: None
class Config:
arbitrary_types_allowed = True
orm_mode = True
I can't understand where it should be fixed - looks like I didn't understand how pydantic and fastapi relate to each other...
This was solved when I started to handle empty (None) responses from the database with the following code:
if not expense:
raise HTTPException(status_code=404, detail="No Expenses found")
return expense

is it possible to organize validation through a separate parent class in pydantic

I have code, which don't work:
class OrderValidator:
#validator("product_id")
def product_exists(cls, product_id):
print(cls)
session = create_session()
order = session.query(Order).get(product_id)
if order is None:
raise ValueError(f"Product with the ID: {product_id} is not found")
return product_id
#validator("product_quantity")
def check_quantity(cls, product_quantity, values):
session = create_session()
product = session.query(Product).get(values["product_id"])
if product.quantity < product_quantity:
raise ValueError(
f"This product does not have such a quantity: {product_quantity}"
)
return product_quantity
class CreateOrder(BaseModel, OrderValidator):
product_id: int
product_quantity: int
contact_whatsapp: str
contact_telegram: str
contact_phone: str
contact_name: str
contact_city: str
status = 0
class EditOrder(BaseModel, OrderValidator):
product_id: Optional[int]
product_quantity: Optional[int]
contact_whatsapp: Optional[str]
contact_telegram: Optional[str]
contact_phone: Optional[str]
contact_name: Optional[str]
contact_city: Optional[str]
status: Optional[int]
I want to create all the validation logic in one class that pydantic models will inherit. But my code doesn't work.
P.S.: If one of the validation points is shoved directly into the model, then everything will work fine
Due to the way the Pydantic ModelMetaclass is set up, validators will only ever be inherited from parent classes that are themselves models, i.e. inherit from BaseModel. (Source)
This means you can easily solve your problem by just having OrderValidator inherit from BaseModel. Beware however that since the fields that it validates aren't defined on it, you'll need to set check_fields=False on each validator.
I suppose you might as well turn it into your base model for the others at this point, so that you don't have to inherit from BaseModel again. But you know better what the inheritence logic should be.

Pydantic/SQLAlchemy: How to work with enums?

What is the best way to convert a sqlalchemy model to a pydantic schema (model) if it includes an enum field?
Sqlalchemy
import enum
from sqlalchemy import Enum, Column, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class StateEnum(enum.Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
class Adapter(Base):
__tablename__ = 'adapters'
id = Column(String, primary_key=True)
friendly_name = Column(String(256), nullable=False)
state: StateEnum = Column(Enum(StateEnum))
Pydantic
from pydantic import BaseModel
from enum import Enum
class StateEnumDTO(str, Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
class AdapterDTO(BaseModel):
friendly_name: str
state: StateEnumDTO # This currently cannot be converted?
class Config:
allow_population_by_field_name = True
orm_mode = True
use_enum_values = True
Conversion
AdapterDTO.from_orm(Adapter(friendly_name='test', state=StateEnum.CREATED))
This leads to the error
value is not a valid enumeration member; permitted: 'CREATED', 'UPDATED' (type=type_error.enum; enum_values=[<StateEnumDTO.CREATED: 'CREATED'>, <StateEnumDTO.UPDATED: 'UPDATED'>])
How can I configure either
a.) the serialization with the from_orm method?
or
b.) the creation of the state field?
c.) How to convert it the other way around?
Is there a native way to do this with pydantic or how is this typically done?
Update:
Test case
def test_enum_conversion_to_dto():
adapter = Adapter(id='1', friendly_name='test', state=StateEnum.CREATED)
adapter_dto = AdapterDTO.from_orm(adapter)
assert adapter_dto.state == StateEnumDTO.CREATED
assert adapter_dto.state.value == StateEnum.CREATED.value
Pydantic requires that both enum classes have the same type definition.
In your case, StateEnum inherits from enum.Enum, but StateEnumDTO inherits from both str and enum.Enum.
You can fix this issue by changing your SQLAlchemy enum definition:
class StateEnum(str, enum.Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
You must add arbitrary_types_allowed = True
To the model Config class.
from pydantic import BaseModel
from enum import Enum
class StateEnumDTO(str, Enum):
CREATED = 'CREATED'
UPDATED = 'UPDATED'
class AdapterDTO(BaseModel):
friendly_name: str
state: StateEnumDTO # This currently cannot be converted?
class Config:
allow_population_by_field_name = True
orm_mode = True
use_enum_values = True
arbitrary_types_allowed = True

Pydantic set attributes with a default function

Is it possible to pass function setters for immutable Pydantic Models.
For example:
from uuid import uuid4, UUID
from pydantic import BaseModel
from datetime import datetime
def generate_uuid():
return uuid4()
def get_datetimenow():
return datetime.now()
class Item(BaseModel):
class Config:
allow_mutation = False
extra = "forbid"
id: UUID
created_at: datetime
I want the methods generate_uuid and get_datetimenow to set the attributes.
>>> Item()
ValidationError: 2 validation errors for Item
id
field required (type=value_error.missing)
created_at
field required (type=value_error.missing)
While I want to get an Item object with id and created_at automatically set. Identical result as when you run:
>>> Item(id=generate_uuid(), created_at=get_datetimenow())
Item(id=UUID('8f898730-3fad-4ca9-9667-c090f62a2954'), created_at=datetime.datetime(2021, 1, 19, 21, 13, 7, 58051))
You can use default_factory parameter of Field with an arbitrary function. Like so:
from uuid import uuid4, UUID
from pydantic import BaseModel, Field
from datetime import datetime
class Item(BaseModel):
class Config:
allow_mutation = False
extra = "forbid"
id: UUID = Field(default_factory=uuid4)
created_at: datetime = Field(default_factory=datetime.now)

Categories