Skip to content

Commit 0473268

Browse files
authored
Release v2.0.0 (#6)
1 parent 6aad2f0 commit 0473268

34 files changed

+1540
-535
lines changed

.github/workflows/build.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ["3.8", "3.9", "3.10"]
14+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1515

1616
steps:
1717
- name: Checkout source code
@@ -28,7 +28,7 @@ jobs:
2828
python -m pip install tox tox-gh-actions
2929
3030
- name: Run tox
31-
run: tox
31+
run: tox -v
3232

3333
- name: Upload coverage reports to Codecov
3434
uses: codecov/[email protected]

CHANGELOG.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,43 @@
22

33
## [1.0.0] - 2024-04-26
44

5+
Changes:
6+
57
- First release on PyPI
68

79
## [1.0.1] - 2024-04-30
810

11+
Changes:
12+
913
- Upgrade dependencies
1014

1115
## [1.0.2] - 2024-09-08
1216

13-
- Add docstrings to handlers
17+
Changes:
18+
19+
- Add docstrings
1420
- Improve Makefile
1521
- Improve README
1622

1723
## [1.0.3] - 2025-03-16
1824

19-
- Update README
20-
- Improve tests
25+
Changes:
26+
2127
- Fix DRF API settings initialization
28+
- Improve tests
29+
- Update README
30+
31+
## [2.0.0] - 2025-07-11
32+
33+
Breaking changes:
34+
35+
- The API error response now **always** includes the keys: `title`, `detail`, and `invalid_param`. The `title` key is always populated, while `detail` and `invalid_param` may be `null` depending on the error source.
36+
- Drop support for python 3.8
37+
38+
Changes:
39+
40+
- Improve code modularity and readability
41+
- Split tests in unittest and integration tests
42+
- Improve test coverage
43+
- Update Makefile
44+
- Update README

Makefile

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,21 @@
44
install:
55
@poetry install --with dev -v
66

7-
# Run poetry shell
8-
shell:
9-
@poetry shell
10-
117
# Run formatters (black, isort) with poetry
128
format:
13-
@poetry run isort drf_simple_api_errors test_project
14-
@poetry run black drf_simple_api_errors test_project
9+
@poetry run isort drf_simple_api_errors integration_tests tests
10+
@poetry run black drf_simple_api_errors integration_tests tests
1511

1612
# Check format (black, isort) and linting (flake8)
1713
lint:
18-
@poetry run isort --check drf_simple_api_errors test_project
19-
@poetry run black --check drf_simple_api_errors test_project --exclude migrations
20-
@poetry run flake8 drf_simple_api_errors test_project --max-line-length 88
14+
@poetry run isort --check drf_simple_api_errors integration_tests tests
15+
@poetry run black --check drf_simple_api_errors integration_tests tests --exclude migrations
16+
@poetry run flake8 drf_simple_api_errors integration_tests tests --max-line-length 88
2117

2218
# Run unittests with poetry
2319
test:
24-
@poetry run pytest test_project
20+
@poetry run pytest
2521

2622
# Run code coverage tests coverage with poetry
2723
test/cov:
28-
@poetry run pytest test_project --cov=drf_simple_api_errors --cov-report xml:coverage.xml
24+
@poetry run pytest --cov=drf_simple_api_errors --cov-report xml:coverage.xml

README.md

Lines changed: 48 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99

1010
A library for [Django Rest Framework](https://www.django-rest-framework.org/) returning **consistent, predictable and easy-to-parse API error messages**.
1111

12-
This library was built with [RFC7807](https://tools.ietf.org/html/rfc7807) guidelines in mind, but with a small twist: it defines a "problem detail" as a list, but it still serves as a way to include errors in a predictable and easy-to-parse format for any API consumer. Error messages are formatted using RFC7807 keywords and DRF exception data.
12+
This library was built with [RFC7807](https://tools.ietf.org/html/rfc7807) guidelines in mind, but with a small twist: it defines a "problem detail" as a list instead of a string, but it still serves as a way to include errors in a human-readable and easy-to-parse format for any API consumer.
13+
Error messages are formatted using RFC7807 keywords and DRF exception data.
14+
15+
Unlike standard DRF, where the error response format varies depending on the error source, this library always returns errors in a consistent and predictable structure.
1316

1417
## What's different?
1518

1619
Compared to other similar and popular libraries, this library:
1720

1821
- Is based on RFC7807 guidelines
1922
- Aims to provide not only a standardized format for error details, but also human-readable error messages (perfect for both internal and public APIs)
20-
- Transforms both `django.core.exceptions.ValidationError` and `rest_framework.errors.ValidationError` to API errors, so you don't have to handle error raised by services/domain logic, `clean()`, or other functions/methods
23+
- Transforms both `django.core.exceptions.ValidationError` and `rest_framework.errors.ValidationError` to API errors, so you don't have to handle error raised by services/domain logic, `clean()`, etc.
2124

2225
## Table of Contents
2326

@@ -57,13 +60,13 @@ REST_FRAMEWORK = {
5760

5861
### Error structure overview
5962

60-
API error messages typically include the following keys:
63+
API error messages will include the following keys:
6164

62-
- `"title"` (`str`): A brief summary that describes the problem type
63-
- `"detail"` (`list[str] | None`): A list of specific explanations related to the problem
64-
- `"invalid_params"` (`list[dict] | None`): A list of dict containing details about parameters that were invalid or malformed in the request. Each dict within this list provides:
65-
- `"name"` (`str`): The name of the parameter that was found to be invalid
66-
- `"reasons"` (`list[str]`): A list of strings describing the specific reasons why the parameter was considered invalid or malformed
65+
- `"title"` (`str`): A brief summary that describes the problem type.
66+
- `"detail"` (`list[str] | None`): A list of specific explanations related to the problem, if any.
67+
- `"invalid_params"` (`list[dict] | None`): A list of dict containing details about parameters that were invalid or malformed in the request, if any. Each dict within this list provides:
68+
- `"name"` (`str`): The name of the parameter that was found to be invalid.
69+
- `"reasons"` (`list[str]`): A list of strings describing the specific reasons why the parameter was considered invalid or malformed.
6770

6871
```json
6972
{
@@ -91,17 +94,18 @@ API error messages typically include the following keys:
9194

9295
```json
9396
{
94-
"title": "Error message.",
95-
"invalid_params": [
96-
{
97-
"name": "field_name",
98-
"reason": [
99-
"error",
100-
...
101-
]
102-
},
103-
...
104-
]
97+
"title": "Error message.",
98+
"details": null,
99+
"invalid_params": [
100+
{
101+
"name": "field_name",
102+
"reason": [
103+
"error"
104+
// ...
105+
]
106+
}
107+
// ...
108+
]
105109
}
106110
```
107111

@@ -111,23 +115,26 @@ API error messages typically include the following keys:
111115
{
112116
"title": "Error message.",
113117
"detail": [
114-
"error",
115-
...
116-
]
118+
"error"
119+
// ...
120+
],
121+
"invalid_params": null
117122
}
118123
```
119124

120125
#### Other bad requests with no detail
121126

122127
```json
123128
{
124-
"title": "Error message."
129+
"title": "Error message.",
130+
"detail": null,
131+
"invalid_params": null
125132
}
126133
```
127134

128135
## Settings
129136

130-
Default available settings:
137+
Default settings:
131138

132139
```python
133140
DRF_SIMPLE_API_ERRORS = {
@@ -146,15 +153,16 @@ If `CAMELIZE` is set to `True`:
146153
```json
147154
{
148155
"title": "Error message.",
156+
"details": null,
149157
"invalidParams": [
150158
{
151159
"name": "fieldName",
152160
"reason": [
153-
"error",
154-
...
161+
"error"
162+
// ...
155163
]
156164
}
157-
...
165+
// ...
158166
]
159167
}
160168
```
@@ -163,89 +171,23 @@ If `CAMELIZE` is set to `True`:
163171

164172
Support for exceptions that differ from the standard structure of the Django Rest Framework.
165173

166-
For instance, you may want to specify you own exception:
167-
168-
```python
169-
class AuthenticationFailed(exceptions.AuthenticationFailed):
170-
def __init__(self, detail=None, code=None):
171-
"""
172-
Builds a detail dictionary for the error to give more information
173-
to API users.
174-
"""
175-
detail_dict = {"detail": self.default_detail, "code": self.default_code}
176-
177-
if isinstance(detail, dict):
178-
detail_dict.update(detail)
179-
elif detail is not None:
180-
detail_dict["detail"] = detail
181-
182-
if code is not None:
183-
detail_dict["code"] = code
184-
185-
super().__init__(detail_dict)
186-
```
187-
188-
Use exception in code:
189-
190-
```python
191-
def my_func():
192-
raise AuthenticationFailed(
193-
{
194-
"detail": _("Error message."),
195-
"messages": [
196-
{
197-
"metadata": "metadata_data",
198-
"type": "type_name",
199-
"message": "error message",
200-
}
201-
],
202-
}
203-
)
204-
```
205-
206-
This will result in:
174+
For example, if you need to customize how a specific exception is handled or want to format an existing exception differently, you can create your own handler.
207175

208-
```python
209-
AuthenticationFailed(
210-
{
211-
"detail": "Error message.",
212-
"messages": [
213-
{
214-
"metadata": "metadata_data",
215-
"type": "type_name",
216-
"message": "error message",
217-
}
218-
],
219-
}
220-
)
221-
```
222-
223-
You can handle this by creating a `handlers.py` file and specifying an handler for your use case:
224-
225-
```python
226-
def handle_exc_custom_authentication_failed(exc):
227-
from path.to.my.exceptions import AuthenticationFailed
228-
229-
if isinstance(exc, AuthenticationFailed):
230-
try:
231-
exc.detail = exc.detail["messages"][0]["message"]
232-
except (KeyError, IndexError):
233-
exc.detail = exc.detail["detail"]
234-
235-
return exc
236-
```
176+
To customize error handling for your project, simply create a new file (for example, `extra_handlers.py`) and define your own handler functions. This approach lets you tailor error responses to fit your specific needs.
237177

238178
Then add it to the `EXTRA_HANDLERS` list in this package settings:
239179

240180
```python
241181
DRF_SIMPLE_API_ERRORS = {
242182
"EXTRA_HANDLERS": [
243-
"path.to.my.handlers.handle_exc_custom_authentication_failed",
183+
"path.to.my.extra_handlers.custom_handler",
244184
# ...
245185
]
246186
}
247187
```
248188

189+
For reference, this library uses the same pattern for its own extra handlers [here](drf_simple_api_errors/extra_handlers.py).
190+
249191
- #### FIELDS_SEPARATOR
250192

251193
Support for nested dicts containing multiple fields to be flattened.
@@ -274,13 +216,19 @@ All the necessary commands are included in the `Makefile`.
274216

275217
We are using `tox` and `poetry` to run tests in every supported Python version.
276218

277-
Run test with the commands below:
219+
Run test during development with the commands below:
278220

279221
```
280-
make install
222+
make install # only if necessary
281223
make test
282224
```
283225

226+
Finally, run `tox` to ensure the changes work for every supported python version:
227+
228+
```
229+
tox -v
230+
```
231+
284232
## Support
285233

286234
Please [open an issue](https://github.com/gripep/drf-simple-api-errors/issues/new).

drf_simple_api_errors/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .exception_handler import exception_handler
22

33
__all__ = ("exception_handler",)
4-
__version__ = "1.0.3"
4+
__version__ = "2.0.0"

0 commit comments

Comments
 (0)