Skip to content

Commit 842eda2

Browse files
committed
chore: release v0.2.3
1 parent 32d2a35 commit 842eda2

File tree

6 files changed

+177
-47
lines changed

6 files changed

+177
-47
lines changed

README.md

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
[![Ruff](https://img.shields.io/badge/linting-ruff-%23ea580c?logo=ruff&logoColor=white)](https://github.com/astral-sh/ruff)
1414
[![uv](https://img.shields.io/badge/packaging-uv-%234285F4?logo=python&logoColor=white)](https://github.com/astral-sh/uv)
1515
[![Poetry](https://img.shields.io/badge/dependencies-poetry-%2360A5FA?logo=poetry&logoColor=white)](https://python-poetry.org/)
16+
[![PyPI status](https://img.shields.io/pypi/status/apiexception?style=flat)](https://pypi.org/project/apiexception/)
1617

1718

1819
**APIException** is a robust, production-ready Python library for FastAPI that simplifies exception handling and ensures consistent, well-structured API responses. Designed for developers who want to eliminate boilerplate error handling and improve Swagger/OpenAPI documentation, APIException makes your FastAPI projects cleaner and easier to maintain.
@@ -34,20 +35,13 @@ Reading the [full documentation](https://akutayural.github.io/APIException/) is
3435
---
3536

3637
> [!IMPORTANT]
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>
38+
> **Actively maintained. Highly recommended.**
4239
>
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>
40+
> APIException is actively maintained and used in production FastAPI services.
41+
> It is designed to be integrated as a library, not copied piece by piece into projects.
42+
>
43+
> If you find yourself copy-pasting error handling logic across services,
44+
> this library exists to replace that pattern with something cleaner and more sustainable.
5145
5246
---
5347

@@ -63,21 +57,71 @@ pip install apiexception
6357

6458
## ⚡ Quickstart: How to Integrate APIException
6559

66-
**1️⃣ Register the Handler**
60+
**1️⃣ Plug and Play**
6761

6862
```python
69-
from api_exception import register_exception_handlers, logger
7063
from fastapi import FastAPI
64+
from pydantic import BaseModel, Field
65+
from api_exception import (
66+
register_exception_handlers,
67+
APIException,
68+
BaseExceptionCode,
69+
ResponseModel,
70+
APIResponse,
71+
)
7172

7273
app = FastAPI()
7374
register_exception_handlers(app) # uses ResponseModel by default
7475

75-
logger.setLevel("INFO") # Set logging level if needed
76+
77+
class CustomExceptionCode(BaseExceptionCode):
78+
USER_NOT_FOUND = ("USR-404", "User not found.", "The user ID does not exist.")
79+
80+
81+
class UserModel(BaseModel):
82+
id: int = Field(..., example=1)
83+
username: str = Field(..., example="John Doe")
84+
85+
86+
@app.get(
87+
"/user/{user_id}",
88+
response_model=ResponseModel[UserModel],
89+
responses=APIResponse.default(),
90+
)
91+
async def user(user_id: int):
92+
if user_id == 1:
93+
raise APIException(
94+
error_code=CustomExceptionCode.USER_NOT_FOUND,
95+
http_status_code=404,
96+
)
97+
98+
if user_id == 3:
99+
a = 1
100+
b = 0
101+
c = a / b # This will raise ZeroDivisionError and be caught by the global exception handler
102+
return c
103+
104+
return ResponseModel[UserModel](
105+
data=UserModel(id=user_id, username="John Doe"),
106+
description="User retrieved successfully.",
107+
)
108+
76109
```
77110

111+
112+
![_user_{user_id}.gif](/assets/apiexception-indexBasicUsage-1.gif)
113+
114+
![apiexception-indexApiExceptionLog.png](docs/advanced/exception_1.png)
115+
116+
117+
**Enjoying APIException?**
118+
If this library saved you time or helped standardise your FastAPI responses,
119+
consider giving it a ⭐ on GitHub. It really helps the project grow.
120+
121+
78122
---
79123

80-
## 🔍 Example: Error Handling with Custom Codes
124+
## 🔍 Example: More Detailed Error Handling Example with Custom Codes
81125

82126
```python
83127
from typing import List, Optional, Any, Dict
@@ -173,6 +217,14 @@ your endpoints will display **clean, predictable response schemas** like this be
173217

174218
![_user_{user_id}.gif](/assets/apiexception-indexBasicUsage-1.gif)
175219

220+
![apiexception-indexApiExceptionLog.png](docs/advanced/exception_1.png)
221+
222+
223+
224+
For more examples, check it out in the [examples directory](https://github.com/akutayural/APIException/tree/main/examples).
225+
226+
227+
176228
---
177229

178230
#### - Successful API Response?
@@ -477,14 +529,22 @@ https://akutayural.github.io/APIException/
477529
🐍 **PyPI**
478530
https://pypi.org/project/apiexception/
479531

480-
💻 **Author Website**
481-
https://ahmetkutayural.dev
532+
💻 **Author **
533+
Ahmet Kutay Ural
534+
535+
536+
---
537+
**Used in real-world FastAPI projects**
538+
APIException is actively used in production systems and shared across the Python community.
539+
If you're using it in your project, a ⭐ on GitHub helps signal support and keeps development active.
482540

483541
---
484542

485543
## 🌍 Community & Recognition
486544

487545
- 📢 Featured in [**Python Weekly #710**](https://www.pythonweekly.com/p/python-weekly-issue-710-august-14-2025-3200567a10d37d87) 🎉
488-
- 🔥 Ranked **#3** globally in [r/FastAPI](https://www.reddit.com/r/FastAPI/comments/1ma39rq/make_your_fastapi_responses_clean_consistent/) under the *pip package* flair.
546+
- 🏆 Selected as one of the **Top Python Libraries of 2025** by [**Tryolabs**](https://tryolabs.com/blog/top-python-libraries-2025)
547+
- 🐍 Shared by [**PythonHub**](https://x.com/PythonHub/status/1957733129000472650) on X (Twitter)
548+
- 🐦 Shared by [@tom_doerr](https://x.com/tom_doerr/status/1996308358916116964) on X (Twitter)
489549
- ⭐ Gaining traction on GitHub with developers adopting it for real-world FastAPI projects.
490550
- 💬 Actively discussed and shared across the Python community.

api_exception/__init__.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
HeaderKeys = Tuple[str, ...]
5353

5454
ExtraLogFields = Callable[
55-
[Callable[[Request, Optional[BaseException]], Dict[str, Any]]],
56-
Callable[[Request, Optional[BaseException]], Awaitable[Dict[str, Any]]]
55+
[Request, Optional[BaseException]],
56+
Union[Dict[str, Any], Awaitable[Dict[str, Any]]]
5757
]
5858

5959

@@ -389,7 +389,7 @@ async def api_exception_handler(request: Request, exc: APIException):
389389
pass
390390

391391
if log_traceback and effective_level <= logging.DEBUG:
392-
tb = traceback.format_exc()
392+
tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
393393
log_with_meta(logging.ERROR, f"APIException: {exc.message}", meta)
394394
logger.error(f"Traceback:\n{tb}")
395395

@@ -445,7 +445,7 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
445445
msg = "Validation error"
446446

447447
if log:
448-
tb = traceback.format_exc()
448+
tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
449449
meta: Dict[str, Any] = {
450450
"event": "validation_error",
451451
"path": request.url.path,
@@ -459,7 +459,11 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
459459
meta.update(_collect_headers(request, log_header_keys))
460460
if extra_log_fields:
461461
try:
462-
meta.update(extra_log_fields(request, exc))
462+
result = extra_log_fields(request, exc)
463+
if inspect.isawaitable(result):
464+
result = await result
465+
if isinstance(result, dict):
466+
meta.update(result)
463467
except Exception:
464468
pass
465469

@@ -529,7 +533,7 @@ async def fallback_exception_middleware(request: Request, call_next: Callable):
529533
try:
530534
return await call_next(request)
531535
except Exception as e:
532-
tb = traceback.format_exc()
536+
tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
533537

534538
if log:
535539
meta: Dict[str, Any] = {
@@ -545,7 +549,11 @@ async def fallback_exception_middleware(request: Request, call_next: Callable):
545549
meta.update(_collect_headers(request, log_header_keys))
546550
if extra_log_fields:
547551
try:
548-
meta.update(extra_log_fields(request, e))
552+
result = extra_log_fields(request, e)
553+
if inspect.isawaitable(result):
554+
result = await result
555+
if isinstance(result, dict):
556+
meta.update(result)
549557
except Exception:
550558
pass
551559

docs/changelog.md

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

66

7+
## [0.2.3] - 2026-01-21
8+
### Improved
9+
- Internal exception handling flow reviewed and stabilised for long-term maintenance.
10+
- Logging paths and async handling verified across all supported Python versions.
11+
- Minor internal refactors to improve readability and future extensibility without changing public APIs.
12+
- Confirmed backward compatibility with all existing `register_exception_handlers` usage patterns.
13+
14+
### Documentation
15+
- README refined with clearer onboarding examples and improved Quickstart section.
16+
- Community & recognition section expanded with external mentions and ecosystem visibility.
17+
- Changelog and docs aligned to reflect current stable usage patterns and production readiness.
18+
19+
### Maintenance
20+
- This release intentionally introduces **no breaking changes**.
21+
- Focused on stability, clarity, and long-term maintainability.
22+
- Actively used in production environments; no migration required for existing users.
23+
724
---
825

926
## [0.2.2] - 2026-01-11
@@ -21,6 +38,7 @@ This project uses *Semantic Versioning*.
2138
- PyPI (`uv add apiexception`)
2239
- GitHub repository (`uv add git+https://github.com/akutayural/APIException.git`)
2340

41+
---
2442

2543
## [0.2.1] - 2025-10-16
2644
### Added
@@ -84,6 +102,7 @@ This project uses *Semantic Versioning*.
84102
- Added missing `import traceback` in `__init__.py`.
85103
This resolves a `NameError` when using `register_exception_handlers` with traceback logging enabled.
86104

105+
---
87106

88107
## [0.1.20] - 2025-08-18
89108

@@ -95,6 +114,7 @@ This project uses *Semantic Versioning*.
95114
- Resolved IDE red import warnings when using the library in external projects.
96115
- Improved top-level import resolution and consistency across modules.
97116

117+
---
98118

99119
## [0.1.19] - 2025-08-18
100120

@@ -108,10 +128,10 @@ This project uses *Semantic Versioning*.
108128
#### Fixed
109129
- Example and README imports updated to use new unified style.
110130

131+
---
111132

112133
## [0.1.18] - 2025-08-17
113134

114-
115135
### Added
116136
- Global logging control (`set_global_log`) with `log` param in `register_exception_handlers`.
117137
- RFC7807 full support with `application/problem+json` responses.
@@ -126,6 +146,8 @@ This project uses *Semantic Versioning*.
126146
- Fallback middleware now returns HTTP 500 instead of 422 for unexpected errors.
127147
- Traceback scope bug fixed in handlers.
128148

149+
---
150+
129151
## [v0.1.17] - 2025-08-11
130152

131153
- `RFC 7807` standard support for consistent error responses (`application/problem+json`)
@@ -152,6 +174,7 @@ This project uses *Semantic Versioning*.
152174

153175
- Readme.md has been updated.
154176

177+
---
155178

156179
## [v0.1.16] - 2025-07-22
157180

@@ -161,6 +184,7 @@ This project uses *Semantic Versioning*.
161184

162185
- Readme.md has been updated.
163186

187+
---
164188

165189
## [v0.1.15] - 2025-07-22
166190

@@ -172,12 +196,15 @@ This project uses *Semantic Versioning*.
172196

173197
- Readme.md has been updated.
174198

199+
---
200+
175201
## [v0.1.14] - 2025-07-22
176202

177203
- setup.py has been updated.
178204

179205
- Project name has been updated. Instead of `APIException` we will use `apiexception` to comply with `PEP 625`.
180206

207+
---
181208

182209
## [v0.1.13] - 2025-07-21
183210

@@ -191,7 +218,7 @@ This project uses *Semantic Versioning*.
191218

192219
- Readme has been updated. New gifs have been added.
193220

194-
221+
---
195222

196223
## [0.1.12] - 2025-07-14
197224

@@ -201,6 +228,7 @@ This project uses *Semantic Versioning*.
201228

202229
- `__all__` includes more methods.
203230

231+
---
204232

205233
## [0.1.11] - 2025-07-13
206234

@@ -210,6 +238,7 @@ This project uses *Semantic Versioning*.
210238

211239
- Production-ready logging for all exceptions
212240

241+
---
213242

214243
## [0.1.0] - 2025-06-25
215244

0 commit comments

Comments
 (0)