pydantic + fastapi response schema failed to be validated - python

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

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

How to add obj to db with sqlmodel

I am new to FastAPI. How can i create record in db using sqlmodel and databases packages?
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
team: Optional[Team] = Relationship(back_populates="heroes")
#app.post("/hero", description="Create new hero")
async def create_hero(data: Hero):
hero = Hero(**data.dict())
q = insert(Hero).values(**hero.dict())
h_id = await db.execute(q)
When i finally try to do this, it shows me:
asyncpg.exceptions.NotNullViolationError: null value in column "id" of relation "hero" violates not-null constraint
DETAIL: Failing row contains (null, spider, black, 18, null).
Referring to the sqlmodel docs, id will be set automatically, but using sqlmodel.Session.
How to do the same thing with
import databases
db = databases.Database("postgresql+asyncpg://postgres:postgres#localhost:5432/testdb")
As some comments suggested, you should probably not use databases together with SQLModel. The beauty of SQLModel is (among many other things) that the database inserts are so simple: You just add your model objects to a database session and commit.
Another suggestion is to make use of the FastAPI dependencies to automatically initialize, start, inject and finally close a session in every route.
Here is a working example:
from typing import Optional
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio.engine import create_async_engine
from sqlalchemy.ext.asyncio.session import AsyncSession
from sqlalchemy.orm.session import sessionmaker
from sqlmodel import Field, SQLModel
api = FastAPI()
db_uri = "postgresql+asyncpg://postgres:postgres#localhost:5432/testdb"
engine = create_async_engine(db_uri, future=True)
session_maker = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
#api.on_event('startup')
async def initialize_db():
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.drop_all)
await conn.run_sync(SQLModel.metadata.create_all)
async def get_session() -> AsyncSession:
session = session_maker()
try:
yield session
finally:
await session.close()
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
...
#api.post("/hero", description="Create new hero")
async def create_hero(hero: Hero, session: AsyncSession = Depends(get_session)):
session.add(hero)
await session.commit()
await session.refresh(hero)
return hero
Note that for testing purposes I simply drop and (re-)create all database tables on startup.
Hope this helps.

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

Is there a way to return two fields from SQLAlchemy / FastAPI relationship?

structure
.
└── sql_app
├── __init__.py
├── crud.py
├── database.py
├── main.py
├── models.py
└── schemas.py
main.py
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
#app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_name(db, name=user.name)
if db_user:
raise HTTPException(status_code=400, detail="Name already registered")
return crud.create_user(db=db, user=user)
#app.get("/users/", response_model=List[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
#app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
#app.post("/users/{user_id}/messages/", response_model=schemas.Message)
def create_message_for_user(
user_id: int, message: schemas.MessageCreate, db: Session = Depends(get_db)
):
return crud.create_user_message(db=db, message=message, user_id=user_id)
#app.get("/messages/", response_model=List[schemas.MessageWithUser])
def read_messages(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
messages = crud.get_messages(db, skip=skip, limit=limit)
return messages
crud.py
from sqlalchemy.orm import Session
from . import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_name(db: Session, name: str):
return db.query(models.User).filter(models.User.name == name).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
db_user = models.User(name=user.name)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def get_messages(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Message).offset(skip).limit(limit).all()
def create_user_message(db: Session, message: schemas.MessageCreate, user_id: int):
db_item = models.Message(**message.dict(), user_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password#postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
models.py
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True, unique=True, nullable=False)
messages = relationship("Message", back_populates="user")
class Message(Base):
__tablename__ = "messages"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, unique=True, nullable=False)
message = Column(String, index=False)
user_id = Column(Integer, ForeignKey("users.id"))
user = relationship("User", back_populates="messages")
schemas.py
from typing import List
from pydantic import BaseModel
class MessageBase(BaseModel):
title: str
message: str
class MessageCreate(MessageBase):
pass
class Message(MessageBase):
id: int
user_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
name: str
class UserCreate(UserBase):
pass
class User(UserBase):
id: int
messages: List[Message] = []
class Config:
orm_mode = True
class UserIdentifier(UserBase):
id: int
name: str
class MessageWithUser(MessageBase):
id: int
title: str
message: str
user: UserIdentifier
Run cmd:
uvicorn sql_app.main:app --reload
Navigate to: 127.0.0.1:8000/docs
Add users and messages for users
Try to read messages
The example result is the correct format but it does not give a response just 500 error and the following
Error:
pydantic.error_wrappers.ValidationError: 4 validation errors for MessageWithUser
response -> 0
value is not a valid dict (type=type_error.dict)
response -> 1
value is not a valid dict (type=type_error.dict)
response -> 2
value is not a valid dict (type=type_error.dict)
response -> 3
value is not a valid dict (type=type_error.dict)
The expected output is what is shown in the example output of the docs page, with UserIdentifier being added to Message but not any other fields from User if there were any, in this case there is not
I don't think that it is possible to cast a SqlAlchemy object with dependencies to a pydantic model out of the box.
#tiagolo (the creator of FastAPI) did this pydantic-sqlalchemy method that seems to do the trick of what you want:
https://github.com/tiangolo/pydantic-sqlalchemy
Otherwise, I think that you can create a list of MessageWithUser manually and return it.

FastAPI - ENUM type models not populated

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)

Categories