tiangolo / fastapi

FastAPI framework, high performance, easy to learn, fast to code, ready for production
https://fastapi.tiangolo.com/
MIT License
73.34k stars 6.18k forks source link

Query fields with numeric types appear not to enforce allow_inf_nan=False, whereas gt=0, etc are enforced #11580

Open Kludex opened 1 month ago

Kludex commented 1 month ago

Discussed in https://github.com/tiangolo/fastapi/discussions/11577

Originally posted by **pat-lasswell** May 14, 2024 ### First Check - [X] I added a very descriptive title here. - [X] I used the GitHub search to find a similar question and didn't find it. - [X] I searched the FastAPI documentation, with the integrated search. - [X] I already searched in Google "How to X in FastAPI" 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 FastAPI but to [Pydantic](https://github.com/pydantic/pydantic). - [X] I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui). - [X] I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc). ### Commit to Help - [X] I commit to help with one of those options 👆 ### Example Code ```python import aiohttp import asyncio import math import uvicorn from fastapi import FastAPI, Query, Request, Response from http import HTTPStatus from typing import Annotated app = FastAPI() @app.get('/') async def get( x: Annotated[float | None, Query(gt=0, description='x')] = 1, y: Annotated[float | None, Query(allow_inf_nan=False, description='y')] = 0) -> str: assert x > 0 assert not math.isnan(y) and not math.isinf(y) return 'OK' async def main(): config = uvicorn.Config(app, host='127.0.0.1', port=8001) server = uvicorn.Server(config) task = asyncio.create_task(server.serve()) await asyncio.sleep(.1) async with aiohttp.ClientSession() as session: async with session.get('http://127.0.0.1:8001/?x=-1') as response: assert response.status == HTTPStatus.UNPROCESSABLE_ENTITY async with session.get('http://127.0.0.1:8001/?y=inf') as response: assert response.status == HTTPStatus.UNPROCESSABLE_ENTITY await server.shutdown() if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main()) ``` ### Description I would expect the `allow_inf_nan` parameter to `Query` to restrict valid values in the same way that `gt`, etc do, resulting in a HTTP 422 status code when the constraint is violated. Instead, `inf` and `nan` values are passed to the route handler. To reproduce, save the example code above in the current directory in a file named `bug.py`, then docker run -it --rm -w `pwd` -v `pwd`:`pwd` python:3.10 bash and in the bash prompt inside the container pip install aiohttp==3.9.5 fastapi==0.111.0 uvicorn==0.29.0 python bug.py The output will be similar to INFO: Started server process [4301] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit) INFO: 127.0.0.1:58094 - "GET /?x=-1 HTTP/1.1" 422 Unprocessable Entity INFO: 127.0.0.1:58094 - "GET /?y=inf HTTP/1.1" 500 Internal Server Error ERROR: Exception in ASGI application Traceback (most recent call last): File "/layer_svc/test/env/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 411, in run_asgi result = await app( # type: ignore[func-returns-value] File "/layer_svc/test/env/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 69, in __call__ return await self.app(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__ await super().__call__(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__ await self.middleware_stack(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__ raise exc File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__ await self.app(scope, receive, _send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in __call__ await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app raise exc File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app await app(scope, receive, sender) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/routing.py", line 756, in __call__ await self.middleware_stack(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/routing.py", line 776, in app await route.handle(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/routing.py", line 297, in handle await self.app(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/routing.py", line 77, in app await wrap_app_handling_exceptions(app, request)(scope, receive, send) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app raise exc File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app await app(scope, receive, sender) File "/layer_svc/test/env/lib/python3.10/site-packages/starlette/routing.py", line 72, in app response = await func(request) File "/layer_svc/test/env/lib/python3.10/site-packages/fastapi/routing.py", line 278, in app raw_response = await run_endpoint_function( File "/layer_svc/test/env/lib/python3.10/site-packages/fastapi/routing.py", line 191, in run_endpoint_function return await dependant.call(**values) File "/layer_svc/app/bug.py", line 85, in get assert not math.isnan(y) and not math.isinf(y) AssertionError Traceback (most recent call last): File "/layer_svc/app/bug.py", line 111, in asyncio.get_event_loop().run_until_complete(main()) File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete return future.result() File "/layer_svc/app/bug.py", line 106, in main assert response.status == HTTPStatus.UNPROCESSABLE_ENTITY AssertionError The expected response is INFO: Started server process [4301] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit) INFO: 127.0.0.1:58094 - "GET /?x=-1 HTTP/1.1" 422 Unprocessable Entity INFO: 127.0.0.1:58094 - "GET /?y=inf HTTP/1.1" 422 Unprocessable Entity ### Operating System Linux ### Operating System Details _No response_ ### FastAPI Version 0.111.0 ### Pydantic Version 2.7.1 ### Python Version 3.10.14 ### Additional Context _No response_