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
Related
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
I'm having trouble understanding how to display the children data in a one-to-many relationship using FastAPI and SQLModel. I'm using Python 3.10.3, FastAPI version 0.78.0 and SQLModel version 0.0.6. Here's a simplified version of the parent/child database models:
from datetime import datetime
from email.policy import default
from sqlalchemy import UniqueConstraint
from sqlmodel import Field, SQLModel, Relationship
class CustomerBase(SQLModel):
__table_args__ = (UniqueConstraint("email"),)
first_name: str
last_name: str
email: str
active: bool | None = True
class Customer(CustomerBase, table=True):
id: int | None =Field(primary_key=True, default=None)
class CustomerCreate(CustomerBase):
pass
class CustomerRead(CustomerBase):
id: int
class CustomerReadWithCalls(CustomerRead):
calls: list["CallRead"] = []
class CallBase(SQLModel):
duration: int
cost_per_minute: int | None = None
customer_id: int | None = Field(default=None, foreign_key="customer.id")
created: datetime = Field(nullable=False, default=datetime.now().date())
class Call(CallBase, table=True):
id: int | None = Field(primary_key=True)
class CallCreate(CallBase):
pass
class CallRead(CallBase):
id: int
class CallReadWithCustomer(CallRead):
customer: CustomerRead | None
Here is the API Route:
from fastapi import APIRouter, HTTPException, Depends, Query
from rbi_app.crud.customer import (
get_customers,
get_customer,
)
from rbi_app.models import (
CustomerRead,
CustomerReadWithCalls,
)
from rbi_app.database import Session, get_session
router = APIRouter()
#router.get("/customers/", status_code=200, response_model=list[CustomerRead])
def read_customers(
email: str = "",
offset: int = 0,
limit: int = Query(default=100, lte=100),
db: Session = Depends(get_session)
):
return get_customers(db, email, offset=offset, limit=limit)
#router.get("/customers/{customer_id}", status_code=200, response_model=CustomerReadWithCalls)
def read_customer(id: int, db: Session = Depends(get_session)):
customer = get_customer(db, id)
if customer is None:
raise HTTPException(status_code=404, detail=f"Customer not found for {id=}")
return customer
And here are the queries to the database the API Route endpoints make:
from sqlmodel import select
from rbi_app.database import Session
from rbi_app.models import (
Customer,
CustomerCreate,
)
# from rbi_app.schemas.customer import CustomerCreate
def get_customer(db: Session, id: int):
return db.get(Customer, id)
def get_customers(db: Session, email: str = "", offset: int = 0, limit: int = 100):
if email:
return db.exec(select(Customer).where(Customer.email == email)).first()
return db.exec(select(Customer).offset(offset).limit(limit).order_by(Customer.id)).all()
When I navigate to a route to get all a customer my query runs and I get a customer, but there is no "calls" list attribute in the customer. The OpenAPI display shows a "calls" attribute, but it's empty.
What am I doing wrong?
The issue here seems to be that you did not define the relationship on the Customer model (or the Call module). Since you query the database with the Customer model and it has no calls attribute, none of that data is present in the object returned by your get_customer function.
Even though your route defines the CustomerReadWithCalls as a response model, upon calling it, the object of that class can only ever be instantiated with the data returned by your route handler function, which is your Customer instance in this case. Since that does not even have the calls attribute (let alone the data), the CustomerReadWithCalls object is essentially created with the default value that you defined for the calls field -- the empty list.
Adding
calls: list["Call"] = Relationship(back_populates="customer")
to your Customer model should be enough.
(But as a side note, for me the route documentation only works properly, when I explicitly update the references on the CustomerReadWithCalls model after the CallRead definition.)
Here is a full working example.
models.py
from datetime import datetime
from sqlalchemy import UniqueConstraint
from sqlmodel import Field, Relationship, SQLModel
class CustomerBase(SQLModel):
__table_args__ = (UniqueConstraint("email"),)
first_name: str
last_name: str
email: str
active: bool | None = True
class Customer(CustomerBase, table=True):
id: int | None = Field(primary_key=True, default=None)
calls: list["Call"] = Relationship(back_populates="customer")
class CustomerCreate(CustomerBase):
pass
class CustomerRead(CustomerBase):
id: int
class CustomerReadWithCalls(CustomerRead):
calls: list["CallRead"] = []
class CallBase(SQLModel):
duration: int
cost_per_minute: int | None = None
customer_id: int | None = Field(default=None, foreign_key="customer.id")
created: datetime = Field(nullable=False, default=datetime.now().date())
class Call(CallBase, table=True):
id: int | None = Field(primary_key=True, default=None)
customer: Customer | None = Relationship(back_populates="calls")
class CallCreate(CallBase):
pass
class CallRead(CallBase):
id: int
# After the definition of `CallRead`, update the forward reference to it:
CustomerReadWithCalls.update_forward_refs()
class CallReadWithCustomer(CallRead):
customer: CustomerRead | None
routes.py
from fastapi import FastAPI, HTTPException, Depends
from sqlmodel import Session, SQLModel, create_engine
from .models import CustomerReadWithCalls, Customer, Call
api = FastAPI()
sqlite_file_name = 'database.db'
sqlite_url = f'sqlite:///{sqlite_file_name}'
engine = create_engine(sqlite_url, echo=True)
#api.on_event('startup')
def initialize_db():
SQLModel.metadata.drop_all(engine)
SQLModel.metadata.create_all(engine)
# For testing:
with Session(engine) as session:
customer = Customer(first_name="Foo", last_name="Bar", email="foo#bar.com")
call1 = Call(duration=123)
call2 = Call(duration=456)
customer.calls.extend([call1, call2])
session.add(customer)
session.commit()
def get_session() -> Session:
session = Session(engine)
try:
yield session
finally:
session.close()
def get_customer(db: Session, id: int):
return db.get(Customer, id)
#api.get("/customers/{customer_id}", status_code=200, response_model=CustomerReadWithCalls)
def read_customer(customer_id: int, db: Session = Depends(get_session)):
customer = get_customer(db, customer_id)
if customer is None:
raise HTTPException(status_code=404, detail=f"Customer not found for {customer_id=}")
return customer
Starting the API server and sending GET to http://127.0.0.1:8000/customers/1 gives me
{
"first_name": "Foo",
"last_name": "Bar",
"email": "foo#bar.com",
"active": true,
"id": 1,
"calls": [
{
"duration": 123,
"cost_per_minute": null,
"customer_id": 1,
"created": "2022-08-16T00:00:00",
"id": 1
},
{
"duration": 456,
"cost_per_minute": null,
"customer_id": 1,
"created": "2022-08-16T00:00:00",
"id": 2
}
]
}
Hope this helps.
The following is my definition of a sqlmodel orm class and its related orm classes:
class CoursePostgresORM(CourseWithMetadataBase, table=True):
__tablename__ = '...'
__table_args__ = {'schema': '...', }
id_: str = Field(alias='id', sa_column=Column('id', primary_key=True))
....
skills: List['CourseSkillLink'] = Relationship(back_populates='course')
regions: List['CourseRegionLinkPostgresORM'] = Relationship(back_populates='course')
class Config:
allow_population_by_field_name = True
def to_elasticsearch_doc(self):
skills = [skill for skill in self.skills]
doc = {
...
}
return doc
class CourseSkillLink(SQLModel, table=True):
__tablename__ = '...'
__table_args__ = {'schema': '...', }
id_: int = Field(alias='id', sa_column=Column('id', primary_key=True))
course_id: str = Field(foreign_key='course_catalog.courses.id')
skill_id: int = Field(foreign_key='public.skills.id')
created_at: datetime
updated_at: datetime
course: 'CoursePostgresORM' = Relationship(back_populates='skills')
skill: 'SkillPostgresOrm' = Relationship(back_populates='courses')
class SkillPostgresOrm(SQLModel, table=True):
__tablename__ = 'skills'
__table_args__ = {'schema': 'public'}
id_: int = Field(alias='id', sa_column=Column('id', primary_key=True))
skill_level: int
parent_id: Optional[int]
deactivated_at: Optional[datetime]
created_at: Optional[datetime]
updated_at: Optional[datetime]
last_updated_by: Optional[str]
name: str
is_selected_skill: bool
courses: List['CourseSkillLink'] = Relationship(back_populates='skill')
When run the following code on my local, everything went fine.
with Session(POSTGRES) as session:
res = session.exec(course_stm)
However, I was thrown with the following error while running this in one of the aws lambda functions.
"errorMessage": "'CoursePostgresORM' object has no attribute 'skills'",
"errorType": "AttributeError",
"stackTrace": [
" File \"/var/task/index_to_es.py\", line 21, in lambda_handler\n courses = fetch_courses_from_sql()\n",
" File \"/var/task/index_to_es.py\", line 38, in fetch_courses_from_sql\n es_docs = [row.to_elasticsearch_doc() for row in res.all()]\n",
" File \"/var/task/index_to_es.py\", line 38, in <listcomp>\n es_docs = [row.to_elasticsearch_doc() for row in res.all()]\n",
" File \"/opt/python/appliedx_data_object/course.py\", line 196, in to_elasticsearch_doc\n skills = [skill for skill in self.skills]\n"
I've spent the whole day trying to figure this out but had no luck. Any input is welcome. Thanks
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])
^^^^