Skip to content

Commit e9adcc2

Browse files
authored
Merge branch 'develop' into #54-fix-missing-mastercard-action-id
2 parents 8625515 + f0cc909 commit e9adcc2

11 files changed

+153
-22
lines changed

.github/ISSUE_TEMPLATE.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@
77
## What happens:
88
1.
99

10-
## Logs
11-
- Logs
10+
## Traceback
11+
[//]: # (If there is a traceback please share it in a quote! You can do this by pasting the traceback text, highlighting it and pressing the quote button.)
12+
13+
## SDK version and environment
14+
- Tested on [0.12.4](https://github.com/bunq/sdk_python/releases/tag/0.12.4)
15+
- [ ] Sandbox
16+
- [ ] Production
17+
18+
## Response id
19+
[//]: # (If this error has something to do with a request that fails, please provide the response id of the request.)
20+
- Response id:
1221

1322
## Extra info:
14-
- Tested on
23+
[//]: # (Please provide any other relevant information here)

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[//]: # (Thanks for opening this pull request! Before you proceed please make sure that you have an issue that explains what this pull request will do.
2+
Make sure that all your commits link to this issue e.g. "My commit. \(bunq/sdk_python#<issue nr>\)".
3+
If this pull request is changing files that are located in "bunq/sdk/model/generated" then this pull request will be closed as these files must/can only be changed on bunq's side.)
4+
5+
## This PR closes/fixes the following issues:
6+
[//]: # (If for some reason your pull request does not require a test case you can just mark this box as checked and explain why it does not require a test case.)
7+
- Closes bunq/sdk_php#
8+
- [ ] Tested

.zappr.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,4 @@ pull-request:
2222
labels:
2323
additional: true
2424
required:
25-
- Reviewed by André
2625
- Can be merged

bunq/sdk/client.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ class ApiClient(object):
1515
:type _api_context: bunq.sdk.context.ApiContext
1616
"""
1717

18+
# Error constants
19+
_ERROR_COULD_NOT_DETERMINE_RESPONSE_ID_HEADER = ('The response header'
20+
'"X-Bunq-Client-Response-'
21+
'Id" or "x-bunq-client-'
22+
'response-id" could not '
23+
'be found.')
24+
1825
# Endpoints not requiring active session for the request to succeed.
1926
_URL_DEVICE_SERVER = 'device-server'
2027
_URI_INSTALLATION = 'installation'
@@ -39,6 +46,8 @@ class ApiClient(object):
3946
HEADER_GEOLOCATION = 'X-Bunq-Geolocation'
4047
HEADER_SIGNATURE = 'X-Bunq-Client-Signature'
4148
HEADER_AUTHENTICATION = 'X-Bunq-Client-Authentication'
49+
HEADER_RESPONSE_ID_UPPER_CASED = 'X-Bunq-Client-Response-Id'
50+
HEADER_RESPONSE_ID_LOWER_CASED = 'x-bunq-client-response-id'
4251

4352
# Default header values
4453
_USER_AGENT_BUNQ = 'bunq-sdk-python/0.12.4'
@@ -215,7 +224,8 @@ def _assert_response_success(self, response):
215224
if response.status_code != self._STATUS_CODE_OK:
216225
raise ExceptionFactory.create_exception_for_response(
217226
response.status_code,
218-
self._fetch_error_messages(response)
227+
self._fetch_all_error_message(response),
228+
self._fetch_response_id(response)
219229
)
220230

221231
@classmethod
@@ -228,7 +238,7 @@ def _create_bunq_response_raw(cls, response):
228238

229239
return BunqResponseRaw(response.content, response.headers)
230240

231-
def _fetch_error_messages(self, response):
241+
def _fetch_all_error_message(self, response):
232242
"""
233243
:type response: requests.Response
234244
@@ -259,6 +269,25 @@ def _fetch_error_descriptions(self, error_dict):
259269

260270
return error_descriptions
261271

272+
def _fetch_response_id(self, response):
273+
"""
274+
:type response: requests.Response
275+
276+
:rtype: str
277+
"""
278+
279+
headers = response.headers
280+
281+
if self.HEADER_RESPONSE_ID_UPPER_CASED in headers:
282+
return headers[self.HEADER_RESPONSE_ID_UPPER_CASED]
283+
284+
if self.HEADER_RESPONSE_ID_LOWER_CASED in headers:
285+
return headers[self.HEADER_RESPONSE_ID_LOWER_CASED]
286+
287+
return exception.BunqException(
288+
self._ERROR_COULD_NOT_DETERMINE_RESPONSE_ID_HEADER
289+
)
290+
262291
def put(self, uri_relative, request_bytes, custom_headers):
263292
"""
264293
:type uri_relative: str

bunq/sdk/exception.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
class ApiException(Exception):
2-
def __init__(self, message, response_code):
2+
def __init__(self, message, response_code, response_id):
33
"""
4+
:type response_id: str
45
:type message: str
56
:type response_code: int
67
"""
78

9+
self._response_id = response_id
810
self._message = message
911
self._response_code = response_code
1012

@@ -26,6 +28,14 @@ def response_code(self):
2628

2729
return self._response_code
2830

31+
@property
32+
def response_id(self):
33+
"""
34+
:rtype: str
35+
"""
36+
37+
return self._response_id
38+
2939

3040
class BunqException(Exception):
3141
def __init__(self, message):

bunq/sdk/exception_factory.py

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,57 +21,109 @@ class ExceptionFactory:
2121

2222
# Constants for formatting messages
2323
_FORMAT_RESPONSE_CODE_LINE = 'HTTP Response Code: {}'
24-
_GLUE_ERROR_MESSAGES = '\n'
24+
_FORMAT_RESPONSE_ID_LINE = 'The response id to help bunq debug: {}'
25+
_FORMAT_ERROR_MESSAGE_LINE = 'Error message: {}'
26+
_GLUE_ERROR_MESSAGE_NEW_LINE = '\n'
27+
_GLUE_ERROR_MESSAGE_STRING_EMPTY = ''
2528

2629
@classmethod
27-
def create_exception_for_response(cls, response_code, messages):
30+
def create_exception_for_response(
31+
cls,
32+
response_code,
33+
messages,
34+
response_id
35+
):
2836
"""
2937
:type response_code: int
3038
:type messages: list[str]
39+
:type response_id: str
3140
3241
:return: The exception according to the status code.
3342
:rtype: ApiException
3443
"""
3544

36-
error_message = cls._generate_message_error(response_code, messages)
45+
error_message = cls._generate_message_error(
46+
response_code,
47+
messages,
48+
response_id
49+
)
3750

3851
if response_code == cls._HTTP_RESPONSE_CODE_BAD_REQUEST:
39-
return BadRequestException(error_message, response_code)
52+
return BadRequestException(
53+
error_message,
54+
response_code,
55+
response_id
56+
)
4057
if response_code == cls._HTTP_RESPONSE_CODE_UNAUTHORIZED:
41-
return UnauthorizedException(error_message, response_code)
58+
return UnauthorizedException(
59+
error_message,
60+
response_code,
61+
response_id
62+
)
4263
if response_code == cls._HTTP_RESPONSE_CODE_FORBIDDEN:
43-
return ForbiddenException(error_message, response_code)
64+
return ForbiddenException(
65+
error_message,
66+
response_code,
67+
response_id
68+
)
4469
if response_code == cls._HTTP_RESPONSE_CODE_NOT_FOUND:
45-
return NotFoundException(error_message, response_code)
70+
return NotFoundException(
71+
error_message,
72+
response_code,
73+
response_id
74+
)
4675
if response_code == cls._HTTP_RESPONSE_CODE_METHOD_NOT_ALLOWED:
47-
return MethodNotAllowedException(error_message, response_code)
76+
return MethodNotAllowedException(
77+
error_message,
78+
response_code,
79+
response_id
80+
)
4881
if response_code == cls._HTTP_RESPONSE_CODE_TOO_MANY_REQUESTS:
49-
return TooManyRequestsException(error_message, response_code)
82+
return TooManyRequestsException(
83+
error_message,
84+
response_code,
85+
response_id
86+
)
5087
if response_code == cls._HTTP_RESPONSE_CODE_INTERNAL_SERVER_ERROR:
51-
return PleaseContactBunqException(error_message, response_code)
88+
return PleaseContactBunqException(
89+
error_message,
90+
response_code,
91+
response_id
92+
)
5293

53-
return UnknownApiErrorException(error_message, response_code)
94+
return UnknownApiErrorException(
95+
error_message,
96+
response_code,
97+
response_id
98+
)
5499

55100
@classmethod
56-
def _generate_message_error(cls, response_code, messages):
101+
def _generate_message_error(cls, response_code, messages, response_id):
57102
"""
58103
:type response_code: int
59104
:type messages: list[str]
105+
:type response_id: str
60106
61107
:rtype: str
62108
"""
63109

64110
line_response_code = cls._FORMAT_RESPONSE_CODE_LINE \
65111
.format(response_code)
112+
line_response_id = cls._FORMAT_RESPONSE_ID_LINE.format(response_id)
113+
line_error_message = cls._FORMAT_ERROR_MESSAGE_LINE.format(
114+
cls._GLUE_ERROR_MESSAGE_STRING_EMPTY.join(messages)
115+
)
66116

67-
return cls._glue_messages([line_response_code] + messages)
117+
return cls._glue_all_error_message(
118+
[line_response_code, line_response_id, line_error_message]
119+
)
68120

69121
@classmethod
70-
def _glue_messages(cls, messages):
122+
def _glue_all_error_message(cls, messages):
71123
"""
72124
:type messages: list[str]
73125
74126
:rtype: str
75127
"""
76128

77-
return cls._GLUE_ERROR_MESSAGES.join(messages)
129+
return cls._GLUE_ERROR_MESSAGE_NEW_LINE.join(messages)

tests/http/__init__.py

Whitespace-only changes.
File renamed without changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from bunq.sdk.exception import ApiException
2+
from bunq.sdk.model.generated.endpoint import UserPerson
3+
from tests.bunq_test import BunqSdkTestCase
4+
5+
6+
class TestPagination(BunqSdkTestCase):
7+
"""
8+
Tests if the response id from a failed request can be retrieved
9+
successfully.
10+
"""
11+
12+
_INVALID_USER_PERSON_ID = 0
13+
14+
def test_bad_request_with_response_id(self):
15+
"""
16+
"""
17+
18+
with self.assertRaises(ApiException) as caught_exception:
19+
UserPerson.get(
20+
self._get_api_context(),
21+
self._INVALID_USER_PERSON_ID
22+
)
23+
24+
self.assertIsNotNone(caught_exception.exception.response_id)
File renamed without changes.

0 commit comments

Comments
 (0)