FastApi get request shows validation error - python

I'm getting this error when I try to get some data from my postgre db and using fastapi.
I don't know why it happens...but here is my code, thank you for your help.
Route
#router.get("/fuentes", response_model=FuenteSerializer.MFuente) # <--- WHEN I REMOVE RESPONSE_MODEL WORKS AND RETURNS A JSON DATA DIRECTLY FROM MODEL I GUESS
async def read_fuentes(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
fuentes = FuenteSerializer.get_fuente(db, skip=skip, limit=limit)
return fuentes
sqlalchemy model
class MFuente(Base):
__tablename__ = 'M_fuentes'
idfuentes = Column(Integer, primary_key=True)
idproductos = Column(ForeignKey('M_productos.idproductos', ondelete='RESTRICT', onupdate='RESTRICT'), index=True)
autoapp = Column(CHAR(2))
rutFabricante = Column(String(12))
elemento = Column(String(100))
estado = Column(Integer)
stype = Column(Integer)
aql = Column(String(5))
equiv = Column(String(5))
division = Column(String(100))
nu = Column(Integer)
filexcel = Column(String(100))
M_producto = relationship('MProducto')
Serializer / schema
class MFuente(BaseModel):
idfuentes: int
autoapp: str
fecregistro: datetime.date
rutFabricante: str
elemento: str
estado: int
stype: int
aql: str
equiv: str
division: str
fileexel: str
productos: List[MProducto]
class Config:
orm_mode = True
def get_fuente(db: Session, skip: int = 0, limit: int = 100):
return db.query(Fuente).offset(skip).limit(limit).all()

For a little debugging i created the same little prototype, and i found some possible answers.
First of all here is the app that i created:
class MFuente(BaseModel):
name: str
value: int
#app.get("/items/{name}", response_model=MFuente)
async def get_item(name: str):
query = fuente_db.select().where(fuente_db.c.name == name)
return await database.fetch_all(query)
So with this schema i get the same error
response -> name
field required (type=value_error.missing)
response -> value
field required (type=value_error.missing)
So i debugged a little bit more i found it's all about response_model, so i came up with this:
from typing import List
...
#app.get("/items/{name}", response_model=List[MFuente])
Everything started working:
INFO: 127.0.0.1:52872 - "GET /items/masteryoda HTTP/1.1" 200 OK
So in your case fix will be:
#router.get("/fuentes", response_model=List[FuenteSerializer.MFuente])
^^^^

Related

Resolve "none is not an allower value"

I am currently trying to write an application in which one object (Room) inherits from the other (Building). But when I try to create a room using the Swagger API (create Room) I get the following error:
pydantic.error_wrappers.ValidationError: 1 validation error for Room
response -> content
none is not an allowed value (type=type_error.none.not_allowed).
I have only recently started with SQL and Python and am having a hard time solving this problem. I would be very happy about help and a possible explanation of what I am doing wrong.
Here is my Code:
models.py:
class Building(_database.Base):
__tablename__ = "buildings"
id = _sql.Column(_sql.Integer, primary_key=True, index=True)
title = _sql.Column(_sql.String, index=True)
date_created = _sql.Column(_sql.DateTime, default=_dt.datetime.utcnow)
date_last_updated = _sql.Column(_sql.DateTime, default=_dt.datetime.utcnow)
rooms = _orm.relationship("Room", back_populates="owner")
class Room(_database.Base):
__tablename__ = "rooms"
id = _sql.Column(_sql.Integer, primary_key=True, index=True)
title = _sql.Column(_sql.String, index=True)
content = _sql.Column(_sql.String, index=True)
owner_id = _sql.Column(_sql.Integer, _sql.ForeignKey("buildings.id"))
building_title = _sql.Column(_sql.String, index=True)
date_created = _sql.Column(_sql.DateTime, default=_dt.datetime.utcnow)
date_last_updated = _sql.Column(_sql.DateTime, default=_dt.datetime.utcnow)
owner = _orm.relationship("Building", back_populates="rooms")
schemas.py:
#Rooms
class _RoomBase(_pydantic.BaseModel):
title: str
content: str
class RoomCreate(_RoomBase):
pass
class RoomUpdate(_RoomBase):
pass
class Room(_RoomBase):
id: int
owner_id: int
building_title: str
date_created: _dt.datetime
date_last_updated: _dt.datetime
class Config:
orm_mode = True
#Buildings
class _BuildingBase(_pydantic.BaseModel):
title: str
class BuildingCreate(_BuildingBase):
pass
class BuildingUpdate(_BuildingBase):
pass
class Building(_BuildingBase):
id: int
date_created: _dt.datetime
date_last_updated: _dt.datetime
rooms: List[Room] = []
class Config:
orm_mode = True
services.py
#Buildings
def create_building(db: _orm.Session, building: _schemas.BuildingCreate):
building = _models.Building(title=building.title)
db.add(building)
db.commit()
db.refresh(building)
return building
def get_building(db: _orm.Session, building_id: int ):
return db.query(_models.Building).filter(_models.Building.id == building_id).first()
def get_building_by_title(db: _orm.Session, building_title: str ):
return db.query(_models.Building).filter(_models.Building.title == building_title).first()
def delete_building(db: _orm.Session, building_id: int):
db.query(_models.Building).filter(_models.Building.id == building_id).delete()
db.commit()
def update_building(db: _orm.Session, building_id: int, building: _schemas.BuildingCreate):
db_building = get_building(db=db, building_id=building_id)
db_building.title = building.title
db.commit()
db.refresh(db_building)
return db_building
#Rooms
def create_room(db: _orm.Session, room: _schemas.RoomCreate, building_id:int, building_title: str):
room = _models.Room(title=room.title,owner_id=building_id, building_title=building_title)
db.add(room)
db.commit()
db.refresh(room)
return room
main.py
#Building
#app.post("/buildings/", response_model=_schemas.Building)
def create_building(
building: _schemas.BuildingCreate, db: _orm.Session = _fastapi.Depends(_services.get_db)
):
return _services.create_building(db=db, building=building)
#app.get("/buildings/{building_id}", response_model=_schemas.Building)
def read_building(building_id: int, db: _orm.Session = _fastapi.Depends(_services.get_db)):
building = _services.get_building(db=db, building_id=building_id)
if building is None:
raise _fastapi.HTTPException(
status_code=404, detail="sorry this building does not exist"
)
return building
#app.delete("/buildings/{building_id}")
def delete_building(building_id: int, db: _orm.Session = _fastapi.Depends(_services.get_db)):
_services.delete_building(db=db, building_id=building_id)
return {"message": f"successfully deleted building with id: {building_id}"}
#app.put("/buildings/{building_id}", response_model=_schemas.Building)
def update_building(
building_id: int,
building: _schemas.BuildingCreate,
db: _orm.Session = _fastapi.Depends(_services.get_db),
):
return _services.update_building(db=db, building=building, building_id=building_id)
#Room
#app.post("/rooms/", response_model=_schemas.Room)
def create_room(
building_title: str,
room: _schemas.RoomCreate,
db: _orm.Session = _fastapi.Depends(_services.get_db),
):
db_building = _services.get_building_by_title(db=db, building_title=building_title)
if db_building is None:
raise _fastapi.HTTPException(
status_code=404, detail="sorry this building does not exist"
)
return _services.create_room(db=db, room=room,building_id=db_building.id, building_title=db_building.title)
Thank you for your help!
As MatsLindh points out, RoomCreate has a content field that is not used in services.create_room. Simply changing to
def create_room(db: _orm.Session, room: _schemas.RoomCreate, building_id:int, building_title: str):
room = _models.Room(title=room.title, content=room.content, owner_id=building_id, building_title=building_title)
If content is required, you should probably also define your SQLAlchemy model as content = _sql.Column(_sql.String, nullable=False, index=True).
For next time, please learn how to provide a minimal reproducible example

pydantic.error_wrappers.ValidationError: 11 validation errors for For Trip type=value_error.missing

Im getting this error with my pydantic schema, but oddly it is generating the object correctly, and sending it to the SQLAlchemy models, then it suddenly throws error for all elements in the model.
response -> id
field required (type=value_error.missing)
response -> date
field required (type=value_error.missing)
response -> time
field required (type=value_error.missing)
response -> price
field required (type=value_error.missing)
response -> distance
field required (type=value_error.missing)
response -> origin_id
field required (type=value_error.missing)
response -> destination_id
field required (type=value_error.missing)
response -> driver_id
field required (type=value_error.missing)
response -> passenger_id
field required (type=value_error.missing)
response -> vehicle_id
field required (type=value_error.missing)
response -> status
field required (type=value_error.missing)
i must say that all the fields should have values. And the error trace do not references any part of my code so i dont even know where to debug. Im a noob in SQLAlchemy/pydantic
here are some parts of the code
class Trip(BaseModel):
id: int
date: str
time: str
price: float
distance: float
origin_id: int
destination_id: int
driver_id: int
passenger_id: int
vehicle_id: int
status: Status
class Config:
orm_mode = True
class TripDB(Base):
__tablename__ = 'trip'
__table_args__ = {'extend_existing': True}
id = Column(Integer, primary_key=True, index=True)
date = Column(DateTime, nullable=False)
time = Column(String(64), nullable=False)
price = Column(Float, nullable=False)
distance = Column(Float, nullable=False)
status = Column(String(64), nullable=False)
origin_id = Column(
Integer, ForeignKey('places.id'), nullable=False)
destination_id = Column(
Integer, ForeignKey('places.id'), nullable=False)
origin = relationship("PlaceDB", foreign_keys=[origin_id])
destination = relationship("PlaceDB", foreign_keys=[destination_id])
driver_id = Column(
Integer, ForeignKey('driver.id'), nullable=False)
vehicle_id = Column(
Integer, ForeignKey('vehicle.id'), nullable=False)
passenger_id = Column(
Integer, ForeignKey('passenger.id'), nullable=False)
def create_trip(trip: Trip, db: Session):
origin = db.query(models.PlaceDB).filter(models.PlaceDB.id == trip.origin_id).first()
destination = db.query(models.PlaceDB).filter(models.PlaceDB.id == trip.destination_id).first()
db_trip = TripDB(
id=(trip.id or None),
date=trip.date or None, time=trip.time or None, price=trip.price or None,
distance=trip.distance or None,
origin_id=trip.origin_id or None, destination_id=(trip.destination_id or None), status=trip.status or None,
driver_id=trip.driver_id or None, passenger_id=trip.passenger_id or None, vehicle_id=trip.vehicle_id or None, origin=origin, destination=destination)
try:
db.add(db_trip)
db.commit()
db.refresh(db_trip)
return db_trip
except:
return "Somethig went wrong"
It seems like a bug on the pydantic model, it happened to me as well, and i was not able to fix it, but indeed if you just skip the type check in the route it works fine
It seems like there is a conflict in your schema and create_trip function. Have you checked whether you are passing the correct param to your schema? You have defined most of the fields as not nullable, and you are passing None as an alternative value in db.add() command.
I had a similar problem with my code, and I figured that naming and type convention between schema and server.py. After matching the field names and type in both file, I resolved the error of the field required (type=value_error.missing).
# project/schema.py
from pydantic import BaseModel
# here I had made mistake by using target_URL in server.py
# variable name and type should be same in both schema and server
class URLBase(BaseModel):
target_url: str
class URL(URLBase):
is_active: bool
clicks: int
class Config:
orm_mode = True
class URLInfo(URL):
url: str
admin_url: str
# project/server.py
#app.post('/url', response_model=schema.URLInfo)
def create_short_url(url: schema.URLBase, db: Session = Depends(get_db)):
if not validators.url(url.target_url):
raise bad_request_message(message="Your provided URL is not valid!")
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
key = "".join(secrets.choice(chars) for _ in range(5))
secret_key = "".join(secrets.choice(chars) for _ in range(8))
db_url = models.URL(target_url=url.target_url, key=key, secret_key=secret_key)
db.add(db_url)
db.commit()
db.refresh(db_url)
db_url.url = key
db_url.admin_url = secret_key
return db_url
Please check the return statement, i had similar issue and got the issue reolved by correcting my return statement. I see return statement missing or wrong in create_trip function.

How to query a nested attribute in SQLModel?

I have these two tables named User and UserRole.
class UserRoleType(str, enum.Enum):
admin = 'admin'
client = 'client'
class UserRole(SQLModel, table=True):
__tablename__ = 'user_role'
id: int | None = Field(default=None, primary_key=True)
type: UserRoleType = Field(
default=UserRoleType.client,
sa_column=Column(Enum(UserRoleType)),
)
write_access: bool = Field(default=False)
read_access: bool = Field(default=False)
users: List['User'] = Relationship(back_populates='user_role')
class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
username: str = Field(..., index=True)
user_role_id: int = Field(..., foreign_key='user_role.id')
user_role: 'UserRole' = Relationship(back_populates='users')
I can easily insert them into the DB with:
async with get_session() as session:
role = UserRole(description=UserRoleType.client)
session.add(role)
await session.commit()
user = User( username='test', user_role_id=role.id)
session.add(user)
await session.commit()
await session.refresh(user)
And access the committed data with:
results = await session.execute(select(User).where(User.id == 1)).one()
Output:
(User(user_role_id=1, username='test', id=1),)
Notice that there's an user_role_id, but where's the user_role object?
In fact, if I try to access it, it raises:
*** AttributeError: Could not locate column in row for column 'user_role'
I also tried to pass the role instead of the user_role_id at the insertion of the User:
user = User( username='test', user_role=role)
But I got:
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 2 - probably unsupported type.
A few things first.
You did not include your import statements, so I will have to guess a few things.
You probably want the User.user_role_id and User.user_role fields to be "pydantically" optional. This allows you to create user instances without passing the role to the constructor, giving you the option to do so after initialization or for example by appending User objects to the UserRole.users list instead. To enforce that a user must have a role on the database level, you simply define nullable=False on the User.user_role_id field. That way, if you try to commit to the DB without having defined a user role for a user in any of the possible ways, you will get an error.
In your database insertion code you write role = UserRole(description=UserRoleType.client). I assume the description is from older code and you meant to write role = UserRole(type=UserRoleType.client).
You probably want your UserRole.type to be not nullable on the database side. You can do so by passing nullable=False to the Column constructor (not the Field constructor).
I will simplify a little by using blocking code (non-async) and a SQLite database.
This should work:
from enum import Enum as EnumPy
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import Enum as EnumSQL
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine
class UserRoleType(str, EnumPy):
admin = 'admin'
client = 'client'
class UserRole(SQLModel, table=True):
__tablename__ = 'user_role'
id: int | None = Field(default=None, primary_key=True)
type: UserRoleType = Field(default=UserRoleType.client, sa_column=Column(EnumSQL(UserRoleType), nullable=False))
write_access: bool = Field(default=False)
read_access: bool = Field(default=False)
users: list['User'] = Relationship(back_populates='user_role')
class User(SQLModel, table=True):
__tablename__ = 'user'
id: int | None = Field(default=None, primary_key=True)
username: str = Field(..., index=True)
user_role_id: int | None = Field(foreign_key='user_role.id', default=None, nullable=False)
user_role: UserRole | None = Relationship(back_populates='users')
def test() -> None:
# Initialize database & session:
sqlite_file_name = 'user_role.db'
sqlite_url = f'sqlite:///{sqlite_file_name}'
engine = create_engine(sqlite_url)
SQLModel.metadata.drop_all(engine)
SQLModel.metadata.create_all(engine)
session = Session(engine)
# Create the test objects:
role = UserRole(type=UserRoleType.client)
user = User(username='test', user_role=role)
session.add(user)
session.commit()
session.refresh(user)
# Do some checks:
assert isinstance(user.user_role.type, EnumPy)
assert user.user_role_id == role.id and isinstance(role.id, int)
assert role.users == [user]
if __name__ == '__main__':
test()
PS: I know the question was posted a while ago, but maybe this still helps or helps someone else.

Add a custom filed to fastapi responce model (serializers)

I'm following this guide from the Fastapi documentation and I have a question what if want to add a custom field when I return an object from DB. In Django I can use serializers.
My case:
I want to save an image name into DB, but before that I need save an actual file in a static folder. When I call GET /items/1 I want to return not just an image name from DB, but full URL, so I need to execute some logic on every request in order to build the URL. The question is how can I achieve that? The only thing I can think of is to add an additional DTO layer that coverts input data to Pydantic classes, so it's gonna be:
DTO class -> Pydantic -> DB
Is there more fancy way of doing that?
Code example:
schemas.py
from typing import List, Literal, Optional
from enum import Enum, IntEnum
from pydantic import BaseModel, constr, validator
class Ingredient(BaseModel):
quantity: int
quantityUnit: QuantityUnitEnum
name: constr(max_length=50)
class RecipeBase(BaseModel):
id: int = None
title: constr(max_length=50)
# image_name: str
#validator('ingredients')
def ingredients_must_have_unique_name(cls, values):
names = []
for item in values:
names.append(item.name)
if len(names) > len(set(names)):
raise ValueError('must contain unique names')
return values
class RecipeCreate(RecipeBase):
pass
class Recipe(RecipeBase):
id: int
class Config:
orm_mode = True
model.py
class Recipe(Base):
__tablename__ = "recipes"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(50), index=True, nullable=False)
image_name = Column(String(50), index=True, nullable=False)
main.py
#app.post("/recipes", response_model=schemas.Recipe)
def create_recipe(recipe: schemas.RecipeCreate, db: Session = Depends(get_db)):
return repository.create_recipe(db=db, recipe=recipe)
#app.get("/recipes/{recipe_id}", response_model=schemas.Recipe)
def get_recipe(recipe_id, db: Session = Depends(get_db)):
return repository.get_recipe(db, recipe_id=recipe_id)
repository.py
def get_recipe(db: Session, recipe_id: int):
return db.query(models.Recipe).get(recipe_id)

fastapi: mapping sqlalchemy database model to pydantic geojson feature

I just started playing with FastAPI, SQLAlchemy, Pydantic and I'm trying to build a simple API endpoint to return the rows in a postgis table as a geojson feature collection.
This is my sqlalchemy model:
class Poi(Base):
__tablename__ = 'poi'
id = Column(Integer, primary_key=True)
name = Column(Text, nullable=False)
type_id = Column(Integer)
geometry = Column(Geometry('POINT', 4326, from_text='ST_GeomFromEWKT'),
nullable=False)
Using geojson_pydantic the relevant pydantic models are:
from geojson_pydantic.features import Feature, FeatureCollection
from geojson_pydantic.geometries import Point
from typing import List
class PoiProperties(BaseModel):
name: str
type_id: int
class PoiFeature(Feature):
id: int
geometry: Point
properties: PoiProperties
class PoiCollection(FeatureCollection):
features: List[PoiFeature]
Desired Output:
Ideally I'd like to be able to retrieve and return the database records like so:
def get_pois(db: Session, skip: int = 0, limit: int = 100):
return db.query(Poi).offset(skip).limit(limit).all()
#app.get("/geojson", response_model=PoiCollection)
def read_geojson(skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)):
return get_pois(db, skip=skip, limit=limit)
Still I'm trying to figure out how to map the name and type_id columns from the db model to the PoiProperties in the PoiFeature object.
You want to return the PoiCollection schema (response_model=schemas.PoiCollection) except that you return your database response directly without any formatting. So you have to convert your crud response into your schema response.
# Different function for translate db response to Pydantic response according to your different schema
def make_response_poi_properties(poi):
return PoiFeature(name=poi.name, type_id=poi.type_id)
def make_response_poi_feature(poi):
return PoiFeature(id=poi.id, geometry=poi.geometry,properties=make_response_poi_properties(poi))
def make_response_poi_collection(pois):
response = []
for poi in pois:
response.append(make_response_poi_feature(poi)
return response
#app.get("/geojson", response_model=PoiCollection)
def read_geojson(skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)):
# Call function for translate db data to pydantic data according to your response_model
return make_response_poi_collection(get_pois(db, skip=skip, limit=limit))
or simply use the orm mode inside your different schema class

Categories

Resources