Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression: FastAPI request fails when query param defined as pydantic model with alias being a python keyword #4467

Closed
6 of 16 tasks
ospikovets opened this issue Sep 2, 2022 · 7 comments
Labels
Change Suggested alteration to pydantic, not a new feature nor a bug

Comments

@ospikovets
Copy link

ospikovets commented Sep 2, 2022

Initial Checks

  • I have searched GitHub for a duplicate issue and I'm sure this is something new
  • I have searched Google & StackOverflow for a solution and couldn't find anything
  • I have read and followed the docs and still think this is a bug
  • I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like FastAPI or mypy)

Description

Starting from pydantic version 1.10.0, FastAPI request fails when query param defined as pydantic model with alias being a python keyword. The request returns 422 status with the response body:

{"detail":[{"loc":["query","extra_data"],"msg":"field required","type":"value_error.missing"}]}

Similar behavior with exactly the same error message was also seeing for pydantic 1.9.2 in case, query param alias contained a non valid python variable name, like:

class Params(BaseModel):
    from_year: int = Query(2022, alias="year.gte")

There is a related issue in FastAPI repository: tiangolo/fastapi#4842

However, with pydantic >= 1.10.0 the issue also started to occur when query param alias is a valid python keyword.

For the context: fastapi==0.81.0

Example Code

from fastapi import Depends
from fastapi import FastAPI
from fastapi import Query
from pydantic import BaseModel

app = FastAPI()


class Params(BaseModel):
    from_year: int = Query(2022, alias="from")


@app.get("/")
def get_error(params: Params = Depends()):
    return {"from_year": params.from_year}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run("main:app", reload=True)

Python, Pydantic & OS Version

pydantic version: 1.10.1
pydantic compiled: True
install path: /Users/ospikovets/.virtualenvs/pythonProject1/lib/python3.9/site-packages/pydantic
python version: 3.9.13 (main, May 24 2022, 21:13:51)  [Clang 13.1.6 (clang-1316.0.21.2)]
platform: macOS-12.5.1-arm64-arm-64bit
optional deps. installed: ['typing-extensions']

Affected Components

@ospikovets ospikovets added bug V1 Bug related to Pydantic V1.X unconfirmed Bug not yet confirmed as valid/applicable labels Sep 2, 2022
@aminalaee
Copy link
Contributor

@ospikovets Does that example work with previous versions of Pydantic? I tested 1.9 and still breaks with the same error.

Just out of curiosity, I haven't seen that usage in FastAPI docs, is that supported?

@ospikovets
Copy link
Author

ospikovets commented Sep 2, 2022

@aminalaee just verified with both pydantic==1.9.0 and pydantic==1.9.2

The example works well.

GET http://127.0.0.1:8000/ returns {"from_year":2022}

pydantic version: 1.9.0
pydantic compiled: True
install path: /Users/ospikovets/.virtualenvs/pythonProject1/lib/python3.9/site-packages/pydantic
python version: 3.9.13 (main, May 24 2022, 21:13:51)  [Clang 13.1.6 (clang-1316.0.21.2)]
platform: macOS-12.5.1-arm64-arm-64bit
optional deps. installed: ['typing-extensions']
pydantic version: 1.9.2
pydantic compiled: True
install path: /Users/ospikovets/.virtualenvs/pythonProject1/lib/python3.9/site-packages/pydantic
python version: 3.9.13 (main, May 24 2022, 21:13:51)  [Clang 13.1.6 (clang-1316.0.21.2)]
platform: macOS-12.5.1-arm64-arm-64bit
optional deps. installed: ['typing-extensions']

However, once I install pydantic==1.10.0, the above example started to return 422 status with the error:

{"detail":[{"loc":["query","extra_data"],"msg":"field required","type":"value_error.missing"}]}

pydantic version: 1.10.0
pydantic compiled: True
install path: /Users/ospikovets/.virtualenvs/pythonProject1/lib/python3.9/site-packages/pydantic
python version: 3.9.13 (main, May 24 2022, 21:13:51)  [Clang 13.1.6 (clang-1316.0.21.2)]
platform: macOS-12.5.1-arm64-arm-64bit
optional deps. installed: ['typing-extensions']

As to the support of such a behavior in FastAPI. It is not explicitly mentioned that a pydantic model can be used as dependency, however, there is a section with an example of usage for classes as dependencies in the context of query parameters.

https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/

Additionally, as mentioned above, the example works for both pydantic==1.9.0 and pydantic==1.9.2, but stoped working in 1.10.0

Lastly, if I change the alias="from" to any of alias="with" or alias="import" - the error stays, but when I set alias="foo" - the api become functional again.

@samuelcolvin samuelcolvin added Change Suggested alteration to pydantic, not a new feature nor a bug and removed bug V1 Bug related to Pydantic V1.X unconfirmed Bug not yet confirmed as valid/applicable labels Sep 5, 2022
@samuelcolvin
Copy link
Member

This is caused by #4011, I can confirm the behaviour change however I do not think it's really a bug - but rather an unexpected side effect of a reasonable change.

Workarounds:

  • fix this in FastAPI - should be possible not not trivial
  • wait for V2 and agree a to avoid this problem then
  • leave it as is and document it

@tiangolo any thoughts?

@tiangolo
Copy link
Member

This is actually a workaround/hack, it is not officially supported in FastAPI, and the combination of Pydantic generating an automatic __init__ method plus using Depends() to tell FastAPI to use the class directly made it work, in a strange way, but is not really documented or supported in FastAPI.


I intend to support something similar, as a future feature, but it would look different. More like:

from fastapi import FastAPI
from fastapi import Query
from pydantic import BaseModel

app = FastAPI()


class Params(BaseModel):
    from_year: int = Field(default=2022, alias="from")


@app.get("/")
def get_error(params: Params = Query()):
    return {"from_year": params.from_year}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run("main:app", reload=True)

So that it would behave similar to using Body(). And it wouldn't need changing the Pydantic model itself using something that is not a Field() but the custom Query().

@ospikovets
Copy link
Author

@tiangolo this looks like an excellent feature, that will solve this problem! Thank you!

@tiangolo
Copy link
Member

Great! I think we could move this to the FastAPI repo and close this issue here, as it would be more FastAPI-specific.

@samuelcolvin
Copy link
Member

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Change Suggested alteration to pydantic, not a new feature nor a bug
Projects
None yet
Development

No branches or pull requests

4 participants