diff --git a/README.md b/README.md index 20dcac2..3d0c342 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,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, and GraphQL out of the box. +- **Complete** — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, 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. @@ -220,11 +220,26 @@ async def put_data(resp: Response) -> dict: return resp.json() +@app.patch(url="https://httpbin.org/patch") +async def patch_data(resp: Response) -> dict: + return resp.json() + + @app.delete(url="https://httpbin.org/delete") async def delete_data(resp: Response) -> int: return resp.status_code +@app.head(url="https://httpbin.org/get") +async def head_data(resp: Response) -> int: + return resp.status + + +@app.options(url="https://httpbin.org/get") +async def options_data(resp: Response) -> dict: + return {"allow": resp.headers.get("allow", "")} + + if __name__ == "__main__": app.run() ``` diff --git a/docs/en/api-reference.md b/docs/en/api-reference.md index 52539cb..65701fa 100644 --- a/docs/en/api-reference.md +++ b/docs/en/api-reference.md @@ -38,6 +38,8 @@ app = FastHTTP( | `put_request` | `dict` | `{}` | PUT settings | | `patch_request` | `dict` | `{}` | PATCH settings | | `delete_request` | `dict` | `{}` | DELETE settings | +| `head_request` | `dict` | `{}` | HEAD settings | +| `options_request` | `dict` | `{}` | OPTIONS settings | | `middleware` | `list` | `[]` | Middleware list | | `security` | `bool` | `True` | Enable built-in security | | `lifespan` | `Callable` | `None` | Context manager for startup/shutdown | @@ -189,7 +191,34 @@ async def lifespan(app): response_model: type = None, request_model: type = None, responses: dict = None, - delete_request: dict = None, +) +``` + +### @app.head() + +```python +@app.head( + url: str, + params: dict = None, + tags: list = [], + dependencies: list = [], + response_model: type = None, + request_model: type = None, + responses: dict = None, +) +``` + +### @app.options() + +```python +@app.options( + url: str, + params: dict = None, + tags: list = [], + dependencies: list = [], + response_model: type = None, + request_model: type = None, + responses: dict = None, ) ``` @@ -481,7 +510,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) | +| `method` | `str` | HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) | | `url` | `str` | Full URL of the request | | `params` | `dict` | Query parameters | | `json` | `dict` | JSON body sent with request | diff --git a/docs/en/dependencies.md b/docs/en/dependencies.md index 985cd17..a788a95 100644 --- a/docs/en/dependencies.md +++ b/docs/en/dependencies.md @@ -88,7 +88,7 @@ Both functions work the same way — the system automatically detects the functi The `route` object contains all information about the request: ```python -route.method # HTTP method: "GET", "POST", "PUT", "PATCH", "DELETE" +route.method # HTTP method: "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS" route.url # Full request URL route.params # Query parameters (dictionary) route.json # JSON request body (dictionary) diff --git a/docs/en/index.md b/docs/en/index.md index 7dcb6ad..ae7e696 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -1,32 +1,70 @@ -# FastHTTP - -Asynchronous HTTP client for Python with a declarative approach, similar to FastAPI. - -## Features - -- **Declarative style** — define requests as functions with decorators -- **Asynchronous** — parallel request execution with asyncio -- **Dependencies** — modify requests before sending -- **Tags** — grouping and filtering requests -- **Middleware** — global logic for all requests -- **Pydantic** — response validation -- **HTTP/2** — support for modern protocol -- **CLI** — convenient command line +
+
+
+ Fast, simple HTTP client with decorator-based routing, async support, and beautiful logging. +
+ + +--- + +**Documentation**: https://fasthttp.ndugram.dev/en/latest/ + +**Source Code**: https://github.com/ndugram/fasthttp + +--- + +FastHTTP is a modern **async HTTP client library** for Python, built on top of **httpx**. It brings a decorator-based API — similar to FastAPI, but for outgoing requests — with structured logging, middleware, Pydantic validation, and a built-in Swagger UI. + +The key features are: + +* **Fast**: built on httpx with full async support and parallel request execution. +* **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. +* **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. + +## Requirements + +Python 3.10+ + +FastHTTP stands on the shoulders of giants: + +*httpx — async HTTP transport.
+* pydantic — response model validation and serialization.
+* orjson — fast JSON parsing.
+* typer — CLI interface.
+* uvicorn — ASGI server for `web_run()`.
## Installation
-```bash
-pip install fasthttp-client
-```
-
-For HTTP/2:
+```console
+$ pip install fasthttp-client
-```bash
-pip install fasthttp-client[http2]
+---> 100%
```
+## Example
-## Quick Example
+### Create it
+
+Create a file `main.py`:
```python
from fasthttp import FastHTTP
@@ -35,8 +73,8 @@ from fasthttp.response import Response
app = FastHTTP()
-@app.get(url="https://jsonplaceholder.typicode.com/posts/1")
-async def main(resp: Response) -> dict:
+@app.get(url="https://httpbin.org/get")
+async def get_data(resp: Response) -> dict:
return resp.json()
@@ -44,154 +82,358 @@ if __name__ == "__main__":
app.run()
```
-:::tip Important
-Handler functions must have a return type annotation (`-> dict`, `-> str`, `-> int`, etc.).
-:::
+### Run it
-## Why FastHTTP?
+```console
+$ python main.py
+```
-### The Problem
+### Check it
-Usually when working with HTTP requests in Python:
+You will see output like:
-```python
-# Lots of boilerplate code
-import aiohttp
+```
+16:09:18.955 │ INFO │ fasthttp │ ✔ FastHTTP started
+16:09:19.519 │ INFO │ fasthttp │ ✔ GET https://httpbin.org/get [200] 458.26ms
+16:09:20.037 │ INFO │ fasthttp │ ✔ Done in 1.08s
+```
-async def main():
- async with aiohttp.ClientSession() as session:
- async with session.get("https://api.example.com/data") as resp:
- data = await resp.json()
- # ... handling
+The `resp` object gives you access to status, headers, and body. `resp.json()` returns the parsed response:
+
+```json
+{
+ "args": {},
+ "headers": {
+ "Accept": "*/*",
+ "Host": "httpbin.org",
+ "User-Agent": "python-httpx/0.28.1"
+ },
+ "origin": "...",
+ "url": "https://httpbin.org/get"
+}
```
-### Solution with FastHTTP
+### Interactive API docs
+
+Replace `app.run()` with `app.web_run()`:
```python
-# Clean and clear code
from fasthttp import FastHTTP
from fasthttp.response import Response
app = FastHTTP()
-@app.get(url="https://api.example.com/data")
-async def main(resp: Response) -> dict:
+
+@app.get(url="https://jsonplaceholder.typicode.com/users/1")
+async def get_user(resp: Response) -> dict:
return resp.json()
+
+
+@app.post(url="https://jsonplaceholder.typicode.com/users")
+async def create_user(resp: Response) -> dict:
+ return resp.json()
+
+
+if __name__ == "__main__":
+ app.web_run()
```
-## Documentation
+Now go to http://127.0.0.1:8000/docs.
-### Basics
+You will see the automatic interactive API documentation:
-- [Quick Start](quick-start.md) — start here
-- [Configuration](configuration.md) — application settings
-- [CLI](cli.md) — command line
+
-### Advanced Topics
+Expand any route to inspect parameters, schemas, and expected responses:
-- [Dependencies](dependencies.md) — request modification
-- [Middleware](middleware.md) — global logic
-- [GraphQL](graphql.md) — GraphQL support
-- [Pydantic](pydantic-validation.md) — validation
-- [HTTP/2](http2-support.md) — HTTP/2 support
-- [Security](security.md) — built-in protection
+
-### Additional
+Click **Try it out** to execute the request directly from the browser and see the real response:
-- [Examples](examples.md) — more code examples
-- [API Reference](api-reference.md) — complete reference
+
-## Comparison with Other Libraries
+### Upgrade the example
-| Library | Style | Async | Dependencies |
-|---------|-------|-------|--------------|
-| **FastHTTP** | Declarative | ✅ Yes | ✅ Yes |
-| requests | Imperative | ❌ No | ❌ No |
-| aiohttp | Imperative | ✅ Yes | ❌ No |
-| httpx | Imperative | ✅ Yes | ❌ No |
+Now modify `main.py` to get more out of FastHTTP. Each upgrade below builds on the previous one.
-## Usage Examples
+httpx[http2] — HTTP/2 protocol support.
+
+```console
+$ pip install fasthttp-client[http2]
+```
+
+Enable HTTP/2 per app instance:
+
+```python
+app = FastHTTP(http2=True)
+```
-- GitHub: https://github.com/ndugram/fasthttp
-- PyPI: https://pypi.org/project/fasthttp-client/
-- Documentation: https://fasthttp.ndugram.dev
+Servers that don't support HTTP/2 fall back to HTTP/1.1 automatically.
## License
-MIT License
+This project is licensed under the terms of the MIT license.
diff --git a/docs/en/openapi.md b/docs/en/openapi.md
index 0d8bc66..3a43b87 100644
--- a/docs/en/openapi.md
+++ b/docs/en/openapi.md
@@ -45,7 +45,7 @@ After running, open in your browser:
### Viewing Documentation
All your HTTP requests are automatically displayed in Swagger UI with:
-- HTTP method (GET, POST, PUT, DELETE)
+- HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
- URL address
- Description from docstring
- Request parameters
diff --git a/docs/en/quick-start.md b/docs/en/quick-start.md
index c98f210..98e586a 100644
--- a/docs/en/quick-start.md
+++ b/docs/en/quick-start.md
@@ -169,6 +169,20 @@ async def patch_user(resp: Response):
async def delete_user(resp: Response):
"""Delete user."""
return resp.status
+
+
+# HEAD — check endpoint headers
+@app.head(url="https://api.example.com/users")
+async def check_users(resp: Response):
+ """Check if endpoint exists."""
+ return resp.status
+
+
+# OPTIONS — get allowed methods
+@app.options(url="https://api.example.com/users")
+async def allowed_methods(resp: Response):
+ """Get allowed HTTP methods."""
+ return {"allow": resp.headers.get("allow", "")}
```
## Request Parameters
diff --git a/docs/en/tutorial/http-methods.md b/docs/en/tutorial/http-methods.md
index 2c75b29..60b2192 100644
--- a/docs/en/tutorial/http-methods.md
+++ b/docs/en/tutorial/http-methods.md
@@ -57,6 +57,26 @@ async def delete_user(resp: Response) -> dict:
return resp.status
```
+## HEAD - Check Endpoint
+
+Returns only headers, no body. Useful for checking if a resource exists or inspecting metadata.
+
+```python
+@app.head(url="https://api.example.com/users")
+async def check_users(resp: Response) -> int:
+ return resp.status
+```
+
+## OPTIONS - Allowed Methods
+
+Returns the HTTP methods supported by the endpoint.
+
+```python
+@app.options(url="https://api.example.com/users")
+async def allowed_methods(resp: Response) -> dict:
+ return {"allow": resp.headers.get("allow", "")}
+```
+
## Decorator Parameters
| Parameter | Description |
diff --git a/docs/en/tutorial/index.md b/docs/en/tutorial/index.md
index b4fd3b2..48f5fb0 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
+- [HTTP Methods](http-methods.md) - GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
- [Request Parameters](request-parameters.md) - Query, JSON, headers
- [Response Handling](response-handling.md) - Working with responses
diff --git a/docs/index.md b/docs/index.md
index b6edc23..8b4cd41 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,32 +1,73 @@
-# FastHTTP
+
+
+
+ Fast, simple HTTP client with decorator-based routing, async support, and beautiful logging. +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
httpx — async HTTP transport.
+* pydantic — response model validation and serialization.
+* orjson — fast JSON parsing.
+* typer — CLI interface.
+* uvicorn — ASGI server for `web_run()`.
## Installation
-```bash
-pip install fasthttp-client
+```console
+$ pip install fasthttp-client
+
+---> 100%
```
-For HTTP/2 support:
+## Example
-```bash
-pip install fasthttp-client[http2]
-```
+### Create it
-## Quick Example
+Create a file `main.py`:
```python
from fasthttp import FastHTTP
@@ -35,8 +76,8 @@ from fasthttp.response import Response
app = FastHTTP()
-@app.get(url="https://jsonplaceholder.typicode.com/posts/1")
-async def main(resp: Response) -> dict:
+@app.get(url="https://httpbin.org/get")
+async def get_data(resp: Response) -> dict:
return resp.json()
@@ -44,54 +85,343 @@ if __name__ == "__main__":
app.run()
```
-Output:
+### Run it
+```console
+$ python main.py
```
-INFO | fasthttp | FastHTTP started
-INFO | fasthttp | Sending 1 requests
-INFO | fasthttp | GET https://jsonplaceholder.typicode.com/posts/1 200 234.56ms
-INFO | fasthttp | Done in 0.24s
+
+### Check it
+
+You will see output like:
+
+```
+16:09:18.955 │ INFO │ fasthttp │ ✔ FastHTTP started
+16:09:19.519 │ INFO │ fasthttp │ ✔ GET https://httpbin.org/get [200] 458.26ms
+16:09:20.037 │ INFO │ fasthttp │ ✔ Done in 1.08s
+```
+
+The `resp` object gives you access to status, headers, and body. `resp.json()` returns the parsed response:
+
+```json
+{
+ "args": {},
+ "headers": {
+ "Accept": "*/*",
+ "Host": "httpbin.org",
+ "User-Agent": "python-httpx/0.28.1"
+ },
+ "origin": "...",
+ "url": "https://httpbin.org/get"
+}
```
-## Why FastHTTP?
+### Interactive API docs
-Traditional HTTP clients require verbose code:
+Replace `app.run()` with `app.web_run()`:
```python
-# Lots of boilerplate code
-import aiohttp
+from fasthttp import FastHTTP
+from fasthttp.response import Response
+
+app = FastHTTP()
+
+
+@app.get(url="https://jsonplaceholder.typicode.com/users/1")
+async def get_user(resp: Response) -> dict:
+ return resp.json()
+
+
+@app.post(url="https://jsonplaceholder.typicode.com/users")
+async def create_user(resp: Response) -> dict:
+ return resp.json()
+
+
+if __name__ == "__main__":
+ app.web_run()
+```
+
+Now go to http://127.0.0.1:8000/docs.
+
+You will see the automatic interactive API documentation:
+
+
+
+Expand any route to inspect parameters, schemas, and expected responses:
+
+
+
+Click **Try it out** to execute the request directly from the browser and see the real response:
+
+
+
+### Upgrade the example
+
+Now modify `main.py` to get more out of FastHTTP. Each upgrade below builds on the previous one.
+
+httpx[http2] — HTTP/2 protocol support.
-## GitHub
+```console
+$ pip install fasthttp-client[http2]
+```
+
+Enable HTTP/2 per app instance:
+
+```python
+app = FastHTTP(http2=True)
+```
-https://github.com/ndugram/fasthttp
+Servers that don't support HTTP/2 fall back to HTTP/1.1 automatically.
-## Documentation Site
+## License
-https://fasthttp.ndugram.dev
+This project is licensed under the terms of the MIT license.
diff --git a/docs/ru/api-reference.md b/docs/ru/api-reference.md
index 313acb4..ae5fd1a 100644
--- a/docs/ru/api-reference.md
+++ b/docs/ru/api-reference.md
@@ -21,6 +21,8 @@ app = FastHTTP(
put_request: dict = {},
patch_request: dict = {},
delete_request: dict = {},
+ head_request: dict = {},
+ options_request: dict = {},
middleware: list = [],
security: bool = True,
lifespan: Callable = None,
@@ -38,6 +40,8 @@ app = FastHTTP(
| `put_request` | `dict` | `{}` | Настройки для PUT |
| `patch_request` | `dict` | `{}` | Настройки для PATCH |
| `delete_request` | `dict` | `{}` | Настройки для DELETE |
+| `head_request` | `dict` | `{}` | Настройки для HEAD |
+| `options_request` | `dict` | `{}` | Настройки для OPTIONS |
| `middleware` | `list` | `[]` | Список middleware |
| `security` | `bool` | `True` | Включить встроенную защиту |
| `lifespan` | `Callable` | `None` | Контекстный менеджер для startup/shutdown |
@@ -189,7 +193,34 @@ async def lifespan(app):
response_model: type = None,
request_model: type = None,
responses: dict = None,
- delete_request: dict = None,
+)
+```
+
+### @app.head()
+
+```python
+@app.head(
+ url: str,
+ params: dict = None,
+ tags: list = [],
+ dependencies: list = [],
+ response_model: type = None,
+ request_model: type = None,
+ responses: dict = None,
+)
+```
+
+### @app.options()
+
+```python
+@app.options(
+ url: str,
+ params: dict = None,
+ tags: list = [],
+ dependencies: list = [],
+ response_model: type = None,
+ request_model: type = None,
+ responses: dict = None,
)
```
diff --git a/docs/ru/dependencies.md b/docs/ru/dependencies.md
index 3f9cb11..2df4351 100644
--- a/docs/ru/dependencies.md
+++ b/docs/ru/dependencies.md
@@ -88,7 +88,7 @@ def my_dependency_sync(route, config):
Объект `route` содержит всю информацию о запросе:
```python
-route.method # HTTP метод: "GET", "POST", "PUT", "PATCH", "DELETE"
+route.method # HTTP метод: "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"
route.url # Полный URL запроса
route.params # Query параметры (словарь)
route.json # JSON тело запроса (словарь)
diff --git a/docs/ru/index.md b/docs/ru/index.md
index 07fdfdb..80a14e2 100644
--- a/docs/ru/index.md
+++ b/docs/ru/index.md
@@ -1,44 +1,121 @@
-# FastHTTP Client
+
+
+
+ Быстрый, простой HTTP-клиент с декораторным роутингом, поддержкой async и красивым логированием. +
+ -Быстрый и простой HTTP-клиент с поддержкой async и красивым логированием. +--- + +**Документация**: https://fasthttp.ndugram.dev/ru/latest/ -[](https://pypi.org/project/fasthttp-client/) -[](https://pypi.org/project/fasthttp-client/) -[](https://github.com/ndugram/fasthttp) -[](https://pypi.org/project/fasthttp-client/) +**Исходный код**: https://github.com/ndugram/fasthttp --- -## Особенности +FastHTTP — это современная **асинхронная HTTP-клиентская библиотека** для Python, построенная поверх **httpx**. Она предоставляет API на основе декораторов — похожий на FastAPI, но для исходящих запросов — со структурированным логированием, middleware, валидацией через Pydantic и встроенным Swagger UI. -| | | -|:---|:---| -| **Декларативный стиль** | Определяйте запросы как функции с декораторами | -| **Асинхронность** | Параллельное выполнение запросов с asyncio | -| **Зависимости** | Модификация запросов перед отправкой | -| **Теги** | Группировка и фильтрация запросов | -| **Middleware** | Глобальная логика для всех запросов | -| **Pydantic** | Валидация ответов | -| **HTTP/2** | Поддержка современного протокола | -| **CLI** | Удобная командная строка | +Ключевые возможности: ---- +* **Быстрый**: построен на httpx с полной поддержкой async и параллельным выполнением запросов. +* **Простой**: определяйте HTTP-запросы как декорированные async-функции, без лишнего кода. +* **Типизированный**: полные аннотации типов; валидация ответов через Pydantic. +* **Логирует**: красочные структурированные логи запросов/ответов со временем выполнения, встроено. +* **Полный**: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS и GraphQL из коробки. +* **Расширяемый**: middleware, dependency injection, роутеры, lifespan-хуки. +* **Интерактивный**: встроенный Swagger UI через `app.web_run()` для просмотра и выполнения запросов в браузере. +* **HTTP/2**: опциональная поддержка HTTP/2 с автоматическим fallback на HTTP/1.1. + +## Требования + +Python 3.10+ + +FastHTTP стоит на плечах гигантов: + +*httpx — асинхронный HTTP-транспорт.
+* pydantic — валидация и сериализация моделей ответов.
+* orjson — быстрый парсинг JSON.
+* typer — CLI-интерфейс.
+* uvicorn — ASGI-сервер для `web_run()`.
## Установка
-```bash
-pip install fasthttp-client
+```console
+$ pip install fasthttp-client
+
+---> 100%
+```
+
+## Пример
+
+### Создайте файл
+
+Создайте файл `main.py`:
+
+```python
+from fasthttp import FastHTTP
+from fasthttp.response import Response
+
+app = FastHTTP()
+
+
+@app.get(url="https://httpbin.org/get")
+async def get_data(resp: Response) -> dict:
+ return resp.json()
+
+
+if __name__ == "__main__":
+ app.run()
```
-Для HTTP/2:
+### Запустите
-```bash
-pip install fasthttp-client[http2]
+```console
+$ python main.py
```
----
+### Проверьте результат
+
+Вы увидите вывод такого вида:
+
+```
+16:09:18.955 │ INFO │ fasthttp │ ✔ FastHTTP started
+16:09:19.519 │ INFO │ fasthttp │ ✔ GET https://httpbin.org/get [200] 458.26ms
+16:09:20.037 │ INFO │ fasthttp │ ✔ Done in 1.08s
+```
+
+Объект `resp` даёт доступ к статусу, заголовкам и телу ответа. `resp.json()` возвращает распарсенный ответ:
+
+```json
+{
+ "args": {},
+ "headers": {
+ "Accept": "*/*",
+ "Host": "httpbin.org",
+ "User-Agent": "python-httpx/0.28.1"
+ },
+ "origin": "...",
+ "url": "https://httpbin.org/get"
+}
+```
+
+### Интерактивная документация API
-## Быстрый пример
+Замените `app.run()` на `app.web_run()`:
```python
from fasthttp import FastHTTP
@@ -47,106 +124,316 @@ from fasthttp.response import Response
app = FastHTTP()
-@app.get(url="https://jsonplaceholder.typicode.com/posts/1")
-async def main(resp: Response) -> dict:
+@app.get(url="https://jsonplaceholder.typicode.com/users/1")
+async def get_user(resp: Response) -> dict:
+ return resp.json()
+
+
+@app.post(url="https://jsonplaceholder.typicode.com/users")
+async def create_user(resp: Response) -> dict:
return resp.json()
+if __name__ == "__main__":
+ app.web_run()
+```
+
+Перейдите на http://127.0.0.1:8000/docs.
+
+Вы увидите автоматическую интерактивную документацию API:
+
+
+
+Раскройте любой маршрут для просмотра параметров, схем и ожидаемых ответов:
+
+
+
+Нажмите **Try it out**, чтобы выполнить запрос прямо из браузера и увидеть реальный ответ:
+
+
+
+### Расширение примера
+
+Измените `main.py`, чтобы использовать больше возможностей FastHTTP. Каждое расширение опирается на предыдущее.
+
+httpx[http2] — поддержка протокола HTTP/2.
+
+```console
+$ pip install fasthttp-client[http2]
+```
+
+Включение HTTP/2 для конкретного приложения:
+
+```python
+app = FastHTTP(http2=True)
+```
+
+Серверы без поддержки HTTP/2 автоматически переходят на HTTP/1.1.
## Лицензия
-MIT License
+Этот проект лицензирован на условиях лицензии MIT.
diff --git a/docs/ru/openapi.md b/docs/ru/openapi.md
index fcac53e..87d0c25 100644
--- a/docs/ru/openapi.md
+++ b/docs/ru/openapi.md
@@ -45,7 +45,7 @@ app.web_run()
### Просмотр документации
Все ваши HTTP-запросы автоматически отображаются в Swagger UI с:
-- Методом HTTP (GET, POST, PUT, DELETE)
+- Методом HTTP (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
- URL адресом
- Описанием из docstring
- Параметрами запроса
diff --git a/docs/ru/quick-start.md b/docs/ru/quick-start.md
index ead7e04..b63cd43 100644
--- a/docs/ru/quick-start.md
+++ b/docs/ru/quick-start.md
@@ -170,6 +170,20 @@ async def patch_user(resp: Response):
async def delete_user(resp: Response):
"""Удалить пользователя."""
return resp.status
+
+
+# HEAD — проверка заголовков endpoint'а
+@app.head(url="https://api.example.com/users")
+async def check_users(resp: Response):
+ """Проверить существование endpoint'а."""
+ return resp.status
+
+
+# OPTIONS — получение разрешённых методов
+@app.options(url="https://api.example.com/users")
+async def allowed_methods(resp: Response):
+ """Получить разрешённые HTTP-методы."""
+ return {"allow": resp.headers.get("allow", "")}
```
## Параметры запроса
diff --git a/docs/ru/tutorial/http-methods.md b/docs/ru/tutorial/http-methods.md
index 416fb43..002abbc 100644
--- a/docs/ru/tutorial/http-methods.md
+++ b/docs/ru/tutorial/http-methods.md
@@ -57,6 +57,26 @@ async def delete_user(resp: Response) -> int:
return resp.status
```
+## HEAD - проверка endpoint'а
+
+Возвращает только заголовки, без тела. Удобно для проверки существования ресурса или получения метаданных.
+
+```python
+@app.head(url="https://api.example.com/users")
+async def check_users(resp: Response) -> int:
+ return resp.status
+```
+
+## OPTIONS - разрешённые методы
+
+Возвращает HTTP-методы, поддерживаемые endpoint'ом.
+
+```python
+@app.options(url="https://api.example.com/users")
+async def allowed_methods(resp: Response) -> dict:
+ return {"allow": resp.headers.get("allow", "")}
+```
+
## Возвращаемые значения
Обработчики могут возвращать разные типы:
diff --git a/docs/ru/tutorial/index.md b/docs/ru/tutorial/index.md
index 5c8f657..03271d4 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
+- [HTTP методы](http-methods.md) - GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
- [Параметры запроса](request-parameters.md) - Query, JSON, заголовки
- [Обработка ответа](response-handling.md) - Работа с ответами
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
new file mode 100644
index 0000000..73f7289
--- /dev/null
+++ b/docs/stylesheets/extra.css
@@ -0,0 +1,606 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,300;0,14..32,400;0,14..32,500;0,14..32,600;0,14..32,700;0,14..32,800;1,14..32,400&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
+
+:root,
+[data-md-color-scheme="slate"] {
+ --nf-brand-blue: #2e73ff;
+ --nf-brand-green: #30fc9d;
+ --nf-bg: #0f0e14;
+ --nf-surface: #17161e;
+ --nf-surface-2: #1e1d27;
+ --nf-border: rgba(255, 255, 255, 0.07);
+ --nf-border-blue: rgba(46, 115, 255, 0.2);
+ --nf-text: #eeeef2;
+ --nf-text-2: rgba(238, 238, 242, 0.72);
+ --nf-text-muted: rgba(238, 238, 242, 0.42);
+ --nf-radius-sm: 8px;
+ --nf-radius: 14px;
+ --nf-radius-lg: 20px;
+ --nf-radius-xl: 28px;
+
+ --md-default-bg-color: #0f0e14;
+ --md-default-bg-color--light: rgba(23, 22, 30, 0.54);
+ --md-default-bg-color--lighter: rgba(23, 22, 30, 0.26);
+ --md-default-bg-color--lightest: rgba(23, 22, 30, 0.07);
+
+ --md-default-fg-color: var(--nf-text);
+ --md-default-fg-color--light: var(--nf-text-2);
+ --md-default-fg-color--lighter: var(--nf-text-muted);
+ --md-default-fg-color--lightest: rgba(238, 238, 242, 0.07);
+
+ --md-primary-fg-color: var(--nf-brand-blue);
+ --md-primary-fg-color--light: rgba(46, 115, 255, 0.14);
+ --md-primary-fg-color--dark: #1a56d6;
+ --md-primary-bg-color: #ffffff;
+ --md-primary-bg-color--light: rgba(255, 255, 255, 0.8);
+
+ --md-accent-fg-color: var(--nf-brand-green);
+ --md-accent-fg-color--transparent: rgba(48, 252, 157, 0.1);
+ --md-accent-bg-color: #0f0e14;
+
+ --md-code-bg-color: #0c0b12;
+ --md-code-fg-color: #c9d1d9;
+
+ --md-typeset-color: var(--nf-text);
+ --md-typeset-a-color: var(--nf-brand-blue);
+ --md-typeset-mark-color: rgba(46, 115, 255, 0.18);
+
+ --md-admonition-fg-color: var(--nf-text);
+ --md-admonition-bg-color: var(--nf-surface);
+
+ --md-footer-bg-color: var(--nf-surface);
+ --md-footer-bg-color--dark: var(--nf-bg);
+ --md-footer-fg-color: var(--nf-text);
+ --md-footer-fg-color--light: var(--nf-text-2);
+ --md-footer-fg-color--lighter: var(--nf-text-muted);
+
+ --md-text-font-family: "Inter", system-ui, -apple-system, sans-serif;
+ --md-code-font-family: "JetBrains Mono", "Fira Code", monospace;
+
+ --md-shadow-z1: 0 2px 12px rgba(0, 0, 0, 0.45);
+ --md-shadow-z2: 0 4px 28px rgba(0, 0, 0, 0.55);
+ --md-shadow-z3: 0 8px 48px rgba(0, 0, 0, 0.65);
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeLegibility;
+}
+
+body {
+ font-family: var(--md-text-font-family) !important;
+ background-color: var(--nf-bg) !important;
+}
+
+::selection {
+ background: rgba(46, 115, 255, 0.28);
+ color: var(--nf-text);
+}
+
+::-webkit-scrollbar { width: 5px; height: 5px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb { background: rgba(46, 115, 255, 0.3); border-radius: 99px; }
+::-webkit-scrollbar-thumb:hover { background: var(--nf-brand-blue); }
+
+.md-header {
+ background: var(--nf-surface) !important;
+ box-shadow: 0 1px 0 var(--nf-border), 0 4px 20px rgba(0, 0, 0, 0.4) !important;
+ backdrop-filter: blur(12px);
+}
+
+.md-header__title {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 700 !important;
+ letter-spacing: -0.025em;
+ font-size: 1rem !important;
+}
+
+.md-header__button {
+ border-radius: var(--nf-radius-sm) !important;
+ transition: background 0.18s ease, color 0.18s ease !important;
+}
+
+.md-header__button:hover {
+ background: rgba(46, 115, 255, 0.12) !important;
+ color: var(--nf-brand-blue) !important;
+ opacity: 1 !important;
+}
+
+.md-tabs {
+ background: var(--nf-surface) !important;
+ border-bottom: 1px solid var(--nf-border);
+}
+
+.md-tabs__link {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 600 !important;
+ font-size: 0.8rem !important;
+ opacity: 0.55 !important;
+ transition: opacity 0.18s ease, color 0.18s ease !important;
+ letter-spacing: 0.01em;
+}
+
+.md-tabs__link:hover { opacity: 0.9 !important; }
+
+.md-tabs__link--active {
+ opacity: 1 !important;
+ color: var(--nf-brand-blue) !important;
+}
+
+.md-sidebar {
+ background: transparent !important;
+}
+
+.md-sidebar__scrollwrap {
+ scrollbar-width: none;
+}
+.md-sidebar__scrollwrap::-webkit-scrollbar { display: none; }
+
+.md-nav__title {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 700 !important;
+ font-size: 0.68rem !important;
+ letter-spacing: 0.09em !important;
+ text-transform: uppercase !important;
+ color: var(--nf-text-muted) !important;
+}
+
+.md-nav__link {
+ font-family: var(--md-text-font-family) !important;
+ color: var(--nf-text-2) !important;
+ font-weight: 500 !important;
+ font-size: 0.8rem !important;
+ border-radius: var(--nf-radius-sm) !important;
+ margin: 1px 0 !important;
+ padding: 5px 10px !important;
+ transition: background 0.15s ease, color 0.15s ease !important;
+}
+
+.md-nav__link:hover {
+ background: rgba(46, 115, 255, 0.09) !important;
+ color: var(--nf-brand-blue) !important;
+}
+
+.md-nav__link--active {
+ background: rgba(46, 115, 255, 0.12) !important;
+ color: var(--nf-brand-blue) !important;
+ font-weight: 600 !important;
+}
+
+.md-nav--secondary .md-nav__link {
+ font-size: 0.75rem !important;
+ color: var(--nf-text-muted) !important;
+ padding: 4px 10px !important;
+}
+
+.md-nav--secondary .md-nav__link:hover,
+.md-nav--secondary .md-nav__link--active {
+ color: var(--nf-brand-blue) !important;
+ background: rgba(46, 115, 255, 0.08) !important;
+}
+
+.md-main {
+ background: var(--nf-bg) !important;
+}
+
+.md-content__inner {
+ background: var(--nf-surface) !important;
+ border-radius: var(--nf-radius-xl) !important;
+ padding: 44px 52px !important;
+ margin-top: 24px !important;
+ margin-bottom: 24px !important;
+ border: 1px solid var(--nf-border) !important;
+ box-shadow: 0 4px 48px rgba(0, 0, 0, 0.35) !important;
+ animation: fade-in-up 0.38s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+@media (max-width: 960px) {
+ .md-content__inner {
+ padding: 28px 24px !important;
+ border-radius: var(--nf-radius-lg) !important;
+ }
+}
+
+@media (max-width: 600px) {
+ .md-content__inner {
+ padding: 20px 16px !important;
+ border-radius: var(--nf-radius) !important;
+ }
+}
+
+.md-typeset {
+ font-family: var(--md-text-font-family) !important;
+ line-height: 1.72 !important;
+ color: var(--nf-text-2) !important;
+}
+
+.md-typeset h1 {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 800 !important;
+ letter-spacing: -0.04em !important;
+ line-height: 1.15 !important;
+ color: var(--nf-text) !important;
+ margin-bottom: 0.4em !important;
+}
+
+.md-typeset h2 {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 700 !important;
+ letter-spacing: -0.025em !important;
+ color: var(--nf-text) !important;
+ border-bottom: 1px solid var(--nf-border) !important;
+ padding-bottom: 0.35em !important;
+ margin-top: 2em !important;
+}
+
+.md-typeset h3 {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 600 !important;
+ letter-spacing: -0.02em !important;
+ color: var(--nf-text) !important;
+}
+
+.md-typeset h4,
+.md-typeset h5,
+.md-typeset h6 {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 600 !important;
+ color: var(--nf-text) !important;
+ letter-spacing: -0.01em !important;
+}
+
+.md-typeset p {
+ color: var(--nf-text-2);
+}
+
+.md-typeset a {
+ color: var(--nf-brand-blue) !important;
+ text-decoration: none !important;
+ font-weight: 500 !important;
+ transition: color 0.15s ease !important;
+}
+
+.md-typeset a:hover {
+ color: #6ba3ff !important;
+ text-decoration: underline !important;
+ text-underline-offset: 3px;
+}
+
+.md-typeset strong {
+ color: var(--nf-text) !important;
+ font-weight: 700 !important;
+}
+
+.md-typeset em {
+ color: var(--nf-text-2);
+}
+
+.md-typeset code {
+ font-family: var(--md-code-font-family) !important;
+ background: rgba(46, 115, 255, 0.1) !important;
+ color: #82b4ff !important;
+ border-radius: var(--nf-radius-sm) !important;
+ padding: 1px 7px !important;
+ font-size: 0.86em !important;
+ border: 1px solid rgba(46, 115, 255, 0.16);
+ font-weight: 400 !important;
+}
+
+.md-typeset pre {
+ border-radius: var(--nf-radius) !important;
+ border: 1px solid var(--nf-border) !important;
+ background: var(--md-code-bg-color) !important;
+ overflow: hidden;
+}
+
+.md-typeset pre > code {
+ font-family: var(--md-code-font-family) !important;
+ background: transparent !important;
+ color: inherit !important;
+ border: none !important;
+ padding: 0 !important;
+ font-size: 0.84em !important;
+ font-weight: 400 !important;
+}
+
+.highlight {
+ background: var(--md-code-bg-color) !important;
+ border-radius: var(--nf-radius) !important;
+ border: 1px solid var(--nf-border) !important;
+ overflow: hidden;
+}
+
+.md-clipboard {
+ color: var(--nf-text-muted) !important;
+ transition: color 0.15s ease, background 0.15s ease !important;
+ border-radius: 7px !important;
+}
+
+.md-clipboard:hover {
+ color: var(--nf-brand-blue) !important;
+ background: rgba(46, 115, 255, 0.1) !important;
+}
+
+.md-typeset table:not([class]) {
+ border-radius: var(--nf-radius) !important;
+ overflow: hidden;
+ border: 1px solid var(--nf-border) !important;
+ box-shadow: none !important;
+ display: table;
+ width: 100%;
+}
+
+.md-typeset table:not([class]) th {
+ background: rgba(46, 115, 255, 0.1) !important;
+ color: var(--nf-brand-blue) !important;
+ font-weight: 700 !important;
+ font-size: 0.72rem !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.07em !important;
+ padding: 11px 14px !important;
+ border-bottom: 1px solid rgba(46, 115, 255, 0.18) !important;
+}
+
+.md-typeset table:not([class]) td {
+ padding: 10px 14px !important;
+ border-bottom: 1px solid var(--nf-border) !important;
+ color: var(--nf-text-2) !important;
+ font-size: 0.88rem !important;
+}
+
+.md-typeset table:not([class]) tr:last-child td {
+ border-bottom: none !important;
+}
+
+.md-typeset table:not([class]) tr:hover td {
+ background: rgba(46, 115, 255, 0.04) !important;
+}
+
+.md-typeset .admonition,
+.md-typeset details {
+ border-radius: var(--nf-radius) !important;
+ border: 1px solid var(--nf-border) !important;
+ background: var(--nf-surface-2) !important;
+ box-shadow: none !important;
+}
+
+.md-typeset .admonition-title,
+.md-typeset summary {
+ font-weight: 600 !important;
+ font-family: var(--md-text-font-family) !important;
+ font-size: 0.85rem !important;
+ border-radius: calc(var(--nf-radius) - 1px) calc(var(--nf-radius) - 1px) 0 0 !important;
+}
+
+.md-typeset .tip, .md-typeset .success {
+ border-left-color: var(--nf-brand-green) !important;
+}
+.md-typeset .tip > .admonition-title,
+.md-typeset .success > .admonition-title {
+ background: rgba(48, 252, 157, 0.07) !important;
+}
+.md-typeset .tip > .admonition-title::before,
+.md-typeset .success > .admonition-title::before {
+ color: var(--nf-brand-green) !important;
+}
+
+.md-typeset .note, .md-typeset .info, .md-typeset .abstract {
+ border-left-color: var(--nf-brand-blue) !important;
+}
+.md-typeset .note > .admonition-title,
+.md-typeset .info > .admonition-title,
+.md-typeset .abstract > .admonition-title {
+ background: rgba(46, 115, 255, 0.07) !important;
+}
+
+.md-typeset .warning {
+ border-left-color: #f5a623 !important;
+}
+.md-typeset .warning > .admonition-title {
+ background: rgba(245, 166, 35, 0.07) !important;
+}
+
+.md-typeset .danger, .md-typeset .error {
+ border-left-color: #ff5a5a !important;
+}
+.md-typeset .danger > .admonition-title,
+.md-typeset .error > .admonition-title {
+ background: rgba(255, 90, 90, 0.07) !important;
+}
+
+.md-typeset details > summary {
+ cursor: pointer;
+ color: var(--nf-brand-blue) !important;
+ font-weight: 600 !important;
+ transition: color 0.15s ease;
+}
+
+.md-typeset details > summary:hover {
+ color: #6ba3ff !important;
+}
+
+.md-search__form {
+ background: rgba(255, 255, 255, 0.06) !important;
+ border-radius: var(--nf-radius-sm) !important;
+ border: 1px solid transparent !important;
+ transition: border-color 0.18s ease, box-shadow 0.18s ease !important;
+}
+
+.md-search__form:focus-within {
+ background: rgba(46, 115, 255, 0.07) !important;
+ border-color: rgba(46, 115, 255, 0.28) !important;
+ box-shadow: 0 0 0 3px rgba(46, 115, 255, 0.1) !important;
+}
+
+.md-search__input {
+ font-family: var(--md-text-font-family) !important;
+ color: var(--nf-text) !important;
+ font-size: 0.85rem !important;
+}
+
+.md-search__input::placeholder {
+ color: var(--nf-text-muted) !important;
+}
+
+.md-search-result {
+ background: var(--nf-surface) !important;
+ border-radius: var(--nf-radius) !important;
+ border: 1px solid var(--nf-border) !important;
+}
+
+.md-search-result__link:hover .md-search-result__article {
+ background: rgba(46, 115, 255, 0.07) !important;
+}
+
+.md-search-result__teaser mark {
+ color: var(--nf-brand-blue) !important;
+ background: transparent !important;
+ font-weight: 700;
+}
+
+.md-search-result__title {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 600 !important;
+}
+
+.md-footer {
+ background: var(--nf-surface) !important;
+ border-top: 1px solid var(--nf-border) !important;
+ border-radius: var(--nf-radius-xl) var(--nf-radius-xl) 0 0;
+ margin-top: 12px;
+}
+
+.md-footer-meta {
+ background: transparent !important;
+}
+
+.md-footer-nav__link {
+ font-family: var(--md-text-font-family) !important;
+ transition: color 0.15s ease !important;
+}
+
+.md-footer-nav__link:hover .md-footer-nav__title {
+ color: var(--nf-brand-blue) !important;
+}
+
+.md-footer-nav__title {
+ font-weight: 600 !important;
+}
+
+.md-typeset .md-button {
+ font-family: var(--md-text-font-family) !important;
+ font-weight: 600 !important;
+ font-size: 0.85rem !important;
+ border-radius: var(--nf-radius-sm) !important;
+ padding: 9px 20px !important;
+ letter-spacing: -0.01em;
+ transition: all 0.18s ease !important;
+ background: rgba(46, 115, 255, 0.12) !important;
+ color: var(--nf-brand-blue) !important;
+ border: 1px solid rgba(46, 115, 255, 0.25) !important;
+}
+
+.md-typeset .md-button:hover {
+ background: var(--nf-brand-blue) !important;
+ color: #ffffff !important;
+ border-color: var(--nf-brand-blue) !important;
+ transform: translateY(-1px) !important;
+ box-shadow: 0 6px 20px rgba(46, 115, 255, 0.3) !important;
+}
+
+.md-typeset .md-button--primary {
+ background: var(--nf-brand-blue) !important;
+ color: #ffffff !important;
+ border-color: var(--nf-brand-blue) !important;
+}
+
+.md-typeset .md-button--primary:hover {
+ background: #1a56d6 !important;
+ border-color: #1a56d6 !important;
+}
+
+.md-typeset hr {
+ border-color: var(--nf-border) !important;
+ margin: 2.2em 0 !important;
+}
+
+.md-typeset blockquote {
+ border-left: 3px solid var(--nf-brand-blue) !important;
+ background: rgba(46, 115, 255, 0.05) !important;
+ border-radius: 0 var(--nf-radius-sm) var(--nf-radius-sm) 0 !important;
+ color: var(--nf-text-2) !important;
+ padding: 12px 18px !important;
+ margin-left: 0 !important;
+}
+
+.md-typeset ul li::marker { color: var(--nf-brand-blue); }
+.md-typeset ol li::marker { color: var(--nf-brand-blue); font-weight: 700; }
+
+.md-typeset kbd {
+ font-family: var(--md-code-font-family) !important;
+ background: var(--nf-surface-2) !important;
+ border-color: var(--nf-border) !important;
+ box-shadow: none !important;
+ color: var(--nf-text) !important;
+ font-size: 0.8em !important;
+}
+
+.md-tag {
+ background: rgba(46, 115, 255, 0.1) !important;
+ color: var(--nf-brand-blue) !important;
+ border-radius: 6px !important;
+ font-weight: 600 !important;
+ font-size: 0.7rem !important;
+ padding: 2px 9px !important;
+}
+
+[data-md-color-scheme="default"] {
+ --nf-bg: #f5f5f7;
+ --nf-surface: #ffffff;
+ --nf-surface-2: #f0f0f4;
+ --nf-border: rgba(0, 0, 0, 0.08);
+ --nf-text: #111117;
+ --nf-text-2: rgba(17, 17, 23, 0.72);
+ --nf-text-muted: rgba(17, 17, 23, 0.4);
+
+ --md-default-bg-color: #f5f5f7;
+ --md-default-bg-color--light: rgba(240, 240, 244, 0.54);
+ --md-default-fg-color: #111117;
+ --md-default-fg-color--light: rgba(17, 17, 23, 0.72);
+ --md-default-fg-color--lighter: rgba(17, 17, 23, 0.38);
+ --md-default-fg-color--lightest: rgba(17, 17, 23, 0.07);
+
+ --md-code-bg-color: #f0f0f5;
+ --md-code-fg-color: #1a1a2e;
+
+ --md-footer-bg-color: #e8e8ee;
+ --md-footer-bg-color--dark: #d8d8e0;
+ --md-footer-fg-color: #111117;
+}
+
+[data-md-color-scheme="default"] .md-typeset code {
+ background: rgba(46, 115, 255, 0.08) !important;
+ color: #1a56d6 !important;
+ border-color: rgba(46, 115, 255, 0.2) !important;
+}
+
+[data-md-color-scheme="default"] .md-typeset blockquote {
+ background: rgba(46, 115, 255, 0.04) !important;
+}
+
+@keyframes fade-in-up {
+ from {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
diff --git a/examples/basic/test_head.py b/examples/basic/test_head.py
new file mode 100644
index 0000000..331d256
--- /dev/null
+++ b/examples/basic/test_head.py
@@ -0,0 +1,16 @@
+from fasthttp import FastHTTP
+from fasthttp.response import Response
+
+app = FastHTTP()
+
+
+@app.head(url="https://httpbin.org/get")
+async def check_endpoint(resp: Response) -> int:
+ print(f"HEAD: {resp.status}")
+ print(f"Content-Type: {resp.headers.get('content-type', 'n/a')}")
+ print(f"Content-Length: {resp.headers.get('content-length', 'n/a')}")
+ return resp.status
+
+
+if __name__ == "__main__":
+ app.run()
diff --git a/examples/basic/test_options.py b/examples/basic/test_options.py
new file mode 100644
index 0000000..070275c
--- /dev/null
+++ b/examples/basic/test_options.py
@@ -0,0 +1,16 @@
+from fasthttp import FastHTTP
+from fasthttp.response import Response
+
+app = FastHTTP()
+
+
+@app.options(url="https://httpbin.org/get")
+async def check_allowed_methods(resp: Response) -> dict:
+ allow = resp.headers.get("allow", "")
+ print(f"OPTIONS: {resp.status}")
+ print(f"Allowed methods: {allow}")
+ return {"status": resp.status, "allow": allow}
+
+
+if __name__ == "__main__":
+ app.run()
diff --git a/fasthttp/app.py b/fasthttp/app.py
index 9559096..c06ea0e 100644
--- a/fasthttp/app.py
+++ b/fasthttp/app.py
@@ -45,7 +45,7 @@ class FastHTTP:
web frameworks like FastAPI, but for outgoing requests.
The application manages:
- - Request routing via decorators (GET, POST, PUT, PATCH, DELETE)
+ - Request routing via decorators (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
- Per-method default request configuration
- Async request execution
- Structured and colorized logging
@@ -80,7 +80,7 @@ def __init__(
"""
Enable debug mode.
- When enabled, FastHTTP will print datailed
+ When enabled, FastHTTP will print detailed
tracebacks and requests/response logs.
"""
),
@@ -153,8 +153,6 @@ def __init__(
RequestsOptinal | None,
Doc(
"""
- # Create the app
-
Default configuration for PATCH requests.
Used to configure headers, timeout and
@@ -170,19 +168,45 @@ def __init__(
"""
Default configuration for DELETE requests.
- Allows defining haders, timeout,
+ Allows defining headers, timeout,
and other options for DELETE requests.
"""
),
]
) = None,
+ head_request: (
+ Annotated[
+ RequestsOptinal | None,
+ Doc(
+ """
+ Default configuration for HEAD requests.
+
+ Allows defining headers, timeout,
+ and other options for HEAD requests.
+ """
+ ),
+ ]
+ ) = None,
+ options_request: (
+ Annotated[
+ RequestsOptinal | None,
+ Doc(
+ """
+ Default configuration for OPTIONS requests.
+
+ Allows defining headers, timeout,
+ and other options for OPTIONS requests.
+ """
+ ),
+ ]
+ ) = None,
security: Annotated[
bool,
Doc(
"""
Enable built-in security features.
- When enabled (default), FastHTTP automatically against:
+ When enabled (default), FastHTTP automatically protects against:
- SSRF attacks (blocking localhost and private IPs)
- Secret leakage in logs
- Circuit breaker for failed hosts
@@ -272,7 +296,7 @@ async def lifespan(app: FastHTTP):
Doc(
"""
The version of UUID to generate on startup if `generate_startup_uuid` is True.
- Supported versions: 'v4' (random UUID), 'v7' (time-based UUID with random component, requires Python 3.12+).
+ Supported versions: 'v4' (random UUID), 'v7' (time-based UUID with random component, requires Python 3.13+).
**Example**
```python
from fashttp import FastHTTP
@@ -346,6 +370,8 @@ async def lifespan(app: FastHTTP):
"PUT": put_request or {},
"PATCH": patch_request or {},
"DELETE": delete_request or {},
+ "HEAD": head_request or {},
+ "OPTIONS": options_request or {},
}
self.security_enabled = security
@@ -471,7 +497,7 @@ def _resolve_url(self, url: str) -> str:
def _add_route(
self,
*,
- method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"],
+ method: Literal["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
url: str,
params: dict | None = None,
json: dict | None = None,
@@ -716,6 +742,50 @@ def delete(
responses=responses,
)
+ def head(
+ self,
+ url: str,
+ *,
+ params: dict | None = None,
+ response_model: type[BaseModel] | None = None,
+ request_model: type[BaseModel] | None = None,
+ tags: list[str] | None = None,
+ dependencies: list | None = None,
+ responses: dict[int, dict[Literal["model"], type[BaseModel]]] | None = None,
+ ) -> Callable[[Callable[..., object]], Callable[..., object]]:
+ return self._add_route(
+ method="HEAD",
+ url=url,
+ params=params,
+ response_model=response_model,
+ request_model=request_model,
+ tags=tags,
+ dependencies=dependencies,
+ responses=responses,
+ )
+
+ def options(
+ self,
+ url: str,
+ *,
+ params: dict | None = None,
+ response_model: type[BaseModel] | None = None,
+ request_model: type[BaseModel] | None = None,
+ tags: list[str] | None = None,
+ dependencies: list | None = None,
+ responses: dict[int, dict[Literal["model"], type[BaseModel]]] | None = None,
+ ) -> Callable[[Callable[..., object]], Callable[..., object]]:
+ return self._add_route(
+ method="OPTIONS",
+ url=url,
+ params=params,
+ response_model=response_model,
+ request_model=request_model,
+ tags=tags,
+ dependencies=dependencies,
+ responses=responses,
+ )
+
def graphql(
self,
url: Annotated[
diff --git a/fasthttp/response.py b/fasthttp/response.py
index 8e74079..8db929c 100644
--- a/fasthttp/response.py
+++ b/fasthttp/response.py
@@ -291,8 +291,5 @@ def assets(
return result
def __repr__(self) -> str:
- """
- Return a debug-friendly string representation
- of the response.
- """
+ """Return a debug-friendly string representation of the response."""
return f"