Skip to content

Commit c6319cf

Browse files
committed
"Release 0.2.1: async support for extra_log_fields + Python 3.9 compatibility"
1 parent 222df26 commit c6319cf

6 files changed

Lines changed: 74 additions & 20 deletions

File tree

README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,20 @@ Reading the [full documentation](https://akutayural.github.io/APIException/) is
3434
---
3535

3636
> [!IMPORTANT]
37-
New in v0.2.0: <br>
38-
- Advanced structured logging (`log_level`, `log_header_keys`, `extra_log_fields`)
39-
- Response headers echo (`response_headers`)
40-
- Type-safety improvements with `mypy`
41-
- APIException accepts `headers` param <br>
42-
- Cleaner import/export structure <br>
43-
- 📢 Featured in [**Python Weekly #710**](https://www.pythonweekly.com/p/python-weekly-issue-710-august-14-2025-3200567a10d37d87) 🎉<br>
44-
- 👉 For full details and usage examples, see
45-
[**register_exception_handlers reference**](https://akutayural.github.io/APIException/usage/register_exception_handlers/)
37+
> **New in v0.2.1:** <br>
38+
> -**Async support** for `extra_log_fields` → you can now use `await request.body()` directly.
39+
> - 🧩 **Python 3.9 compatibility restored** with `typing_extensions.TypeGuard`.
40+
> - ⚡ Improved `response_utils.py` type-safety for all Python versions. <br>
41+
> - 📦 Updated dependencies and `pyproject.toml` for wider environment support. <br>
42+
>
43+
> **Previously in v0.2.0:** <br>
44+
> - Advanced structured logging (`log_level`, `log_header_keys`, `extra_log_fields`)
45+
> - Response headers echo (`response_headers`)
46+
> - Type-safety improvements with `mypy`
47+
> - `APIException` accepts `headers` param <br>
48+
> - Cleaner import/export structure <br>
49+
> - 📢 Featured in [**Python Weekly #710**](https://www.pythonweekly.com/p/python-weekly-issue-710-august-14-2025-3200567a10d37d87) 🎉 <br>
50+
> - 👉 For full details and usage examples, see [**register_exception_handlers reference**](https://akutayural.github.io/APIException/usage/register_exception_handlers/) <br>
4651
4752
---
4853

@@ -448,7 +453,7 @@ Benchmark scripts and raw Locust reports are available in the [benchmark](https:
448453

449454
## 📜 Changelog
450455

451-
Currently, the most stable and suggested version is v0.2.0
456+
Currently, the most stable and suggested version is v0.2.1
452457

453458
👉 [See full changelog](https://akutayural.github.io/APIException/changelog/)
454459

api_exception/__init__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import logging
44
import traceback
5-
from typing import Callable, Tuple, Optional, Dict, Any, Iterable, Union
5+
from typing import Callable, Tuple, Optional, Dict, Any, Iterable, Union, Awaitable
66
from typing import Literal
7+
import inspect
8+
79

810
from fastapi import FastAPI, Request
911
from fastapi.encoders import jsonable_encoder
@@ -40,7 +42,11 @@
4042

4143
LogLevelLiteral = Literal[10, 20, 30, 40, 50]
4244
HeaderKeys = Tuple[str, ...]
43-
ExtraLogFields = Callable[[Request, Optional[BaseException]], Dict[str, Any]]
45+
46+
ExtraLogFields = Callable[
47+
[Callable[[Request, Optional[BaseException]], Dict[str, Any]]],
48+
Callable[[Request, Optional[BaseException]], Awaitable[Dict[str, Any]]]
49+
]
4450

4551

4652
def register_exception_handlers(
@@ -115,6 +121,8 @@ def register_exception_handlers(
115121
extra_log_fields : Callable[[Request, Optional[BaseException]], Dict[str, Any]] | None, default=None
116122
A hook to inject **custom** fields into the log `meta`. Receives `(request, exc)` and must return a dict.
117123
Useful for business context (tenant_id, feature flags, masked user ids, etc.).
124+
def my_fields(req, exc): ...
125+
async def my_fields(req, exc): ...
118126
Example:
119127
```python
120128
def my_extra_fields(req, exc):
@@ -364,9 +372,12 @@ async def api_exception_handler(request: Request, exc: APIException):
364372

365373
if extra_log_fields:
366374
try:
367-
meta.update(extra_log_fields(request, exc))
375+
result = extra_log_fields(request, exc)
376+
if inspect.isawaitable(result):
377+
result = await result
378+
if isinstance(result, dict):
379+
meta.update(result)
368380
except Exception:
369-
# Avoid breaking the handler due to user hook errors
370381
pass
371382

372383
if log_traceback and effective_level <= logging.DEBUG:

api_exception/response_utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
from typing import Dict, Tuple, TypeGuard, Union
1+
from typing import Dict, Tuple, Union
2+
3+
# --- Backward-compatible TypeGuard import (Python 3.8–3.9 support) ---
4+
try:
5+
from typing import TypeGuard # Python 3.10+
6+
except ImportError:
7+
from typing_extensions import TypeGuard # For Python <3.10
8+
9+
210
from api_exception.response_model import ResponseModel
311
from api_exception.enums import ExceptionStatus, BaseExceptionCode
412
from api_exception.rfc7807_model import RFC7807ResponseModel

docs/changelog.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ All notable changes to APIException will be documented here.
44
This project uses *Semantic Versioning*.
55

66

7+
---
8+
9+
## [0.2.1] - 2025-10-16
10+
### Added
11+
- **Async support** for `extra_log_fields`:
12+
You can now define your callback as an `async def` function and directly use
13+
`await request.body()` or other asynchronous operations inside it.
14+
Backward compatibility for sync functions is fully preserved.
15+
- **Python 3.9 compatibility restored**:
16+
Introduced conditional import of `TypeGuard` via `typing_extensions`
17+
to ensure full support for Python 3.9 environments.
18+
- **typing-extensions** added as a dependency for backward-compatible typing features.
19+
20+
### Changed
21+
- `response_utils.py` refactored for cleaner type-safety and full cross-version support.
22+
- Updated `pyproject.toml`:
23+
- Python version lowered to `>=3.9,<4.0`
24+
- Added `typing-extensions>=4.0.0`
25+
- Internal handlers now safely await async results from `extra_log_fields`,
26+
ensuring consistent behavior across all exception types.
27+
28+
### Fixed
29+
- Fixed `ImportError` on Python 3.9 caused by direct import of `TypeGuard` from `typing`.
30+
- Minor logging improvements and better exception trace readability.
31+
32+
---
33+
34+
735
## [0.2.0] - 2025-08-23
836
### Added
937
- **Advanced logging customizations** in `register_exception_handlers`:

examples/fastapi_usage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
app = FastAPI()
1818

1919

20-
def my_extra_fields(request: Request, exc: Optional[BaseException]) -> Dict[str, Any]:
21-
# Örn. özel header'ı maskeyle logla
20+
async def my_extra_fields(request: Request, exc: Optional[BaseException]) -> Dict[str, Any]:
21+
# Ex: Mask extra fields
2222
user_id = request.headers.get("x-user-id", "anonymous")
2323
return {
2424
"masked_user_id": f"user-{user_id[-2:]}",

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "apiexception"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
description = "Consistent JSON response formatting and exception & error handling for FastAPI applications"
55
authors = ["Ahmet Kutay URAL <ahmetkutayural@gmail.com>"]
66
readme = "README.md"
@@ -14,6 +14,7 @@ classifiers = [
1414
"Operating System :: OS Independent",
1515
"Programming Language :: Python",
1616
"Programming Language :: Python :: 3",
17+
"Programming Language :: Python :: 3.9",
1718
"Programming Language :: Python :: 3.10",
1819
"Programming Language :: Python :: 3.11",
1920
"Programming Language :: Python :: 3.12",
@@ -34,11 +35,12 @@ packages = [
3435
"Changelog" = "https://github.com/akutayural/APIException/blob/main/docs/changelog.md"
3536

3637
[tool.poetry.dependencies]
37-
python = ">=3.10"
38+
python = ">=3.9,<4.0"
3839
fastapi = ">=0.115.4"
3940
httpx = ">=0.27.0"
4041
pydantic = ">=2.0.0"
4142
click = ">=8.0.0"
43+
typing-extensions = ">=4.0.0"
4244

4345
[tool.poetry.group.dev.dependencies]
4446
mkdocs-awesome-pages-plugin = ">=2.10.1"
@@ -62,7 +64,7 @@ build-backend = "poetry.core.masonry.api"
6264
api_exception = ["py.typed"]
6365

6466
[tool.mypy]
65-
python_version = "3.10"
67+
python_version = "3.9"
6668
ignore_missing_imports = true
6769
strict_optional = true
6870
warn_unused_ignores = true

0 commit comments

Comments
 (0)