I am just a starter in FastAPI/Pydantic & SqlAlchemy - I have two model Post and Category where I want Post should fetch Category name instead of only id
when I try to use the below code it gives following error in console
Any help in solving this is much appreciated thank you!
response -> 1 -> category_name
field required (type=value_error.missing)
post.py models
class Post(Base):
__tablename__="post"
id = Column(Integer, primary_key=True, index=True)
title=Column(String(50))
user_id=Column(Integer, ForeignKey("users.id"))
category_id = Column(Integer, ForeignKey("category.id"))
category_name=relationship("Category", backref="post")
class Category(Base):
__tablename__="category"
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
Pydantic models
class CategoryGet(BaseModel):
id:int
name:str
class Config:
orm_mode=True
class Post(BaseModel):
id = int
title=str
user_id=int
category_id = int
category_name=CategoryGet
class Config:
orm_mode=True
My mainapp.py
router = APIRouter()
#router.get("/", response_model=List[schemas.VehicleGet])
def get_vehicle(db: Session = Depends(get_db), skip: int = 0, limit: int = 50) -> Any:
vehicle = crud.post.get_multi(db, skip=skip, limit=limit)
return vehicle
Pydantic models attributes uses : instead of =
class Post(BaseModel):
id: int
title: str
user_id: int
category_id: int
category_name: CategoryGet
class Config:
orm_mode = True
I guess the problem is that, you forgot orm_mode=True config for you Post model and consequently it is unable to recognize the category_name field. I hope this will solve but if not you could check this thread where an example and some clarification about relationship handling with pydantic.
Related
I have a question regarding the way I can sort a list of results in a many to one relationship using GraphQL.
Let's take the example from graphene-sqlalchemy.
The schema is:
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
name = Column(String)
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
name = Column(String)
hired_on = Column(DateTime, default=func.now())
department_id = Column(Integer, ForeignKey('department.id'))
department = relationship(
Department,
backref=backref('employees',
uselist=True,
cascade='delete,all'))
And here is my Schema :
import graphene
from models import (Employee as EmployeeModel, Department as DepartmentModel)
from graphene_sqlalchemy import (
SQLAlchemyObjectType
)
class Employee(SQLAlchemyObjectType):
class Meta:
model = EmployeeModel
class Department(SQLAlchemyObjectType):
class Meta:
model = DepartmentModek
class Query(graphene.ObjectType):
find_departments = graphene.List(of_type = Department)
def resolve_find_departments(root, info) :
return db.session.query(DepartmentModel).all()
In my query I would like to access the list of employee from my department and order the results by name. My query could look like :
query findDepartments(){
findDepartments{
department{
employee(orderBy : 'name'){
name
}
}
}
}
But this query won't work. How can I achieve the sorting of the nested field employee ?
There are multiple ways to do this, but a simple way is to create a custom resolver with the ordering that you want, i.e. something like
class Query(graphene.ObjectType):
find_departments = graphene.List(of_type = Department)
find_departments_by_name = graphene.List(of_type = Department)
def resolve_find_departments(root, info):
return db.session.query(DepartmentModel).all()
def resolve_find_departments_by_name(root, info):
return db.session.query(DepartmentModel).order_by("name").all() # this is a guess of how sqlalchemy sorting syntax
You can override resolver for employees:
import graphene
from models import (Employee as EmployeeModel, Department as DepartmentModel)
from graphene_sqlalchemy import (
SQLAlchemyObjectType
)
class Employee(SQLAlchemyObjectType):
class Meta:
model = EmployeeModel
class Department(SQLAlchemyObjectType):
class Meta:
model = DepartmentModel
employees = graphene.List(of_type=Employee, order_by=graphene.String())
def resolve_employees(root, info, **kwargs):
query = self.employees
order_arg = kwargs.get('order_by')
if order_org:
# should check that `order_arg` is a column of `Employee`
query = query.order_by(order_arg)
return query.all()
class Query(graphene.ObjectType):
find_departments = graphene.List(of_type = Department)
def resolve_find_departments(root, info) :
return db.session.query(DepartmentModel).all()
I need to get one value from relationship Many-to-one model in pydantic BaseModel.
How can I do this?
My children class
class Picnic(Base):
__tablename__ = 'picnic'
id = Column(Integer, primary_key=True, autoincrement=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
city = relationship('City', backref='picnics')
class City(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True, nullable=False, index=True)
I need to get city name value :
class Picnics(BaseModel):
id: int
# city: str[CityBaseInDB.name] not working
# city: str = Field(source='city.name') not working
# city_name: str not working
class Config:
orm_mode: bool = True
from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship, sessionmaker, Query
from pydantic import BaseModel
Base = declarative_base()
engine = create_engine("sqlite://", echo=True)
class Picnic(Base):
__tablename__ = 'picnic'
id = Column(Integer, primary_key=True, autoincrement=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
city = relationship('City', backref='picnics')
class City(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True, nullable=False, index=True)
class PicnicModel(BaseModel):
id: int
# city: str[CityBaseInDB.name] not working
# city: str = Field(source='city.name') not working
city_name: str
class Config:
orm_mode: bool = True
picnic = Picnic(city=City(name='Shenzhen'))
Base.metadata.create_all(engine)
LocalSession = sessionmaker(bind=engine)
db: Session = LocalSession()
db.add(picnic)
db.commit()
q: Query = db.query(Picnic.id, City.name.label('city_name'))
q = q.select_from(Picnic).join(City)
row = q.one_or_none()
model = PicnicModel.from_orm(row)
print(model)
You need to have a pydantic model for your parent model too and put it as the type in the relationship field.
from pydantic import BaseModel
class City(BaseModel):
id: int
name: str
class Config:
orm_mode: bool = True
class Picnics(BaseModel):
id: int
city: City
class Config:
orm_mode: bool = True
Description
Previously, my query returned the contents of a single Stories table. Now I want to add more information: I need to output the prizes_count for each Story. There is no field prizes_count in the Stories table so I made the following query.
db.query(models.Stories, func.count(models.Stories.prizes).label("prizes_count")).join(models.Prizes)\
.group_by(models.Stories.id).all()
But I have two problems with it.
I get validation errors from Pydantic, because this query returns a list of tuples like (<database.models.Stories object at 0x0000026BB0055E20>, 1). I have to insert the prizes_count value into the Stories object or vice versa, pull all fields into the tuple. I can do it manually, of course, but I think there is a better way.
With this query I lose all stories with 0 prizes because my join ignores them.
Code
endpoint
#app.get("/stories/", response_model=List[schemas.StoryFullInfo])
def get_stories(db: Session = Depends(get_db)):
return crud.get_stories(db)
crud
def get_stories(db: Session):
return db.query(models.Stories, func.count(models.Stories.prizes).label("prizes_count")).join(models.Prizes)\
.group_by(models.Stories.id).all()
models
class Stories(Base):
__tablename__ = "stories"
id = Column(INTEGER(unsigned=True), primary_key=True)
title = Column(String(length=128), index=True)
text = Column(String(length=1000))
author_id = Column(INTEGER(unsigned=True), ForeignKey("users.id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False)
status = Column(TINYINT(unsigned=True), server_default="0")
genre_type = Column(TINYINT(unsigned=True), server_default="0")
likes_count = Column(INTEGER(unsigned=True), server_default="0")
image = Column(Text)
added_to_best_by = Column(INTEGER(unsigned=True))
creation_DT = Column(DateTime, server_default=func.now())
change_status_DT = Column(DateTime)
author = relationship("Users", back_populates="stories")
comments = relationship("Comments", back_populates="story")
prizes = relationship("Prizes", back_populates="story")
class Prizes(Base):
__tablename__ = "prizes"
id = Column(INTEGER(unsigned=True), primary_key=True)
title = Column(String(length=128), nullable=False)
image_id = Column(TINYINT(unsigned=True))
story_id = Column(INTEGER(unsigned=True), ForeignKey("stories.id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False)
user_id = Column(INTEGER(unsigned=True), ForeignKey("users.id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False)
text = Column(String(length=512), nullable=False)
creation_DT = Column(DateTime, server_default=func.now())
story = relationship("Stories", back_populates="prizes")
author = relationship("Users", back_populates="prizes")
schemas
class StoryBaseInfo(BaseModel):
id: int
title: str = None
author_id: int
class Config:
orm_mode = True
class StoryUpdateInfo(StoryBaseInfo):
#title: str = None
text: str = None
status: int
genre_type: int
likes_count: int
image: str = None
added_to_best_by: int = None
change_status_DT: datetime = None
class Config:
orm_mode = True
class StoryFullInfo(StoryUpdateInfo):
creation_DT: datetime
author: UserBaseInfo
prizes_count: int
class Config:
orm_mode = True
class PrizeBaseInfo(BaseModel):
id: int
story_id: int
class Config:
orm_mode = True
class PrizeInfo(PrizeBaseInfo):
title: str
image_id: int
text: str
creation_DT: datetime
author: UserBaseInfo
story: StoryBaseInfo
class Config:
orm_mode = True
Well, it turns out I was thinking in the wrong direction when I asked this. The problem is solved by the features of SQLAlchemy. I can count prizes by using my configured relationship.
My solution is to add the hybrid property to Stories SQLAlchemy model
class Stories(Base):
__tablename__ = "stories"
id = Column(INTEGER(unsigned=True), primary_key=True)
title = Column(String(length=128), index=True)
text = Column(String(length=1000))
author_id = Column(INTEGER(unsigned=True), ForeignKey("users.id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False)
status = Column(TINYINT(unsigned=True), server_default="0")
genre_type = Column(TINYINT(unsigned=True), server_default="0")
likes_count = Column(INTEGER(unsigned=True), server_default="0")
image = Column(Text)
added_to_best_by = Column(INTEGER(unsigned=True))
creation_DT = Column(DateTime, server_default=func.now())
change_status_DT = Column(DateTime)
author = relationship("Users", back_populates="stories")
comments = relationship("Comments", back_populates="story")
prizes = relationship("Prizes", back_populates="story")
#hybrid_property
def prizes_count(self):
return len(self.prizes)
And then the following query will satisfy the Pydantic scheme.
def get_stories(db: Session):
return db.query(models.Stories).all()
So I'm building an app and I'm trying to save new changes to my database but when I try to commit the changes in the flask using db.session.commit() it returns me the following error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'products.country_id' could not find table 'countries' with which to generate a foreign key to target column 'id'
In my models.py I have the following:
from app import db
from . import db
from datetime import datetime
def now():
return datetime.now()
class Countries(db.Model):
__tablename__ = 'countries'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
code = db.Column(db.String(45))
def __repr__(self):
return f'Id {self.id}'
class Categories(db.Model):
__tablename__ = 'categories'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
def __repr__(self):
return f'Id {self.id}'
class Brands(db.Model):
__tablename__ = 'brands'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
logo = db.Column(db.String(5000))
feed = db.Column(db.String(5000))
feed_type = db.Column(db.String(45))
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
awinmid = db.Column(db.Integer)
def __repr__(self):
return f'Id {self.id}'
class Products(db.Model):
__tablename__ = 'products'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
url = db.Column(db.Text)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
price = db.Column(db.Float)
currency = db.Column(db.String(45))
discount_price = db.Column(db.Float)
shipping = db.Column(db.Float)
brand_id = db.Column(db.Integer, db.ForeignKey('brands.id'))
Am I doing anything wrong when associating a column in products with a foreign key? This is the first time I encounter this error so I'm really lost on what to do right now.
To fix I just added the schema to the db.ForeignKey and it worked
Example:
db.ForeignKey('products_data.countries.id')
PS:
Not my idea. Just wanted to post the answer in case someone visits the post later with the same problem.
Gord Thompson thanks for the help!
First of all, I don t see any table Categories. Secondly, you copy pasted your schema from the Products table into your Countries one.
PS: By default sqlalchemy gives the tables the name of the class (lower cased). So your __tablename__='products' does nothing actually.
EDIT:
The problem with your code lies in how you set the __table_args__ attribute. You assign an object to it, which by their specifications is wrong.
Take a look at the following example and modify your code accordingly
__table_args__ = ({'schema': 'products_data'})
Also for further reference, take a look at this https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/table_config.html
I have model like this:
class CreatedMixin(DeclarativeBase):
__abstract__ = True
#declared_attr
def updated_by(cls):
return Column(Integer, ForeignKey('user.user_id',
onupdate="cascade", ondelete="restrict"),
onupdate=CURRENT_USER_ID)
updated_at = Column(DateTime, nullable=False, default=dt.now(),
onupdate=dt.now())
And auth with repoze.what-quickstart. How to get CURRENT_USER_ID?
I think you could do the following
identity = request.environ.get('repoze.who.identity')
and the gettin the user id
id = identity['user'].id