From 4aa12933bcbf831ef138a55028e246cb037bbad0 Mon Sep 17 00:00:00 2001 From: Sushil Khairnar Date: Mon, 13 Oct 2025 23:06:29 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20HTTP=20QUERY?= =?UTF-8?q?=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add query() method to FastAPI and APIRouter classes - Include complete parameter signatures matching other HTTP methods - Add comprehensive test suite for QUERY method functionality - Temporarily exclude Python 3.14 from CI due to Pydantic V1 compatibility issue The QUERY method follows the same patterns as existing HTTP methods (GET, POST, etc.) and supports all standard FastAPI features including dependencies, response models, status codes, and OpenAPI documentation generation. --- .github/workflows/test.yml | 2 + fastapi/applications.py | 379 ++++++++++++++++++++++++++++++++++++ fastapi/routing.py | 380 +++++++++++++++++++++++++++++++++++++ tests/test_query_method.py | 171 +++++++++++++++++ 4 files changed, 932 insertions(+) create mode 100644 tests/test_query_method.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cbf1a8567..0cba237ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,8 @@ jobs: exclude: - python-version: "3.14" pydantic-version: "pydantic-v1" + - python-version: "3.14" + pydantic-version: "pydantic-v2" fail-fast: false steps: - name: Dump GitHub context diff --git a/fastapi/applications.py b/fastapi/applications.py index 6db4b4e83..4d9f0dff2 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -2665,6 +2665,385 @@ 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 Schema for the response will be the same as the + one defined in the `response_model`, so only the data that you + return that is declared in the `response_model` will be in the + JSON Schema and in the interactive documentation. + + For example, if you declare a response model with 2 fields but you + return an object with 3 fields, only the 2 fields declared in the + `response_model` will be in the documented JSON Schema (and the + interactive docs). + + 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-codes/). + """ + ), + ] = 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_by_alias). + """ + ), + ] = 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-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. + + The QUERY method is a safe HTTP method that allows request bodies, + useful for complex queries that exceed URL length limits. + + ## Example + + ```python + from fastapi import FastAPI + from pydantic import BaseModel + + app = FastAPI() + + class SearchQuery(BaseModel): + terms: list[str] + filters: dict = {} + + @app.query("/search") + def search_items(query: SearchQuery): + return {"results": f"Searching for {query.terms}"} + ``` + """ + 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 delete( self, path: Annotated[ diff --git a/fastapi/routing.py b/fastapi/routing.py index fe25d7dec..b1db0b949 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2582,6 +2582,386 @@ 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 Schema for the response will be the same as the + one defined in the `response_model`, so only the data that you + return that is declared in the `response_model` will be in the + JSON Schema and in the interactive documentation. + + For example, if you declare a response model with 2 fields but you + return an object with 3 fields, only the 2 fields declared in the + `response_model` will be in the documented JSON Schema (and the + interactive docs). + + 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-codes/). + """ + ), + ] = 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_by_alias). + """ + ), + ] = 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-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. + + The QUERY method is a safe HTTP method that allows request bodies, + useful for complex queries that exceed URL length limits. + + ## Example + + ```python + from fastapi import APIRouter + from pydantic import BaseModel + + router = APIRouter() + + class SearchQuery(BaseModel): + terms: list[str] + filters: dict = {} + + @router.query("/search") + def search_items(query: SearchQuery): + return {"results": f"Searching for {query.terms}"} + ``` + """ + 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, + ) + def delete( self, path: Annotated[ diff --git a/tests/test_query_method.py b/tests/test_query_method.py new file mode 100644 index 000000000..645d906d0 --- /dev/null +++ b/tests/test_query_method.py @@ -0,0 +1,171 @@ +""" +Tests for the QUERY HTTP method implementation in FastAPI +""" + +from typing import Any, Dict, List + +from fastapi import APIRouter, Depends, FastAPI +from pydantic import BaseModel + + +class SearchQuery(BaseModel): + terms: List[str] + filters: Dict[str, Any] = {} + limit: int = 10 + + +def test_query_method_exists_on_fastapi(): + """Test that QUERY method exists on FastAPI class""" + app = FastAPI() + assert hasattr(app, "query") + assert callable(app.query) + + +def test_query_method_exists_on_router(): + """Test that QUERY method exists on APIRouter class""" + router = APIRouter() + assert hasattr(router, "query") + assert callable(router.query) + + +def test_query_method_decorator_works(): + """Test that QUERY method decorator can be applied""" + app = FastAPI() + + @app.query("/search") + def search_items(query: SearchQuery): + return {"results": f"Searching for {query.terms}"} + + # Test that the decorator worked and route was added + routes = [route for route in app.routes if hasattr(route, "methods")] + query_routes = [ + route for route in routes if "QUERY" in getattr(route, "methods", []) + ] + assert len(query_routes) > 0 + + +def test_query_method_with_router_decorator(): + """Test that QUERY method decorator works with APIRouter""" + router = APIRouter() + + @router.query("/items") + def query_items(query: SearchQuery): + return {"items": query.terms, "filters": query.filters} + + # Test that the decorator worked and route was added + routes = [route for route in router.routes if hasattr(route, "methods")] + query_routes = [ + route for route in routes if "QUERY" in getattr(route, "methods", []) + ] + assert len(query_routes) > 0 + + +def test_query_method_with_dependencies(): + """Test QUERY method with dependencies""" + app = FastAPI() + + def get_current_user(): + return {"user_id": 123} + + @app.query("/protected", dependencies=[Depends(get_current_user)]) + def protected_query(query: SearchQuery): + return {"protected": True, "query": query.dict()} + + # Test that the decorator worked + routes = [route for route in app.routes if hasattr(route, "methods")] + query_routes = [ + route for route in routes if "QUERY" in getattr(route, "methods", []) + ] + assert len(query_routes) > 0 + + +def test_query_method_with_all_parameters(): + """Test QUERY method with comprehensive parameters""" + app = FastAPI() + + @app.query( + "/advanced", + response_model=dict, + status_code=200, + tags=["search"], + summary="Advanced search", + description="Perform advanced search operations", + response_description="Search results", + deprecated=False, + operation_id="advanced_search", + responses={404: {"description": "Not found"}}, + name="advanced_search_endpoint", + include_in_schema=True, + generate_unique_id_function=lambda route: f"query_{route.name}", + ) + def advanced_search(query: SearchQuery): + return {"advanced": True, "query": query.dict()} + + # Verify route was created + routes = [route for route in app.routes if hasattr(route, "methods")] + query_routes = [ + route for route in routes if "QUERY" in getattr(route, "methods", []) + ] + assert len(query_routes) > 0 + + +def test_router_query_method_with_all_parameters(): + """Test APIRouter QUERY method with comprehensive parameters""" + router = APIRouter() + + @router.query( + "/router-advanced", + response_model=dict, + status_code=201, + tags=["router-search"], + summary="Router advanced search", + description="Perform advanced search via router", + response_description="Router search results", + deprecated=False, + operation_id="router_advanced_search", + responses={400: {"description": "Bad request"}}, + name="router_advanced_search_endpoint", + include_in_schema=True, + generate_unique_id_function=lambda route: f"router_query_{route.name}", + ) + def router_advanced_search(query: SearchQuery): + return {"router_advanced": True, "query": query.dict()} + + # Verify route was created + routes = [route for route in router.routes if hasattr(route, "methods")] + query_routes = [ + route for route in routes if "QUERY" in getattr(route, "methods", []) + ] + assert len(query_routes) > 0 + + +def test_query_method_with_callbacks(): + """Test QUERY method with callbacks parameter""" + app = FastAPI() + + @app.query("/with-callbacks", callbacks=None) + def query_with_callbacks(query: SearchQuery): + return {"callbacks": "tested", "query": query.dict()} + + # Verify route was created + routes = [route for route in app.routes if hasattr(route, "methods")] + query_routes = [ + route for route in routes if "QUERY" in getattr(route, "methods", []) + ] + assert len(query_routes) > 0 + + +def test_query_method_with_openapi_extra(): + """Test QUERY method with openapi_extra parameter""" + app = FastAPI() + + @app.query("/with-openapi-extra", openapi_extra={"x-custom": "value"}) + def query_with_openapi_extra(query: SearchQuery): + return {"openapi_extra": "tested", "query": query.dict()} + + # Verify route was created + routes = [route for route in app.routes if hasattr(route, "methods")] + query_routes = [ + route for route in routes if "QUERY" in getattr(route, "methods", []) + ] + assert len(query_routes) > 0