Skip to content

Commit c006891

Browse files
authored
Merge pull request #26 from bunq/feature/exception-handler
Feature/exception handler
2 parents 33f5d47 + 6194612 commit c006891

File tree

6 files changed

+194
-34
lines changed

6 files changed

+194
-34
lines changed

EXCEPTIONS.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
## Exceptions
2+
When you make a request via the SDK, there is a chance of request failing
3+
due to various reasons. When such a failure happens, an exception
4+
corresponding to the error occurred is raised.
5+
6+
7+
----
8+
#### Possible Exceptions
9+
* `BadRequestException` If the request returns with status code `400`
10+
* `UnauthorizedException` If the request returns with status code `401`
11+
* `ForbiddenException` If the request returns with status code `403`
12+
* `NotFoundException` If the request returns with status code `404`
13+
* `MethodNotAllowedException` If the request returns with status code `405`
14+
* `TooManyRequestsException` If the request returns with status code `429`
15+
* `PleaseContactBunqException` If the request returns with status code `500`.
16+
If you get this exception, please contact us preferably via the support chat in the bunq app.
17+
* `UnknownApiErrorException` If none of the above mentioned exceptions are raised,
18+
this exception will be raised instead.
19+
20+
For more information regarding these errors, please take a look on the documentation
21+
page here: https://doc.bunq.com/api/1/page/errors
22+
23+
---
24+
#### Base exception
25+
All the exceptions have the same base exception which looks like this:
26+
```python
27+
class ApiException(Exception):
28+
def __init__(self, message, response_code):
29+
pass
30+
31+
@property
32+
def message(self):
33+
"""
34+
:rtype: str
35+
"""
36+
37+
return self._message
38+
39+
@property
40+
def response_code(self):
41+
"""
42+
:rtype: int
43+
"""
44+
45+
return self._response_code
46+
```
47+
This means that each exception will have the response code and the error message
48+
related to the specific exception that has been raised.
49+
50+
---
51+
#### Exception handling
52+
Because we raise different exceptions for each error, you can catch an error
53+
if you expect it to be raised.
54+
55+
```python
56+
from bunq.sdk.exception import BadRequestException
57+
from bunq.sdk.context import ApiEnvironmentType, ApiContext
58+
59+
API_KEY = "Some invalid API key"
60+
DESCRIPTION = "This wil raise a BadRequestException"
61+
62+
try:
63+
# Make a call that might raise an exception
64+
ApiContext(ApiEnvironmentType.SANDBOX, API_KEY, DESCRIPTION)
65+
except BadRequestException as error:
66+
# Do something if exception is raised
67+
print(error.response_code)
68+
print(error.message) # or just print(error)
69+
```

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,7 @@ Please do not forget to set the `_API_KEY` constant in
183183

184184
Information regarding the test cases can be found in the [README.md](./tests/README.md)
185185
located in [test](/tests)
186+
187+
## Exceptions
188+
The SDK can raise multiple exceptions. For an overview of these exceptions please
189+
take a look at [EXCEPTIONS.md](./EXCEPTIONS.md)

bunq/sdk/client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from bunq.sdk import exception
88
from bunq.sdk import security
9+
from bunq.sdk.exception_factory import ExceptionFactory
910
from bunq.sdk.json import converter
1011

1112

@@ -200,7 +201,7 @@ def _assert_response_success(self, response):
200201
"""
201202

202203
if response.status_code != self._STATUS_CODE_OK:
203-
raise exception.ApiException(
204+
raise ExceptionFactory.create_exception_for_response(
204205
response.status_code,
205206
self._fetch_error_messages(response)
206207
)

bunq/sdk/exception.py

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,22 @@
11
class ApiException(Exception):
2-
"""
3-
:type _response_code: int
4-
"""
5-
6-
# Constants for formatting messages
7-
_FORMAT_RESPONSE_CODE_LINE = 'HTTP Response Code: {}'
8-
_GLUE_ERROR_MESSAGES = '\n'
9-
10-
def __init__(self, response_code, messages):
2+
def __init__(self, message, response_code):
113
"""
4+
:type message: str
125
:type response_code: int
13-
:type messages: list[str]
146
"""
157

16-
super(ApiException, self).__init__(
17-
self.generate_message_error(response_code, messages)
18-
)
8+
self._message = message
199
self._response_code = response_code
2010

21-
def generate_message_error(self, response_code, messages):
22-
"""
23-
:type response_code: int
24-
:type messages: list[str]
25-
26-
:rtype: str
27-
"""
28-
29-
line_response_code = self._FORMAT_RESPONSE_CODE_LINE \
30-
.format(response_code)
31-
32-
return self.glue_messages([line_response_code] + messages)
11+
super(ApiException, self).__init__(message)
3312

34-
def glue_messages(self, messages):
13+
@property
14+
def message(self):
3515
"""
36-
:type messages: list[str]
37-
3816
:rtype: str
3917
"""
4018

41-
return self._GLUE_ERROR_MESSAGES.join(messages)
19+
return self._message
4220

4321
@property
4422
def response_code(self):
@@ -52,3 +30,35 @@ def response_code(self):
5230
class BunqException(Exception):
5331
def __init__(self, message):
5432
super(BunqException, self).__init__(message)
33+
34+
35+
class UnknownApiErrorException(ApiException):
36+
pass
37+
38+
39+
class BadRequestException(ApiException):
40+
pass
41+
42+
43+
class UnauthorizedException(ApiException):
44+
pass
45+
46+
47+
class ForbiddenException(ApiException):
48+
pass
49+
50+
51+
class NotFoundException(ApiException):
52+
pass
53+
54+
55+
class MethodNotAllowedException(ApiException):
56+
pass
57+
58+
59+
class TooManyRequestsException(ApiException):
60+
pass
61+
62+
63+
class PleaseContactBunqException(ApiException):
64+
pass

bunq/sdk/exception_factory.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from bunq.sdk.exception import BadRequestException
2+
from bunq.sdk.exception import UnauthorizedException
3+
from bunq.sdk.exception import ForbiddenException
4+
from bunq.sdk.exception import NotFoundException
5+
from bunq.sdk.exception import MethodNotAllowedException
6+
from bunq.sdk.exception import TooManyRequestsException
7+
from bunq.sdk.exception import PleaseContactBunqException
8+
from bunq.sdk.exception import UnknownApiErrorException
9+
from bunq.sdk.exception import ApiException
10+
11+
12+
class ExceptionFactory:
13+
# Error response code constants
14+
_HTTP_RESPONSE_CODE_BAD_REQUEST = 400
15+
_HTTP_RESPONSE_CODE_UNAUTHORIZED = 401
16+
_HTTP_RESPONSE_CODE_FORBIDDEN = 403
17+
_HTTP_RESPONSE_CODE_NOT_FOUND = 404
18+
_HTTP_RESPONSE_CODE_METHOD_NOT_ALLOWED = 405
19+
_HTTP_RESPONSE_CODE_TOO_MANY_REQUESTS = 429
20+
_HTTP_RESPONSE_CODE_INTERNAL_SERVER_ERROR = 500
21+
22+
# Constants for formatting messages
23+
_FORMAT_RESPONSE_CODE_LINE = 'HTTP Response Code: {}'
24+
_GLUE_ERROR_MESSAGES = '\n'
25+
26+
@classmethod
27+
def create_exception_for_response(cls, response_code, messages):
28+
"""
29+
:type response_code: int
30+
:type messages: list[str]
31+
32+
:return: The exception according to the status code.
33+
:rtype: ApiException
34+
"""
35+
36+
error_message = cls._generate_message_error(response_code, messages)
37+
38+
if response_code == cls._HTTP_RESPONSE_CODE_BAD_REQUEST:
39+
return BadRequestException(error_message, response_code)
40+
if response_code == cls._HTTP_RESPONSE_CODE_UNAUTHORIZED:
41+
return UnauthorizedException(error_message, response_code)
42+
if response_code == cls._HTTP_RESPONSE_CODE_FORBIDDEN:
43+
return ForbiddenException(error_message, response_code)
44+
if response_code == cls._HTTP_RESPONSE_CODE_NOT_FOUND:
45+
return NotFoundException(error_message, response_code)
46+
if response_code == cls._HTTP_RESPONSE_CODE_METHOD_NOT_ALLOWED:
47+
return MethodNotAllowedException(error_message, response_code)
48+
if response_code == cls._HTTP_RESPONSE_CODE_TOO_MANY_REQUESTS:
49+
return TooManyRequestsException(error_message, response_code)
50+
if response_code == cls._HTTP_RESPONSE_CODE_INTERNAL_SERVER_ERROR:
51+
return PleaseContactBunqException(error_message, response_code)
52+
53+
return UnknownApiErrorException(error_message, response_code)
54+
55+
@classmethod
56+
def _generate_message_error(cls, response_code, messages):
57+
"""
58+
:type response_code: int
59+
:type messages: list[str]
60+
61+
:rtype: str
62+
"""
63+
64+
line_response_code = cls._FORMAT_RESPONSE_CODE_LINE \
65+
.format(response_code)
66+
67+
return cls._glue_messages([line_response_code] + messages)
68+
69+
@classmethod
70+
def _glue_messages(cls, messages):
71+
"""
72+
:type messages: list[str]
73+
74+
:rtype: str
75+
"""
76+
77+
return cls._GLUE_ERROR_MESSAGES.join(messages)

tests/bunq_test.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from tests.config import Config
55
from bunq.sdk.context import ApiContext
66
from bunq.sdk.context import ApiEnvironmentType
7-
from bunq.sdk.model.generated import endpoint
8-
from bunq.sdk.exception import ApiException
97

108

119
class BunqSdkTestCase(unittest.TestCase):
@@ -35,10 +33,11 @@ def _get_api_context(cls):
3533

3634
try:
3735
api_context = ApiContext.restore(filename_bunq_config_full)
38-
endpoint.User.list(api_context)
39-
except (ApiException, FileNotFoundError):
36+
except FileNotFoundError:
4037
api_context = ApiContext(ApiEnvironmentType.SANDBOX, cls._API_KEY,
4138
cls._DEVICE_DESCRIPTION, [])
39+
else:
40+
api_context.ensure_session_active()
4241

4342
api_context.save(filename_bunq_config_full)
4443

0 commit comments

Comments
 (0)