sqlmodel icon indicating copy to clipboard operation
sqlmodel copied to clipboard

There seems to be a bug in using model with aliased fields to define request body

Open boh5 opened this issue 1 year ago • 7 comments

First Check

  • [x] I added a very descriptive title to this issue.
  • [X] I used the GitHub search to find a similar issue and didn't find it.
  • [X] I searched the SQLModel documentation, with the integrated search.
  • [X] I already searched in Google "How to X in SQLModel" and didn't find any information.
  • [X] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [X] I already checked if it is not related to SQLModel but to Pydantic.
  • [X] I already checked if it is not related to SQLModel but to SQLAlchemy.

Commit to Help

  • [x] I commit to help with one of those options 👆

Example Code

from typing import Optional

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str = Field(alias='secretName')
    age: Optional[int] = Field(default=None, index=True)


class HeroPydantic(BaseModel):
    id: Optional[int] = Field(default=None)
    name: str = Field()
    secret_name: str = Field(alias='secretName')
    age: Optional[int] = Field(default=None)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"


connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)


app = FastAPI()


@app.post("/heroes/")
def create_hero(hero: Hero):
    with Session(engine) as session:
        session.add(hero)
        session.commit()
        session.refresh(hero)
        return hero


if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8081)

Description

  1. Create Hero model, the secret_name field aliased to secretName
  2. Post data
{
  "name": "string",
  "secretName": "string",
  "age": 0
}
  1. Then get hero.secret_name is None
  2. If I replace hero: Hero to hero: HeroPydantic, the hero.secret_name can get correct value from post data. But the strainge thing is that I can get correct hero.secret_name by hero: Hero = Hero.parse_obj(hero.dict(by_alias=True))

So it seems that alias argument in sqlmodel seems not working for converting body from post data, I have to write two models in order to achieve that.

Operating System

Windows

Operating System Details

No response

SQLModel Version

0.0.6

Python Version

3.7.6

Additional Context

No response

boh5 avatar Jul 12 '22 15:07 boh5

I found that:

  1. Fastapi generate hero object from request body by function fastapi.dependencies.utils.request_body_to_args, and actually generate hero by this line v_, errors_ = field.validate(value, values, loc=loc)
  2. Then call sqlmodel.SQLModel.validate. In this function, transform alias dict value to field name dict values by values, fields_set, validation_error = validate_model(cls, value), then init model by model = cls(**values)
  3. But I did not set Hero model allow_population_by_field_name = True, so model = cls(**values) can not init correctlly
  4. I have tried set allow_population_by_field_name = True on Hero model, then it works, hero object contains the filed secret_name with value string
  5. If I replace hero: Hero to hero: HeroPydantic, v_, errors_ = field.validate(value, values, loc=loc) can init correctly. But I can't step into pydantic.ModelField.validate in debugger of pycharm-2022.1.3. So I can't find the reason why pydantic and sqlmodel have different results

boh5 avatar Jul 17 '22 07:07 boh5

@boh5 Do you have solution about this problem? I use the latest version and I still have this problem.

Undertone0809 avatar Feb 02 '24 10:02 Undertone0809

Facing the same issue here

anthony2261 avatar Feb 20 '24 09:02 anthony2261

Would be amazing to get this addressed, if possible. It's blocking us from adopting this project.

chenweiss avatar Feb 21 '24 02:02 chenweiss

Looks like there is an open PR to fix this: https://github.com/tiangolo/sqlmodel/pull/774#issuecomment-1883904625

riziles avatar Feb 21 '24 03:02 riziles

Looks like there is an open PR to fix this: #774 (comment)

I think this PR will only partially fix this issue. It will allow a workaround using validation_alias but does not address the issue that alias keyword does not behave the same way as the Pydantic Field alias when it comes to validating models.

Do we know if this issue is on the radar to be addressed?

coneillpj avatar Mar 15 '24 17:03 coneillpj

Facing a similar issue here, the alias is not taken into account when generating the body for the request.

I am also using pydantic's v2 alias_generator, and it works if alias is not defined in the field. If the alias is passed, the body doesn't apply the alias_generator and ignores aliases completely.

sergio-alegria avatar Apr 24 '24 08:04 sergio-alegria