From 81435c271fc4e4ba69d57ce2f6faaa9168743ce3 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:30 +0700 Subject: [PATCH 01/23] update uv.lock --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 92c61f5..f8ddca7 100644 --- a/uv.lock +++ b/uv.lock @@ -512,7 +512,7 @@ wheels = [ [[package]] name = "fasthttp-client" -version = "1.3.28" +version = "1.3.29" source = { editable = "." } dependencies = [ { name = "annotated-doc" }, From c4e8c6d6c5431ede2e5f29f0144b57098409c3ba Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:32 +0700 Subject: [PATCH 02/23] docs: update docs/index.md --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 1ae81e8..7dc7c0d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -39,7 +39,7 @@ The key features are: * **Simple**: define HTTP requests as decorated async functions, no boilerplate. * **Typed**: full type annotations throughout; validate responses with Pydantic models. * **Logged**: colorful, structured request/response logs with timing, built-in. -* **Complete**: GET, POST, PUT, PATCH, DELETE, and GraphQL out of the box. +* **Complete**: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY, and GraphQL out of the box. * **Extensible**: middleware, dependency injection, routers, lifespan hooks. * **Interactive**: built-in Swagger UI via `app.web_run()` to browse and execute requests in the browser. * **HTTP/2**: optional HTTP/2 support, with automatic fallback to HTTP/1.1. From 524c3f248ba79369cb9edc534dc7d113d79f7cf6 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:35 +0700 Subject: [PATCH 03/23] docs: update api-reference.md --- docs/en/api-reference.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/en/api-reference.md b/docs/en/api-reference.md index 65701fa..31ab01c 100644 --- a/docs/en/api-reference.md +++ b/docs/en/api-reference.md @@ -17,6 +17,7 @@ app = FastHTTP( debug: bool = False, http2: bool = False, get_request: dict = {}, + query_request: dict = {}, post_request: dict = {}, put_request: dict = {}, patch_request: dict = {}, @@ -34,6 +35,7 @@ app = FastHTTP( | `debug` | `bool` | `False` | Debug mode | | `http2` | `bool` | `False` | Use HTTP/2 | | `get_request` | `dict` | `{}` | GET settings | +| `query_request` | `dict` | `{}` | QUERY settings | | `post_request` | `dict` | `{}` | POST settings | | `put_request` | `dict` | `{}` | PUT settings | | `patch_request` | `dict` | `{}` | PATCH settings | @@ -129,6 +131,25 @@ async def lifespan(app): ) ``` +### @app.query() + +Safe and idempotent like GET, but accepts a `json`/`data` body (like POST). See [HTTP Methods](tutorial/http-methods.md#query-complex-reads-with-a-body). + +```python +@app.query( + url: str, + json: dict = None, + data: bytes = None, + params: dict = None, + tags: list = [], + dependencies: list = [], + response_model: type = None, + request_model: type = None, + responses: dict = None, + query_request: dict = None, +) +``` + ### @app.post() ```python @@ -510,7 +531,7 @@ The Route object represents a registered HTTP request. It contains all informati | Attribute | Type | Description | |-----------|------|-------------| -| `method` | `str` | HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) | +| `method` | `str` | HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY) | | `url` | `str` | Full URL of the request | | `params` | `dict` | Query parameters | | `json` | `dict` | JSON body sent with request | From c61b7e513f922d3c6aa8c49e7261c80d3d64c17f Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:44 +0700 Subject: [PATCH 04/23] docs: update docs/en/index.md --- docs/en/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/index.md b/docs/en/index.md index 22c210f..05c6154 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -36,7 +36,7 @@ The key features are: * **Simple**: define HTTP requests as decorated async functions, no boilerplate. * **Typed**: full type annotations throughout; validate responses with Pydantic models. * **Logged**: colorful, structured request/response logs with timing, built-in. -* **Complete**: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and GraphQL out of the box. +* **Complete**: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY, and GraphQL out of the box. * **Extensible**: middleware, dependency injection, routers, lifespan hooks. * **Interactive**: built-in Swagger UI via `app.web_run()` to browse and execute requests in the browser. * **HTTP/2**: optional HTTP/2 support, with automatic fallback to HTTP/1.1. From ffbf48866eb2e899f84a9777b4aacecc3283c154 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:46 +0700 Subject: [PATCH 05/23] docs: update fasthttp.md --- docs/en/reference/fasthttp.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/en/reference/fasthttp.md b/docs/en/reference/fasthttp.md index f8cd48b..e3b2639 100644 --- a/docs/en/reference/fasthttp.md +++ b/docs/en/reference/fasthttp.md @@ -16,6 +16,7 @@ app = FastHTTP( middleware: list = [], base_url: str = None, get_request: dict = {}, + query_request: dict = {}, post_request: dict = {}, put_request: dict = {}, patch_request: dict = {}, @@ -36,6 +37,7 @@ app = FastHTTP( | `middleware` | `list` | `[]` | Middleware list | | `base_url` | `str` | `None` | Default base URL for decorators and routers | | `get_request` | `dict` | `{}` | Default GET settings | +| `query_request` | `dict` | `{}` | Default QUERY settings | | `post_request` | `dict` | `{}` | Default POST settings | | `put_request` | `dict` | `{}` | Default PUT settings | | `patch_request` | `dict` | `{}` | Default PATCH settings | @@ -123,6 +125,24 @@ Decorator for GET requests. ) ``` +### query() + +Decorator for QUERY requests. Safe and idempotent like GET, but accepts a `json`/`data` body (like POST). + +```python +@app.query( + url: str, + json: dict = None, + data: bytes = None, + params: dict = None, + tags: list = [], + dependencies: list = [], + response_model: type = None, + request_model: type = None, + responses: dict = None, +) +``` + ### post() Decorator for POST requests. @@ -227,6 +247,7 @@ AsyncSession( | Method | Signature | |--------|-----------| | `get` | `(url, *, params, headers, timeout) → Response \| None` | +| `query` | `(url, *, json, data, headers, timeout) → Response \| None` | | `post` | `(url, *, json, data, headers, timeout) → Response \| None` | | `put` | `(url, *, json, data, headers, timeout) → Response \| None` | | `patch` | `(url, *, json, data, headers, timeout) → Response \| None` | From ec42e157dc3298cc9c7782c0274cd68de35ca93f Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:48 +0700 Subject: [PATCH 06/23] docs: update http-methods.md --- docs/en/tutorial/http-methods.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/en/tutorial/http-methods.md b/docs/en/tutorial/http-methods.md index a6e5b15..4b6a62a 100644 --- a/docs/en/tutorial/http-methods.md +++ b/docs/en/tutorial/http-methods.md @@ -49,6 +49,22 @@ async def patch_user(resp: Response) -> dict: return resp.json() ``` +## QUERY - Complex Reads with a Body + +`QUERY` is a newer HTTP method (proposed in an IETF draft, `draft-ietf-httpbis-safe-method-w-body`) for read operations that don't fit in a URL. It's safe and idempotent like `GET`, but carries a JSON body like `POST` — useful for complex search/filter requests. + +```python +@app.query( + url="https://api.example.com/users/search", + json={"role": "admin", "active": True} +) +async def search_users(resp: Response) -> dict: + return resp.json() +``` + +!!! note + `QUERY` is not yet universally supported by servers and proxies — only use it against APIs that explicitly document `QUERY` support. It also isn't part of the standard OpenAPI Path Item Object method set, so `QUERY` routes won't be rendered in Swagger UI (see [OpenAPI](openapi/index.md)), even though they're included in the generated `/openapi.json`. + ## DELETE - Remove Data ```python @@ -83,7 +99,7 @@ async def allowed_methods(resp: Response) -> dict: |-----------|-------------| | `url` | Request URL (required) | | `params` | Query parameters | -| `json` | JSON body (for POST, PUT, PATCH) | +| `json` | JSON body (for POST, PUT, PATCH, QUERY) | | `data` | Raw bytes body | | `files` | File uploads (multipart/form-data) | | `tags` | Tags for grouping | From ee2b1ab0353a82aace276b41f6c5cb31f90d0118 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:50 +0700 Subject: [PATCH 07/23] docs: update docs/en/tutorial/index.md --- docs/en/tutorial/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/tutorial/index.md b/docs/en/tutorial/index.md index f83084f..00d1e7b 100644 --- a/docs/en/tutorial/index.md +++ b/docs/en/tutorial/index.md @@ -8,7 +8,7 @@ The tutorial is divided into several sections: ### Getting Started - [First Steps](first-steps.md) - Installation and basic concepts -- [HTTP Methods](http-methods.md) - GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS +- [HTTP Methods](http-methods.md) - GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY - [Request Parameters](request-parameters.md) - Query, JSON, headers - [Response Handling](response-handling.md) - Working with responses From e48a26cad92fa8c133b15b48a2a1d3c92e8d18d9 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:54 +0700 Subject: [PATCH 08/23] docs: update api-reference.md --- docs/ru/api-reference.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/ru/api-reference.md b/docs/ru/api-reference.md index ae5fd1a..3375c1b 100644 --- a/docs/ru/api-reference.md +++ b/docs/ru/api-reference.md @@ -17,6 +17,7 @@ app = FastHTTP( debug: bool = False, http2: bool = False, get_request: dict = {}, + query_request: dict = {}, post_request: dict = {}, put_request: dict = {}, patch_request: dict = {}, @@ -36,6 +37,7 @@ app = FastHTTP( | `debug` | `bool` | `False` | Режим отладки | | `http2` | `bool` | `False` | Использовать HTTP/2 | | `get_request` | `dict` | `{}` | Настройки для GET | +| `query_request` | `dict` | `{}` | Настройки для QUERY | | `post_request` | `dict` | `{}` | Настройки для POST | | `put_request` | `dict` | `{}` | Настройки для PUT | | `patch_request` | `dict` | `{}` | Настройки для PATCH | @@ -131,6 +133,25 @@ async def lifespan(app): ) ``` +### @app.query() + +Безопасен и идемпотентен как GET, но принимает тело `json`/`data` (как POST). См. [HTTP методы](tutorial/http-methods.md). + +```python +@app.query( + url: str, + json: dict = None, + data: bytes = None, + params: dict = None, + tags: list = [], + dependencies: list = [], + response_model: type = None, + request_model: type = None, + responses: dict = None, + query_request: dict = None, +) +``` + ### @app.post() ```python From a17208898f18afb90bd238d097efa1371d83d3dd Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:42:56 +0700 Subject: [PATCH 09/23] docs: update docs/ru/index.md --- docs/ru/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ru/index.md b/docs/ru/index.md index ed50ca0..3367ba6 100644 --- a/docs/ru/index.md +++ b/docs/ru/index.md @@ -36,7 +36,7 @@ FastHTTP — это современная **асинхронная HTTP-кли * **Простой**: определяйте HTTP-запросы как декорированные async-функции, без лишнего кода. * **Типизированный**: полные аннотации типов; валидация ответов через Pydantic. * **Логирует**: красочные структурированные логи запросов/ответов со временем выполнения, встроено. -* **Полный**: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS и GraphQL из коробки. +* **Полный**: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY и GraphQL из коробки. * **Расширяемый**: middleware, dependency injection, роутеры, lifespan-хуки. * **Интерактивный**: встроенный Swagger UI через `app.web_run()` для просмотра и выполнения запросов в браузере. * **HTTP/2**: опциональная поддержка HTTP/2 с автоматическим fallback на HTTP/1.1. From 065ef89a465e62cfccda14a78593c66a1a79db01 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:01 +0700 Subject: [PATCH 10/23] docs: update fasthttp.md --- docs/ru/reference/fasthttp.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/ru/reference/fasthttp.md b/docs/ru/reference/fasthttp.md index b72a58e..8875dbc 100644 --- a/docs/ru/reference/fasthttp.md +++ b/docs/ru/reference/fasthttp.md @@ -16,6 +16,7 @@ app = FastHTTP( middleware: list = [], base_url: str = None, get_request: dict = {}, + query_request: dict = {}, post_request: dict = {}, put_request: dict = {}, patch_request: dict = {}, @@ -36,6 +37,7 @@ app = FastHTTP( | `middleware` | `list` | `[]` | Список middleware | | `base_url` | `str` | `None` | Базовый URL для декораторов и роутеров | | `get_request` | `dict` | `{}` | Настройки GET | +| `query_request` | `dict` | `{}` | Настройки QUERY | | `post_request` | `dict` | `{}` | Настройки POST | | `put_request` | `dict` | `{}` | Настройки PUT | | `patch_request` | `dict` | `{}` | Настройки PATCH | @@ -97,6 +99,24 @@ app.include_router( Декораторы для HTTP методов. +### query() + +Декоратор для QUERY-запросов. Безопасен и идемпотентен как GET, но принимает тело `json`/`data` (как POST). + +```python +@app.query( + url: str, + json: dict = None, + data: bytes = None, + params: dict = None, + tags: list = [], + dependencies: list = [], + response_model: type = None, + request_model: type = None, + responses: dict = None, +) +``` + ### graphql() Декоратор для GraphQL. @@ -173,6 +193,7 @@ AsyncSession( | Метод | Сигнатура | |-------|-----------| | `get` | `(url, *, params, headers, timeout) → Response \| None` | +| `query` | `(url, *, json, data, headers, timeout) → Response \| None` | | `post` | `(url, *, json, data, headers, timeout) → Response \| None` | | `put` | `(url, *, json, data, headers, timeout) → Response \| None` | | `patch` | `(url, *, json, data, headers, timeout) → Response \| None` | From 962142b9b8416c81738acd9dba83d27e7ce92380 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:03 +0700 Subject: [PATCH 11/23] docs: update http-methods.md --- docs/ru/tutorial/http-methods.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/ru/tutorial/http-methods.md b/docs/ru/tutorial/http-methods.md index 49ffb8c..547593f 100644 --- a/docs/ru/tutorial/http-methods.md +++ b/docs/ru/tutorial/http-methods.md @@ -49,6 +49,22 @@ async def patch_user(resp: Response) -> dict: return resp.json() ``` +## QUERY - сложные запросы на чтение с телом + +`QUERY` — более новый HTTP-метод (предложен в IETF-черновике `draft-ietf-httpbis-safe-method-w-body`) для операций чтения, которые не помещаются в URL. Он безопасен и идемпотентен как `GET`, но несёт JSON-тело как `POST` — удобно для сложных запросов поиска/фильтрации. + +```python +@app.query( + url="https://api.example.com/users/search", + json={"role": "admin", "active": True} +) +async def search_users(resp: Response) -> dict: + return resp.json() +``` + +!!! note + `QUERY` пока поддерживается не всеми серверами и прокси — используйте его только для API, которые явно документируют поддержку `QUERY`. Также этот метод не входит в стандартный набор методов OpenAPI Path Item Object, поэтому `QUERY`-маршруты не отображаются в Swagger UI (см. [OpenAPI](openapi/index.md)), хотя и попадают в сгенерированный `/openapi.json`. + ## DELETE - удаление данных ```python @@ -83,7 +99,7 @@ async def allowed_methods(resp: Response) -> dict: |----------|----------| | `url` | URL запроса (обязательный) | | `params` | Query параметры | -| `json` | JSON тело (для POST, PUT, PATCH) | +| `json` | JSON тело (для POST, PUT, PATCH, QUERY) | | `data` | Сырые байты | | `files` | Загрузка файлов (multipart/form-data) | | `tags` | Теги для группировки | From c6fc6b98a9aba4a957d1e4dcdc7c146ca16a65ea Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:05 +0700 Subject: [PATCH 12/23] docs: update docs/ru/tutorial/index.md --- docs/ru/tutorial/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ru/tutorial/index.md b/docs/ru/tutorial/index.md index e2d591e..05dc121 100644 --- a/docs/ru/tutorial/index.md +++ b/docs/ru/tutorial/index.md @@ -8,7 +8,7 @@ ### Начало работы - [Первые шаги](first-steps.md) - Установка и основные понятия -- [HTTP методы](http-methods.md) - GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS +- [HTTP методы](http-methods.md) - GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY - [Параметры запроса](request-parameters.md) - Query, JSON, заголовки - [Обработка ответа](response-handling.md) - Работа с ответами From b46fdf87414e7f3ad1917b280eea371cd7de1f30 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:11 +0700 Subject: [PATCH 13/23] update app.py --- fasthttp/app.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/fasthttp/app.py b/fasthttp/app.py index 9dc4ba3..ae84d53 100644 --- a/fasthttp/app.py +++ b/fasthttp/app.py @@ -143,6 +143,19 @@ def __init__( ), ] ) = None, + query_request: ( + Annotated[ + RequestsOptional | None, + Doc( + """ + Default configuration for QUERY requests. + + Allows setting headers, timeout and other request-level + options that will be applied to all QUERY requests. + """ + ), + ] + ) = None, post_request: ( Annotated[ RequestsOptional | None, @@ -532,6 +545,7 @@ async def lifespan(app: FastHTTP): self.request_configs = { "GET": get_request or {}, + "QUERY": query_request or {}, "POST": post_request or {}, "PUT": put_request or {}, "PATCH": patch_request or {}, @@ -831,6 +845,41 @@ def get( responses=responses, ) + def query( + self, + url: str, + *, + json: dict[str, Any] | None = None, + data: object | None = None, + files: FileUpload | None = None, + response_model: type | None = None, + request_model: type[BaseModel] | None = None, + tags: list[str] | None = None, + dependencies: list[Any] | None = None, + raise_for_status: bool = False, + auth: BasicAuth | DigestAuth | BearerAuth | OAuth2ClientCredentials | None = None, + responses: dict[int, dict[Literal["model"], type[BaseModel]]] | None = None, + ) -> Callable[[Callable[..., object]], Callable[..., object]]: + """Decorator for registering a QUERY route. + + QUERY is safe and idempotent like GET, but carries a request body + (per the IETF QUERY method draft), so it accepts ``json``/``data``. + """ + return self._add_route( + method="QUERY", + url=url, + json=json, + data=data, + files=files, + response_model=response_model, + request_model=request_model, + tags=tags, + dependencies=dependencies, + raise_for_status=raise_for_status, + auth=auth, + responses=responses, + ) + def post( self, url: str, From 3c9f84492d8d6fe97d257390f4e004bd07906316 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:19 +0700 Subject: [PATCH 14/23] update client.py --- fasthttp/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fasthttp/client.py b/fasthttp/client.py index 5795b0c..98ed7fb 100644 --- a/fasthttp/client.py +++ b/fasthttp/client.py @@ -442,9 +442,9 @@ async def _follow_redirect( "follow_redirects": False, "auth": resolve_auth(route.auth), } - if new_method in ("POST", "PUT", "PATCH") and route.json: + if new_method in ("POST", "PUT", "PATCH", "QUERY") and route.json: req_kwargs["json"] = route.json - elif new_method in ("POST", "PUT", "PATCH") and route.data: + elif new_method in ("POST", "PUT", "PATCH", "QUERY") and route.data: req_kwargs["content"] = route.data new_resp = await client.request(**req_kwargs) From 645860214ec386d9ceff001ebe8633b1a308d999 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:33 +0700 Subject: [PATCH 15/23] update routing.py --- fasthttp/routing.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/fasthttp/routing.py b/fasthttp/routing.py index aa2c5e8..f9c8953 100644 --- a/fasthttp/routing.py +++ b/fasthttp/routing.py @@ -43,7 +43,7 @@ class Route(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) method: HTTPMethod - """HTTP method for the request (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS).""" + """HTTP method for the request (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY).""" url: str """Target URL for the HTTP request. Must include scheme, host, and path.""" @@ -303,6 +303,41 @@ def get( responses=responses, ) + def query( + self, + url: str, + *, + json: dict[str, Any] | None = None, + data: object | None = None, + files: FileUpload | None = None, + response_model: type[BaseModel] | None = None, + request_model: type[BaseModel] | None = None, + tags: list[str] | None = None, + dependencies: list[Any] | None = None, + raise_for_status: bool = False, + auth: BasicAuth | DigestAuth | BearerAuth | OAuth2ClientCredentials | None = None, + responses: dict[int, dict[Literal["model"], type[BaseModel]]] | None = None, + ) -> Callable[[Callable[..., object]], Callable[..., object]]: + """Decorator for registering a QUERY route on the router. + + QUERY is safe and idempotent like GET, but carries a request body + (per the IETF QUERY method draft), so it accepts ``json``/``data``. + """ + return self._add_route( + method="QUERY", + url=url, + json=json, + data=data, + files=files, + response_model=response_model, + request_model=request_model, + tags=tags, + dependencies=dependencies, + raise_for_status=raise_for_status, + auth=auth, + responses=responses, + ) + def post( self, url: str, From f1ee3c2c1af09753bdc3853299fbef9660181dd3 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:39 +0700 Subject: [PATCH 16/23] update session.py --- fasthttp/session.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/fasthttp/session.py b/fasthttp/session.py index ac78be4..4a3a5c0 100644 --- a/fasthttp/session.py +++ b/fasthttp/session.py @@ -132,7 +132,16 @@ def __init__( base_config = {"headers": dict(self._session_headers), "timeout": timeout} self._request_configs: dict[str, dict[str, Any]] = { method: dict(base_config) - for method in ("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS") + for method in ( + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS", + "QUERY", + ) } _secret = secret_key or secrets.token_bytes(32) @@ -231,6 +240,23 @@ async def get( ), ) + async def query( + self, + url: str, + *, + json: dict[str, Any] | None = None, + data: object | None = None, + headers: dict[str, str] | None = None, + timeout: float | None = None, + ) -> Response | None: + """Send a QUERY request (safe/idempotent like GET, but with a body).""" + return await self._http_client.send( + self._ensure_open(), + self._build_route( + "QUERY", url, json=json, data=data, headers=headers, timeout=timeout + ), + ) + async def post( self, url: str, From 915b4bae7d99447847b1b9f2e86778e422497dda Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:43 +0700 Subject: [PATCH 17/23] update types.py --- fasthttp/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fasthttp/types.py b/fasthttp/types.py index e712cc4..b7d128e 100644 --- a/fasthttp/types.py +++ b/fasthttp/types.py @@ -7,7 +7,7 @@ from annotated_doc import Doc HTTPMethod: TypeAlias = Literal[ - "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS" + "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY" ] OAuth2Scope: TypeAlias = Literal[ From 01b96cdd1a96944c31dfe96185d54e57f776280a Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:51 +0700 Subject: [PATCH 18/23] update route_inspect.py --- fasthttp/helpers/route_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fasthttp/helpers/route_inspect.py b/fasthttp/helpers/route_inspect.py index c0294ff..f2f8e10 100644 --- a/fasthttp/helpers/route_inspect.py +++ b/fasthttp/helpers/route_inspect.py @@ -100,7 +100,7 @@ def validate_handler(func: Callable[..., object]) -> None: def create_route_params( *, - method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"], + method: Literal["GET", "POST", "PUT", "PATCH", "DELETE", "QUERY"], url: str, params: dict | None = None, json: dict | None = None, From 171a8b1f059838d675d467c00bf394ca686e7bf6 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:54 +0700 Subject: [PATCH 19/23] update routes.py --- fasthttp/openapi/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fasthttp/openapi/routes.py b/fasthttp/openapi/routes.py index 8bdd3cc..17e3a85 100644 --- a/fasthttp/openapi/routes.py +++ b/fasthttp/openapi/routes.py @@ -143,7 +143,7 @@ async def handle_request( "headers": headers, } - if body and method in ("POST", "PUT", "PATCH"): + if body and method in ("POST", "PUT", "PATCH", "QUERY"): if isinstance(body, dict): kwargs["json"] = body else: From 04b8ba7202a3a0f1755bb87566ec52b766178071 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:56 +0700 Subject: [PATCH 20/23] test: update test_middleware.py --- tests/test_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 9314846..4f5dfb4 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -800,7 +800,7 @@ def test_http_method_count(self): from fasthttp.types import HTTPMethod - assert len(get_args(HTTPMethod)) == 7 + assert len(get_args(HTTPMethod)) == 8 # --------------------------------------------------------------------------- From 02416fa963b65f81e8b0af6bf64f1ffaa9d3e817 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:43:58 +0700 Subject: [PATCH 21/23] test: update test_routing.py --- tests/test_routing.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_routing.py b/tests/test_routing.py index 88aa492..32fa445 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -319,6 +319,16 @@ async def get_users(_resp: Response) -> list: assert len(router._route_defs) == 1 assert router._route_defs[0].method == "GET" + def test_router_query_decorator(self): + router = Router(base_url="https://api.example.com") + + @router.query(url="/users/search", json={"role": "admin"}) + async def search_users(_resp: Response) -> list: + return [] + + assert router._route_defs[0].method == "QUERY" + assert router._route_defs[0].json == {"role": "admin"} + def test_router_post_decorator(self): router = Router(base_url="https://api.example.com") From b5eea2dba83b2024fee0247387c9b711f61e3e41 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:44:00 +0700 Subject: [PATCH 22/23] test: update test_session.py --- tests/test_session.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_session.py b/tests/test_session.py index 2d8edf0..0542d8a 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -78,6 +78,21 @@ async def test_post_request_with_json(self) -> None: assert resp is not None assert resp.status == 201 + @pytest.mark.asyncio + async def test_query_request_with_json(self) -> None: + async with AsyncSession(security=False) as session: + with patch.object( + session._http_client, + "send", + AsyncMock(return_value=_mock_resp(200, "[]")), + ): + resp = await session.query( + "https://example.com/api/search", json={"role": "admin"} + ) + + assert resp is not None + assert resp.status == 200 + @pytest.mark.asyncio async def test_put_request(self) -> None: async with AsyncSession(security=False) as session: From b9b0333c5712d100dff0d380e88cbe4c3c86269b Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Tue, 14 Jul 2026 11:49:52 +0700 Subject: [PATCH 23/23] docs: update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a43dc7..10a4301 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Key features: - **Simple** — define HTTP requests as decorated async functions, no boilerplate. - **Typed** — full type annotations throughout; validate responses with Pydantic models. - **Logged** — colorful, structured request/response logs with timing, built-in. -- **Complete** — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, GraphQL, and **WebSocket** out of the box. +- **Complete** — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY, GraphQL, and **WebSocket** out of the box. - **Extensible** — middleware, dependency injection, routers, lifespan hooks. - **Interactive** — built-in Swagger UI via `app.web_run()` to browse and execute requests in the browser. - **HTTP/2** — optional HTTP/2 support, with automatic fallback to HTTP/1.1.