Skip to content

Commit 222f6c3

Browse files
authored
v2.1.0 (#7)
1 parent 0473268 commit 222f6c3

File tree

11 files changed

+82
-75
lines changed

11 files changed

+82
-75
lines changed

CHANGELOG.md

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
# Change Log
22

3-
## [1.0.0] - 2024-04-26
3+
## [2.1.0] - 2025-08-17
44

55
Changes:
66

7-
- First release on PyPI
7+
- Camelize would capitalize words starting with an underscore. It now leaves them unchanged.
88

9-
## [1.0.1] - 2024-04-30
9+
- Old: `"_special"` -> `"Special"`
10+
- Now: `"_special"` -> `"_special"`
1011

11-
Changes:
12+
- Improve test coverage
13+
- Update README
1214

13-
- Upgrade dependencies
15+
## [2.0.0] - 2025-07-16
1416

15-
## [1.0.2] - 2024-09-08
17+
Breaking changes:
18+
19+
- 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.
20+
- Drop support for python 3.8
1621

1722
Changes:
1823

19-
- Add docstrings
20-
- Improve Makefile
21-
- Improve README
24+
- Improve code modularity and readability
25+
- Split tests in unittest and integration tests
26+
- Improve test coverage
27+
- Update Makefile
28+
- Update README
2229

2330
## [1.0.3] - 2025-03-16
2431

@@ -28,17 +35,22 @@ Changes:
2835
- Improve tests
2936
- Update README
3037

31-
## [2.0.0] - 2025-07-11
38+
## [1.0.2] - 2024-09-08
39+
40+
Changes:
3241

33-
Breaking changes:
42+
- Add docstrings
43+
- Improve Makefile
44+
- Improve README
3445

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
46+
## [1.0.1] - 2024-04-30
3747

3848
Changes:
3949

40-
- Improve code modularity and readability
41-
- Split tests in unittest and integration tests
42-
- Improve test coverage
43-
- Update Makefile
44-
- Update README
50+
- Upgrade dependencies
51+
52+
## [1.0.0] - 2024-04-26
53+
54+
Changes:
55+
56+
- First release on PyPI

README.md

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,14 @@ API error messages will include the following keys:
7070

7171
```json
7272
{
73-
"title": "Error message.",
74-
"detail": [
75-
"error",
76-
...
77-
],
78-
"invalid_params": [
79-
{
80-
"name": "field_name",
81-
"reason": [
82-
"error",
83-
...
84-
]
85-
},
86-
...
87-
]
73+
"title": "Error message.",
74+
"detail": ["error"],
75+
"invalid_params": [
76+
{
77+
"name": "field_name",
78+
"reason": ["error"]
79+
}
80+
]
8881
}
8982
```
9083

@@ -99,12 +92,8 @@ API error messages will include the following keys:
9992
"invalid_params": [
10093
{
10194
"name": "field_name",
102-
"reason": [
103-
"error"
104-
// ...
105-
]
95+
"reason": ["error"]
10696
}
107-
// ...
10897
]
10998
}
11099
```
@@ -114,10 +103,7 @@ API error messages will include the following keys:
114103
```json
115104
{
116105
"title": "Error message.",
117-
"detail": [
118-
"error"
119-
// ...
120-
],
106+
"detail": ["error"],
121107
"invalid_params": null
122108
}
123109
```
@@ -157,12 +143,8 @@ If `CAMELIZE` is set to `True`:
157143
"invalidParams": [
158144
{
159145
"name": "fieldName",
160-
"reason": [
161-
"error"
162-
// ...
163-
]
146+
"reason": ["error"]
164147
}
165-
// ...
166148
]
167149
}
168150
```
@@ -226,6 +208,7 @@ make test
226208
Finally, run `tox` to ensure the changes work for every supported python version:
227209

228210
```
211+
pip install tox # only if necessary
229212
tox -v
230213
```
231214

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__ = "2.0.0"
4+
__version__ = "2.1.0"

drf_simple_api_errors/exception_handler.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import logging
2+
from typing import Optional
23

34
from rest_framework import exceptions
45
from rest_framework.response import Response
56
from rest_framework.views import set_rollback
67

78
from drf_simple_api_errors import formatter, handlers
8-
from drf_simple_api_errors.exceptions import ServerError
99
from drf_simple_api_errors.types import ExceptionHandlerContext
1010

1111
logger = logging.getLogger(__name__)
1212

1313

14-
def exception_handler(exc: Exception, context: ExceptionHandlerContext) -> Response:
14+
def exception_handler(
15+
exc: Exception, context: ExceptionHandlerContext
16+
) -> Optional[Response]:
1517
"""
1618
Custom exception handler for DRF.
1719
@@ -44,8 +46,7 @@ def exception_handler(exc: Exception, context: ExceptionHandlerContext) -> Respo
4446
# This is because it's not good practice to expose the details of
4547
# unhandled exceptions to the client.
4648
if not isinstance(exc, exceptions.APIException):
47-
logger.info("Server error (500) from unexpected exception.", exc_info=True)
48-
return ServerError
49+
return None
4950

5051
# Get the API response headers from the exception.
5152
headers = handlers.get_response_headers(exc)

drf_simple_api_errors/exceptions.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

drf_simple_api_errors/formatter.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,10 @@ def format_exc(exc: exceptions.APIException) -> APIErrorResponseDict:
101101
# Create the API error response based on the exception detail...
102102
if isinstance(exc_detail, dict):
103103
return _format_exc_detail_dict(data, exc_detail)
104-
elif isinstance(exc_detail, list):
105-
# If the exception detail is a list, we will return all the errors
106-
# in a single list.
107-
return _format_exc_detail_list(data, exc_detail)
108104
else:
109-
return data.to_dict()
105+
# If the exception detail in not a dict, it must be a list, and
106+
# we will return all the errors in a single list.
107+
return _format_exc_detail_list(data, exc_detail)
110108

111109

112110
def _format_exc_detail_dict(

drf_simple_api_errors/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def underscore_to_camel(match: re.Match) -> str:
2929
if len(group) == 3:
3030
return group[0] + group[2].upper()
3131
else:
32-
return group[1].upper()
32+
return group
3333

3434
if not api_settings.CAMELIZE:
3535
return s

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "drf-simple-api-errors"
3-
version = "2.0.0"
3+
version = "2.1.0"
44
description = "A library for Django Rest Framework returning consistent and easy-to-parse API error messages."
55
authors = ["Gian <[email protected]>"]
66
license = "MIT"

tests/test_exception_handler.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,12 @@ def test_drf_throttled(self, mocker, wait, expected_detail):
399399
"invalid_params": None,
400400
}
401401
assert render_response(response.data) == expected_response
402+
403+
def test_unexpected_exception(self, mocker):
404+
"""
405+
Test the exception handler for unexpected exceptions.
406+
This should return a 500 Internal Server Error response.
407+
"""
408+
exc = Exception("Unexpected error")
409+
response = exception_handler(exc, mocker.Mock())
410+
assert response is None

tests/test_formatter.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,21 +207,32 @@ def test_exc_detail_is_dict_with_non_field_errors_formats(self, mocker, exc_deta
207207
assert data["invalid_params"] is None
208208

209209
@pytest.mark.parametrize(
210-
"exc_detail",
210+
"exc_detail, expected_detail",
211211
[
212-
"This is a non-field error.",
213-
["This is a non-field error."],
212+
("This is a non-field error.", ["This is a non-field error."]),
213+
(["This is a non-field error."], ["This is a non-field error."]),
214+
(
215+
["This is a non-field error.", "Another error."],
216+
["This is a non-field error.", "Another error."],
217+
),
218+
(
219+
[
220+
"This is a non-field error.",
221+
["Another error.", "Yet another error."],
222+
],
223+
["This is a non-field error.", "Another error.", "Yet another error."],
224+
),
214225
],
215226
)
216-
def test_exc_detail_is_list_formats(self, exc_detail):
227+
def test_exc_detail_is_list_formats(self, exc_detail, expected_detail):
217228
"""
218229
Test that when the exception detail is a list or a string,
219230
the detail is set to the error messages list and invalid_params is `None`.
220231
"""
221232
exc = drf_exceptions.ValidationError(exc_detail)
222233

223234
data = formatter.format_exc(exc)
224-
assert data["detail"] == ["This is a non-field error."]
235+
assert data["detail"] == expected_detail
225236
assert data["invalid_params"] is None
226237

227238
def test_format_exc_detail_is_list_error_when_unexpected_type(self):

0 commit comments

Comments
 (0)