fastapi / full-stack-fastapi-template

Full stack, modern web application template. Using FastAPI, React, SQLModel, PostgreSQL, Docker, GitHub Actions, automatic HTTPS and more.
MIT License
27.21k stars 4.86k forks source link

Why do we use `jsonable_encoder` in crud/base:update #430

Open haykkh opened 3 years ago

haykkh commented 3 years ago

I'm working through a project that was (at one point a long time ago) based on this stack.

https://github.com/tiangolo/full-stack-fastapi-postgresql/blob/490c554e23343eec0736b06e59b2108fdd057fdc/%7B%7Bcookiecutter.project_slug%7D%7D/backend/app/app/crud/base.py#L49

I've run into an issue with SQLAlchemy hybrid_properties not being included when jsonable_encoder is called on db_obj. This got me thinking as to why we even encode db_obj into obj_data here, seeing as we only use obj_data to iterate through the attributes of db_obj.

To fix this issue I've removed the jsonable_encoder logic here, and instead check if db_obj contains the attribute with hasattr (see below). Am I missing something obvious as to why I should be using jsonable_encoder here? Or are my modifications safe?

My changes:

def update(
    self,
    db: Session,
    *,
    db_obj: ModelType,
    obj_in: Union[UpdateSchemaType, Dict[str, Any]]
) -> ModelType:
    if isinstance(obj_in, dict):
        update_data = obj_in
    else:
        update_data = obj_in.dict(exclude_unset=True)
    for field in update_data:
        if hasattr(db_obj, field):
            setattr(db_obj, field, update_data[field])
    db.add(db_obj)
    db.commit()
    db.refresh(db_obj)
    return db_obj
shulcsm commented 3 years ago

Makes sense. Also, i think missing attr should result in exception.

krconv commented 2 years ago

I am curious of the same thing; ran into two separate issues caused by this;

Both of these issues are fixed with this:

def update(
    self,
    db: Session,
    *,
    db_obj: ModelType,
    obj_in: Union[UpdateSchemaType, Dict[str, Any]]
) -> ModelType:
    if isinstance(obj_in, dict):
        update_data = obj_in
    else:
        update_data = obj_in.dict(exclude_unset=True)
    for field in update_data:
        setattr(db_obj, field, update_data[field])
    db.add(db_obj)
    db.commit()
    db.refresh(db_obj)
    return db_obj