From dbb01d23aaccd2cc6e4fdbb870047bff1de8b732 Mon Sep 17 00:00:00 2001 From: Rauan Date: Wed, 26 Nov 2025 09:30:01 +0500 Subject: [PATCH 1/5] feat: Add support for HTTP QUERY method - Add `QUERY` to `METHODS_WITH_BODY` in `fastapi/openapi/constants.py` to correctly generate OpenAPI schemas for bodies in QUERY requests. - Add `query()` decorator method to `APIRouter` in `fastapi/routing.py`. - Add `query()` decorator method to `FastAPI` class in `fastapi/applications.py`. This allows users to define routes using the HTTP QUERY method (e.g., `@app.query("/search")`), enabling safe, idempotent requests that utilize a request body for complex query parameters, following the IETF HTTP QUERY method draft. --- fastapi/applications.py | 378 ++++++++++++++++++++++++++++++++++ fastapi/openapi/constants.py | 2 +- fastapi/routing.py | 382 +++++++++++++++++++++++++++++++++++ 3 files changed, 761 insertions(+), 1 deletion(-) diff --git a/fastapi/applications.py b/fastapi/applications.py index 0a47699ae..6eed22db6 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -4536,6 +4536,384 @@ class FastAPI(Starlette): generate_unique_id_function=generate_unique_id_function, ) + def query( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP QUERY operation. + + ## Example + + ```python + from fastapi import FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + + @app.query("/items/") + def query_item(item: Item): + return {"message": "Item queried", "item": item} + ``` + """ + return self.router.query( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + def websocket_route( self, path: str, name: Union[str, None] = None ) -> Callable[[DecoratedCallable], DecoratedCallable]: diff --git a/fastapi/openapi/constants.py b/fastapi/openapi/constants.py index d724ee3cf..1963b1e0e 100644 --- a/fastapi/openapi/constants.py +++ b/fastapi/openapi/constants.py @@ -1,3 +1,3 @@ -METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"} +METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "QUERY"} REF_PREFIX = "#/components/schemas/" REF_TEMPLATE = "#/components/schemas/{model}" diff --git a/fastapi/routing.py b/fastapi/routing.py index a8e12eb60..0726552c1 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -4488,6 +4488,388 @@ class APIRouter(routing.Router): generate_unique_id_function=generate_unique_id_function, ) + def query( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP QUERY operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + router = APIRouter() + + @router.query("/items/") + def query_items(item: Item): + return {"message": "Items queried", "query": item} + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["QUERY"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + @deprecated( """ on_event is deprecated, use lifespan event handlers instead. From fae884e3197fb6a63eaf7a9076323f7ebd802286 Mon Sep 17 00:00:00 2001 From: Rauan Date: Wed, 26 Nov 2025 09:40:41 +0500 Subject: [PATCH 2/5] test: Add comprehensive tests for HTTP QUERY method support - Add `tests/test_http_query.py` to verify `app.query()` and `router.query()` functionality. - Ensure `QUERY` requests correctly accept and validate request bodies. - Verify OpenAPI schema generation for `QUERY` operations, specifically checking for the presence of `requestBody`. - Add compatibility checks for Pydantic v1 and v2 responses. --- tests/test_http_query_method.py | 288 ++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 tests/test_http_query_method.py diff --git a/tests/test_http_query_method.py b/tests/test_http_query_method.py new file mode 100644 index 000000000..7006ec851 --- /dev/null +++ b/tests/test_http_query_method.py @@ -0,0 +1,288 @@ +from typing import Optional + +from dirty_equals import IsDict +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient +from pydantic import BaseModel + +app = FastAPI() +router = APIRouter() + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + + +@app.query("/items/") +def query_item(item: Item): + return item + + +@app.query("/items/{item_id}") +def query_item_by_id(item_id: int, item: Item, q: Optional[str] = None): + return {"item_id": item_id, "item": item, "q": q} + + +@router.query("/router-items/") +def router_query_item(item: Item): + return item + + +app.include_router(router) + +client = TestClient(app) + + +def test_query_item(): + response = client.request( + "QUERY", "/items/", json={"name": "Foo", "price": 50.5} + ) + assert response.status_code == 200 + assert response.json() == { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + } + + +def test_query_item_validation_error(): + response = client.request("QUERY", "/items/", json={"name": "Foo"}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "price"], + "msg": "Field required", + "input": {"name": "Foo"}, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "price"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +def test_query_item_by_id(): + response = client.request( + "QUERY", + "/items/42?q=somequery", + json={"name": "Bar", "price": 10.0}, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 42, + "item": {"name": "Bar", "price": 10.0, "description": None, "tax": None}, + "q": "somequery", + } + + +def test_router_query_item(): + response = client.request( + "QUERY", "/router-items/", json={"name": "Baz", "price": 20.0} + ) + assert response.status_code == 200 + assert response.json() == { + "name": "Baz", + "price": 20.0, + "description": None, + "tax": None, + } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "query": { + "summary": "Query Item", + "operationId": "query_item_items__query", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/{item_id}": { + "query": { + "summary": "Query Item By Id", + "operationId": "query_item_by_id_items__item_id__query", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Q", "type": "string"} + ), + "name": "q", + "in": "query", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router-items/": { + "query": { + "summary": "Router Query Item", + "operationId": "router_query_item_router_items__query", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Description", "type": "string"} + ), + "price": {"title": "Price", "type": "number"}, + "tax": IsDict( + { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Tax", "type": "number"} + ), + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } \ No newline at end of file From 5960ddebfa949422cdc48fbb89619a9832410847 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 04:50:22 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/applications.py | 364 ++++++++++++++++---------------- fastapi/routing.py | 364 ++++++++++++++++---------------- tests/test_http_query_method.py | 6 +- 3 files changed, 366 insertions(+), 368 deletions(-) diff --git a/fastapi/applications.py b/fastapi/applications.py index 6eed22db6..3c6530e95 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -4537,30 +4537,30 @@ class FastAPI(Starlette): ) def query( - self, - path: Annotated[ - str, - Doc( - """ + self, + path: Annotated[ + str, + Doc( + """ The URL path to be used for this *path operation*. - + For example, in `http://example.com/items`, the path is `/items`. """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ The type to use for the response. - + It could be any valid Pydantic *field* type. So, it doesn't have to be a Pydantic model, it could be other things, like a `list`, `dict`, etc. - + It will be used for: - + * Documentation: the generated OpenAPI (and the UI at `/docs`) will show it as the response (JSON Schema). * Serialization: you could return an arbitrary object and the @@ -4577,296 +4577,296 @@ class FastAPI(Starlette): valid, that would mean a violation of the contract with the client, so it's an error from the API developer. So, FastAPI will raise an error and return a 500 error code (Internal Server Error). - + Read more about it in the [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). """ - ), - ] = Default(None), - status_code: Annotated[ - Optional[int], - Doc( - """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ The default status code to be used for the response. - + You could override the status code by returning a response directly. - + Read more about it in the [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). """ - ), - ] = None, - tags: Annotated[ - Optional[List[Union[str, Enum]]], - Doc( - """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ A list of tags to be applied to the *path operation*. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). """ - ), - ] = None, - dependencies: Annotated[ - Optional[Sequence[Depends]], - Doc( - """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ A list of dependencies (using `Depends()`) to be applied to the *path operation*. - + Read more about it in the [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). """ - ), - ] = None, - summary: Annotated[ - Optional[str], - Doc( - """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ A summary for the *path operation*. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). """ - ), - ] = None, - description: Annotated[ - Optional[str], - Doc( - """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ A description for the *path operation*. - + If not provided, it will be extracted automatically from the docstring of the *path operation function*. - + It can contain Markdown. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ The description for the default response. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). """ - ), - ] = "Successful Response", - responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], - Doc( - """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ Additional responses that could be returned by this *path operation*. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). """ - ), - ] = None, - deprecated: Annotated[ - Optional[bool], - Doc( - """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ Mark this *path operation* as deprecated. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). """ - ), - ] = None, - operation_id: Annotated[ - Optional[str], - Doc( - """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ Custom operation ID to be used by this *path operation*. - + By default, it is generated automatically. - + If you provide a custom operation ID, you need to make sure it is unique for the whole API. - + You can customize the operation ID generation with the parameter `generate_unique_id_function` in the `FastAPI` class. - + Read more about it in the [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). """ - ), - ] = None, - response_model_include: Annotated[ - Optional[IncEx], - Doc( - """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ Configuration passed to Pydantic to include only certain fields in the response data. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). """ - ), - ] = None, - response_model_exclude: Annotated[ - Optional[IncEx], - Doc( - """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ Configuration passed to Pydantic to exclude certain fields in the response data. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response model should be serialized by alias when an alias is used. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response data should have all the fields, including the ones that were not set and have their default values. This is different from `response_model_exclude_defaults` in that if the fields are set, they will be included in the response, even if the value is the same as the default. - + When `True`, default values are omitted from the response. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response data should have all the fields, including the ones that have the same value as the default. This is different from `response_model_exclude_unset` in that if the fields are set but contain the same default values, they will be excluded from the response. - + When `True`, default values are omitted from the response. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response data should exclude fields set to `None`. - + This is much simpler (less smart) than `response_model_exclude_unset` and `response_model_exclude_defaults`. You probably want to use one of those two instead of this one, as those allow returning `None` values when it makes sense. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ Include this *path operation* in the generated OpenAPI schema. - + This affects the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ - ), - ] = True, - response_class: Annotated[ - Type[Response], - Doc( - """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ Response class to be used for this *path operation*. - + This will not be used if you return a response directly. - + Read more about it in the [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). """ - ), - ] = Default(JSONResponse), - name: Annotated[ - Optional[str], - Doc( - """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ Name for this *path operation*. Only used internally. """ - ), - ] = None, - callbacks: Annotated[ - Optional[List[BaseRoute]], - Doc( - """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ List of *path operations* that will be used as OpenAPI callbacks. - + This is only for OpenAPI documentation, the callbacks won't be used directly. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). """ - ), - ] = None, - openapi_extra: Annotated[ - Optional[Dict[str, Any]], - Doc( - """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ Extra metadata to be included in the OpenAPI schema for this *path operation*. - + Read more about it in the [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ Customize the function used to generate unique IDs for the *path operations* shown in the generated OpenAPI. - + This is particularly useful when automatically generating clients or SDKs for your API. - + Read more about it in the [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). """ - ), - ] = Default(generate_unique_id), + ), + ] = Default(generate_unique_id), ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP QUERY operation. diff --git a/fastapi/routing.py b/fastapi/routing.py index 0726552c1..723e7f33b 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -4489,30 +4489,30 @@ class APIRouter(routing.Router): ) def query( - self, - path: Annotated[ - str, - Doc( - """ + self, + path: Annotated[ + str, + Doc( + """ The URL path to be used for this *path operation*. - + For example, in `http://example.com/items`, the path is `/items`. """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ The type to use for the response. - + It could be any valid Pydantic *field* type. So, it doesn't have to be a Pydantic model, it could be other things, like a `list`, `dict`, etc. - + It will be used for: - + * Documentation: the generated OpenAPI (and the UI at `/docs`) will show it as the response (JSON Schema). * Serialization: you could return an arbitrary object and the @@ -4529,296 +4529,296 @@ class APIRouter(routing.Router): valid, that would mean a violation of the contract with the client, so it's an error from the API developer. So, FastAPI will raise an error and return a 500 error code (Internal Server Error). - + Read more about it in the [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). """ - ), - ] = Default(None), - status_code: Annotated[ - Optional[int], - Doc( - """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ The default status code to be used for the response. - + You could override the status code by returning a response directly. - + Read more about it in the [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). """ - ), - ] = None, - tags: Annotated[ - Optional[List[Union[str, Enum]]], - Doc( - """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ A list of tags to be applied to the *path operation*. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). """ - ), - ] = None, - dependencies: Annotated[ - Optional[Sequence[params.Depends]], - Doc( - """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ A list of dependencies (using `Depends()`) to be applied to the *path operation*. - + Read more about it in the [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). """ - ), - ] = None, - summary: Annotated[ - Optional[str], - Doc( - """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ A summary for the *path operation*. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). """ - ), - ] = None, - description: Annotated[ - Optional[str], - Doc( - """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ A description for the *path operation*. - + If not provided, it will be extracted automatically from the docstring of the *path operation function*. - + It can contain Markdown. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ The description for the default response. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). """ - ), - ] = "Successful Response", - responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], - Doc( - """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ Additional responses that could be returned by this *path operation*. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). """ - ), - ] = None, - deprecated: Annotated[ - Optional[bool], - Doc( - """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ Mark this *path operation* as deprecated. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). """ - ), - ] = None, - operation_id: Annotated[ - Optional[str], - Doc( - """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ Custom operation ID to be used by this *path operation*. - + By default, it is generated automatically. - + If you provide a custom operation ID, you need to make sure it is unique for the whole API. - + You can customize the operation ID generation with the parameter `generate_unique_id_function` in the `FastAPI` class. - + Read more about it in the [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). """ - ), - ] = None, - response_model_include: Annotated[ - Optional[IncEx], - Doc( - """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ Configuration passed to Pydantic to include only certain fields in the response data. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). """ - ), - ] = None, - response_model_exclude: Annotated[ - Optional[IncEx], - Doc( - """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ Configuration passed to Pydantic to exclude certain fields in the response data. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response model should be serialized by alias when an alias is used. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response data should have all the fields, including the ones that were not set and have their default values. This is different from `response_model_exclude_defaults` in that if the fields are set, they will be included in the response, even if the value is the same as the default. - + When `True`, default values are omitted from the response. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response data should have all the fields, including the ones that have the same value as the default. This is different from `response_model_exclude_unset` in that if the fields are set but contain the same default values, they will be excluded from the response. - + When `True`, default values are omitted from the response. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ Configuration passed to Pydantic to define if the response data should exclude fields set to `None`. - + This is much simpler (less smart) than `response_model_exclude_unset` and `response_model_exclude_defaults`. You probably want to use one of those two instead of this one, as those allow returning `None` values when it makes sense. - + Read more about it in the [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ Include this *path operation* in the generated OpenAPI schema. - + This affects the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). """ - ), - ] = True, - response_class: Annotated[ - Type[Response], - Doc( - """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ Response class to be used for this *path operation*. - + This will not be used if you return a response directly. - + Read more about it in the [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). """ - ), - ] = Default(JSONResponse), - name: Annotated[ - Optional[str], - Doc( - """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ Name for this *path operation*. Only used internally. """ - ), - ] = None, - callbacks: Annotated[ - Optional[List[BaseRoute]], - Doc( - """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ List of *path operations* that will be used as OpenAPI callbacks. - + This is only for OpenAPI documentation, the callbacks won't be used directly. - + It will be added to the generated OpenAPI (e.g. visible at `/docs`). - + Read more about it in the [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). """ - ), - ] = None, - openapi_extra: Annotated[ - Optional[Dict[str, Any]], - Doc( - """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ Extra metadata to be included in the OpenAPI schema for this *path operation*. - + Read more about it in the [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ Customize the function used to generate unique IDs for the *path operations* shown in the generated OpenAPI. - + This is particularly useful when automatically generating clients or SDKs for your API. - + Read more about it in the [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). """ - ), - ] = Default(generate_unique_id), + ), + ] = Default(generate_unique_id), ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP QUERY operation. diff --git a/tests/test_http_query_method.py b/tests/test_http_query_method.py index 7006ec851..9219fbd2b 100644 --- a/tests/test_http_query_method.py +++ b/tests/test_http_query_method.py @@ -37,9 +37,7 @@ client = TestClient(app) def test_query_item(): - response = client.request( - "QUERY", "/items/", json={"name": "Foo", "price": 50.5} - ) + response = client.request("QUERY", "/items/", json={"name": "Foo", "price": 50.5}) assert response.status_code == 200 assert response.json() == { "name": "Foo", @@ -285,4 +283,4 @@ def test_openapi_schema(): }, } }, - } \ No newline at end of file + } From 1d327c7f55d96187eb45928bd4bd6de45818c2a3 Mon Sep 17 00:00:00 2001 From: Rauan Date: Wed, 26 Nov 2025 10:15:28 +0500 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=93=9D=20Add=20docs=20for=20HTTP=20QU?= =?UTF-8?q?ERY=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add docs source example in `docs_src/http_query/tutorial001.py` - Add documentation page `docs/en/docs/tutorial/http-query.md` - Add test for docs in `tests/test_tutorial/test_http_query/test_tutorial001.py` - Update `mkdocs.yml` navigation --- docs/en/docs/tutorial/http-query.md | 30 +++++++++++++++++ docs/en/mkdocs.yml | 1 + docs_src/http_query/tutorial001.py | 17 ++++++++++ .../test_http_query/test_tutorial001.py | 32 +++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 docs/en/docs/tutorial/http-query.md create mode 100644 docs_src/http_query/tutorial001.py create mode 100644 tests/test_tutorial/test_http_query/test_tutorial001.py diff --git a/docs/en/docs/tutorial/http-query.md b/docs/en/docs/tutorial/http-query.md new file mode 100644 index 000000000..a2ed698a7 --- /dev/null +++ b/docs/en/docs/tutorial/http-query.md @@ -0,0 +1,30 @@ +# HTTP QUERY + +Typically, when you want to read data you use `GET`. If you need to send complex data (like a large JSON object) to filter that data, you traditionally had to use `POST` because `GET` requests does not support request bodies. + +However, using `POST` for read-only operations isn't semantically correct, as `POST` implies that you are creating or modifying data. + +There is a newer HTTP method called **QUERY**. It is designed exactly for this: performing safe, idempotent read operations that require a request body. + +## Using `QUERY` + +In **FastAPI**, you can use the `QUERY` method using the `@app.query()` decorator. + +It works similarly to `@app.post()`, allowing you to receive Pydantic models in the body, but it signals that the operation is a read-only query. + + +{* ../../docs_src/http_query/tutorial001.py hl[7] *} + +### Testing it + +You can test it using an HTTP client that supports the `QUERY` method. + +Because it allows a body, you can send complex filters without hitting URL length limits common with `GET` query parameters. + +### Technical Details + +The `QUERY` method is defined in the [IETF HTTP QUERY Method Draft](https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-02.html). It is considered: + +* **Safe**: It does not alter the state of the server (read-only). +* **Idempotent**: Making the same request multiple times yields the same result. + diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index fd346a3d3..b549d04bd 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -136,6 +136,7 @@ nav: - tutorial/request-forms-and-files.md - tutorial/handling-errors.md - tutorial/path-operation-configuration.md + - tutorial/http-query.md - tutorial/encoder.md - tutorial/body-updates.md - Dependencies: diff --git a/docs_src/http_query/tutorial001.py b/docs_src/http_query/tutorial001.py new file mode 100644 index 000000000..2fd51acd0 --- /dev/null +++ b/docs_src/http_query/tutorial001.py @@ -0,0 +1,17 @@ +from typing import Optional + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class ItemSearch(BaseModel): + keyword: str + min_price: Optional[float] = None + max_price: Optional[float] = None + + +@app.query("/items/") +async def search_items(search_params: ItemSearch): + return {"message": "Searching items", "search_params": search_params} \ No newline at end of file diff --git a/tests/test_tutorial/test_http_query/test_tutorial001.py b/tests/test_tutorial/test_http_query/test_tutorial001.py new file mode 100644 index 000000000..5a29d2f0f --- /dev/null +++ b/tests/test_tutorial/test_http_query/test_tutorial001.py @@ -0,0 +1,32 @@ +from fastapi.testclient import TestClient + +from docs_src.http_query.tutorial001 import app + +client = TestClient(app) + + +def test_query_items(): + response = client.request( + "QUERY", + "/items/", + json={"keyword": "book", "max_price": 50}, + ) + assert response.status_code == 200 + assert response.json() == { + "message": "Searching items", + "search_params": { + "keyword": "book", + "min_price": None, + "max_price": 50.0, + }, + } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + schema = response.json() + assert "query" in schema["paths"]["/items/"] + operation = schema["paths"]["/items/"]["query"] + assert "requestBody" in operation + assert "ItemSearch" in operation["requestBody"]["content"]["application/json"]["schema"]["$ref"] From b419cf06a10b651484f3a6feaabd1d9a23f562c3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 05:31:59 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/http-query.md | 9 ++++----- docs_src/http_query/tutorial001.py | 2 +- tests/test_tutorial/test_http_query/test_tutorial001.py | 5 ++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/en/docs/tutorial/http-query.md b/docs/en/docs/tutorial/http-query.md index a2ed698a7..c3843abaf 100644 --- a/docs/en/docs/tutorial/http-query.md +++ b/docs/en/docs/tutorial/http-query.md @@ -1,4 +1,4 @@ -# HTTP QUERY +# HTTP QUERY { #http-query } Typically, when you want to read data you use `GET`. If you need to send complex data (like a large JSON object) to filter that data, you traditionally had to use `POST` because `GET` requests does not support request bodies. @@ -6,7 +6,7 @@ However, using `POST` for read-only operations isn't semantically correct, as `P There is a newer HTTP method called **QUERY**. It is designed exactly for this: performing safe, idempotent read operations that require a request body. -## Using `QUERY` +## Using `QUERY` { #using-query } In **FastAPI**, you can use the `QUERY` method using the `@app.query()` decorator. @@ -15,16 +15,15 @@ It works similarly to `@app.post()`, allowing you to receive Pydantic models in {* ../../docs_src/http_query/tutorial001.py hl[7] *} -### Testing it +### Testing it { #testing-it } You can test it using an HTTP client that supports the `QUERY` method. Because it allows a body, you can send complex filters without hitting URL length limits common with `GET` query parameters. -### Technical Details +### Technical Details { #technical-details } The `QUERY` method is defined in the [IETF HTTP QUERY Method Draft](https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-02.html). It is considered: * **Safe**: It does not alter the state of the server (read-only). * **Idempotent**: Making the same request multiple times yields the same result. - diff --git a/docs_src/http_query/tutorial001.py b/docs_src/http_query/tutorial001.py index 2fd51acd0..5c29adcd9 100644 --- a/docs_src/http_query/tutorial001.py +++ b/docs_src/http_query/tutorial001.py @@ -14,4 +14,4 @@ class ItemSearch(BaseModel): @app.query("/items/") async def search_items(search_params: ItemSearch): - return {"message": "Searching items", "search_params": search_params} \ No newline at end of file + return {"message": "Searching items", "search_params": search_params} diff --git a/tests/test_tutorial/test_http_query/test_tutorial001.py b/tests/test_tutorial/test_http_query/test_tutorial001.py index 5a29d2f0f..01df2719b 100644 --- a/tests/test_tutorial/test_http_query/test_tutorial001.py +++ b/tests/test_tutorial/test_http_query/test_tutorial001.py @@ -29,4 +29,7 @@ def test_openapi_schema(): assert "query" in schema["paths"]["/items/"] operation = schema["paths"]["/items/"]["query"] assert "requestBody" in operation - assert "ItemSearch" in operation["requestBody"]["content"]["application/json"]["schema"]["$ref"] + assert ( + "ItemSearch" + in operation["requestBody"]["content"]["application/json"]["schema"]["$ref"] + )