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
Related
I'm trying to get my hands on Tiangolo's SQLModel and I tried the example code in this doc here : https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/read-relationships/
The code is the following :
from typing import List, Optional
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
heroes: List["Hero"] = Relationship(back_populates="team")
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")
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
with Session(engine) as session:
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret’s Bar")
hero_deadpond = Hero(
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
)
hero_rusty_man = Hero(
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
)
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_deadpond)
session.add(hero_rusty_man)
session.add(hero_spider_boy)
session.commit()
session.refresh(hero_deadpond)
session.refresh(hero_rusty_man)
session.refresh(hero_spider_boy)
print("Created hero:", hero_deadpond)
print("Created hero:", hero_rusty_man)
print("Created hero:", hero_spider_boy)
hero_spider_boy.team = team_preventers
session.add(hero_spider_boy)
session.commit()
session.refresh(hero_spider_boy)
print("Updated hero:", hero_spider_boy)
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
team_wakaland = Team(
name="Wakaland",
headquarters="Wakaland Capital City",
heroes=[hero_black_lion, hero_sure_e],
)
session.add(team_wakaland)
session.commit()
session.refresh(team_wakaland)
print("Team Wakaland:", team_wakaland)
hero_tarantula = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
hero_dr_weird = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
hero_cap = Hero(
name="Captain North America", secret_name="Esteban Rogelios", age=93
)
team_preventers.heroes.append(hero_tarantula)
team_preventers.heroes.append(hero_dr_weird)
team_preventers.heroes.append(hero_cap)
session.add(team_preventers)
session.commit()
session.refresh(hero_tarantula)
session.refresh(hero_dr_weird)
session.refresh(hero_cap)
print("Preventers new hero:", hero_tarantula)
print("Preventers new hero:", hero_dr_weird)
print("Preventers new hero:", hero_cap)
def select_heroes():
with Session(engine) as session:
statement = select(Hero).where(Hero.name == "Spider-Boy")
result = session.exec(statement)
hero_spider_boy = result.one()
statement = select(Team).where(Team.id == hero_spider_boy.id)
result = session.exec(statement)
team = result.first()
print("Spider-Boy's team:", team)
print("Spider-Boy's team again:", hero_spider_boy.team)
def main():
create_db_and_tables()
create_heroes()
select_heroes()
if __name__ == "__main__":
main()
However, when I try to run it, I've got the following error :
File "example.py", line 83, in create_heroes
team_preventers.heroes.append(hero_tarantula)
AttributeError: 'Team' object has no attribute 'heroes'
I can't understand why this error occurs since the heroes attribute seems to be well defined in the Team class. And I'm pretty sure an example code in a tutorial couldn't be wrong. Am I missing something? Thanks!
Note : Using last 0.0.6 sqlmodel version on Python 3.9
SQLModel has an outstanding issue with SQLAlchemy 1.4.36+
https://github.com/tiangolo/sqlmodel/issues/315
For now,
pip install sqlalchemy==1.4.35
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.
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])
^^^^
So I followed this tutorial to kombine fastapi & peewee:
link
And due to this tutorial i got those models (peewee):
class User(peewee.Model):
email = peewee.CharField(unique=True, index=True)
hashed_password = peewee.CharField()
is_active = peewee.BooleanField(default=True)
class Meta:
database = db
class Item(peewee.Model):
title = peewee.CharField(index=True)
description = peewee.CharField(index=True)
owner = peewee.ForeignKeyField(User, backref="items")
class Meta:
database = db
And those basemodels (fastapi):
class PeeweeGetterDict(GetterDict):
def get(self, key: Any, default: Any = None):
res = getattr(self._obj, key, default)
if isinstance(res, peewee.ModelSelect):
return list(res)
return res
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
I have this to call the api:
database.db.connect()
database.db.create_tables([User, Item])
database.db.close()
app = FastAPI()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
#app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users():
return list(models.User.select()
This is the base. Now begins my problem / question:
If I call this request (GET "/users/"), I get the following JSON as a result (the data is imaginary its only about the structure)
[
{
"email": "123#test.com"
"id": 1
"is_active": 1
"items": [
{
"title": "item1"
"description": "placeholder"
"id": "1"
"owner_id": "1"
}
]
}
]
This is how it is supposed to be BUT i don't want it exactly like this. I want that I only get the user data, without its items.
So... my question:
How can I get the user data without loading the data of the items?
There is a simple way to do that, response_model_exclude is exactly what you are looking for.
#app.get("/users/", response_model=List[schemas.User], response_model_exclude={"items"}, dependencies=[Depends(get_db)])
def read_users():
return list(models.User.select()
Ref: FastAPI Response Model
I have a certain number of python classes that i would like to map to tables in a database with python sqlalchemy. I saw examples where the mapped class is derived from an sqlalchemy base class. I don't want to do that. Is there any other way ?
For example, how to map this simple class ?
class Person:
def __init__(self, firstname: str = "x", name: str = "y", age: int = 0):
self.firstname = firstname
self.name = name
self.age = age
def __str__(self) -> str:
return f"[{self.__firstname},{self.__name},{self.__age}]"
#property
def id(self):
return self.__id
#property
def firstname(self) -> str:
return self.__firstname
#property
def name(self) -> str:
return self.__name
#property
def age(self) -> int:
return self.__age
# setters
#id.setter
def id(self, id: int):
if not isinstance(id,int) or id<=0:
raise MyException(f"...")
#firstname.setter
def firstname(self, firstname: str):
if Utils.is_string_ok(firstname):
self.__firstname = firstname.strip()
else:
raise MyException("...")
#name.setter
def name(self, name: str):
if Utils.is_string_ok(name):
self.__name = name.strip()
else:
raise MyException("...")
#age.setter
def age(self, age: int):
error = False
if isinstance(age, int):
if age >= 0:
self.__age = age
else:
error = True
else:
error = True
if error:
raise MyException("...")
I want to map it to a table with columns (col1,col2,col3,col4) for example (arbitrary names different from class properties).
For anyone interested, i finally got it (below i changed the identifiers of my working code to match the original post):
# imports
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData
from sqlalchemy.orm import mapper, sessionmaker
from Person import Person
# mysql database
engine = create_engine("mysql+mysqlconnector://root#localhost/dbpersonnes")
# metadata
metadata = MetaData()
# table
persons_table = Table("persons", metadata,
Column('col1', Integer, primary_key=True),
Column('col2', String(30), nullable=False),
Column("col3", String(30), nullable=False),
Column("col4", Integer, nullable=False)
)
# mapping
mapper(Person, persons_table, properties={
'id': persons_table.c.col1,
'firstname': persons_table.c.col2,
'name': persons_table.c.col3,
'age': persons_table.c.col4,
})
# session factory
Session = sessionmaker()
Session.configure(bind=engine)
# session
session = Session()
# insert
session.add(Personne(67, "x", "y", 10))
session.commit()
# query
personnes = session.query(Personne).all()
# logs
for personne in personnes:
print(personne)