Skip to content

Commit b9dc68c

Browse files
author
Kevin Hellemun
committed
Merge branch 'release/0.13.0'
2 parents 3853b32 + acb9687 commit b9dc68c

26 files changed

+12375
-7121
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
autobranch:
2+
pattern: 'bunq/sdk_python#{number}-{title}'
3+
length: 100
4+
commit:
5+
message:
6+
patterns:
7+
- '([A-Za-z0-9 ]+)\. (\(bunq\/sdk_python#[0-9]+\))'
8+
specification:
9+
title:
10+
minimum-length:
11+
enabled: true
12+
length: 8
13+
body:
14+
minimum-length:
15+
enabled: true
16+
length: 8
17+
contains-url: true
18+
contains-issue-number: true
19+
template:
20+
differs-from-body: true
21+
pull-request:
22+
labels:
23+
additional: true
24+
required:
25+
- Can be merged

CHANGELOG.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
# Change Log
22

3+
## [0.13.0](https://github.com/bunq/sdk_python/tree/0.13.0) (2018-03-20)
4+
5+
[Full Changelog](https://github.com/bunq/sdk_python/compare/0.12.4...HEAD)
6+
7+
**Implemented enhancements:**
8+
9+
- Add zappr integration for better quality control  [\#62](https://github.com/bunq/sdk_python/issues/62)
10+
- Add more information to template [\#60](https://github.com/bunq/sdk_python/issues/60)
11+
- Add response id to error messages from failed requests  [\#59](https://github.com/bunq/sdk_python/issues/59)
12+
13+
**Fixed bugs:**
14+
15+
- Token request ideal is missing id attribute in response. [\#67](https://github.com/bunq/sdk_python/issues/67)
16+
- Field ID is missing from MasterCardAction [\#54](https://github.com/bunq/sdk_python/issues/54)
17+
- TokenQrRequestIdeal returns the wrong type [\#53](https://github.com/bunq/sdk_python/issues/53)
18+
19+
**Closed issues:**
20+
21+
- bunq update 7 [\#75](https://github.com/bunq/sdk_python/issues/75)
22+
23+
**Merged pull requests:**
24+
25+
- Bunq update 7 [\#76](https://github.com/bunq/sdk_python/pull/76) ([OGKevin](https://github.com/OGKevin))
26+
- Regenerate code for release [\#74](https://github.com/bunq/sdk_python/pull/74) ([OGKevin](https://github.com/OGKevin))
27+
- Regenerated code to add object types. \(bunq/sdk\_python\#53\) [\#70](https://github.com/bunq/sdk_python/pull/70) ([OGKevin](https://github.com/OGKevin))
28+
- Bunq/sdk python\#67 add missing token qr id field [\#69](https://github.com/bunq/sdk_python/pull/69) ([OGKevin](https://github.com/OGKevin))
29+
- Added missing id field to mastercard action. \(bunq/sdk\_python\#54\) [\#66](https://github.com/bunq/sdk_python/pull/66) ([OGKevin](https://github.com/OGKevin))
30+
- Feature/bunq/sdk python\#59 add response id to request error [\#64](https://github.com/bunq/sdk_python/pull/64) ([OGKevin](https://github.com/OGKevin))
31+
- Configure Zappr [\#63](https://github.com/bunq/sdk_python/pull/63) ([OGKevin](https://github.com/OGKevin))
32+
- \(bunq/sdk\_python\#60\) improve issue and pr template [\#61](https://github.com/bunq/sdk_python/pull/61) ([OGKevin](https://github.com/OGKevin))
33+
334
## [0.12.4](https://github.com/bunq/sdk_python/tree/0.12.4) (2017-12-21)
435
[Full Changelog](https://github.com/bunq/sdk_python/compare/0.12.3...0.12.4)
536

@@ -10,6 +41,7 @@
1041
- Return base class from createFromJsonString [\#49](https://github.com/bunq/sdk_python/issues/49)
1142
- CHANGELOG.md is empty [\#46](https://github.com/bunq/sdk_python/issues/46)
1243
- Improve decoder to recognise child object [\#42](https://github.com/bunq/sdk_python/issues/42)
44+
- Generated CHANGELOG.md :clap:. \(bunq/sdk\_python\#46\) [\#47](https://github.com/bunq/sdk_python/pull/47) ([OGKevin](https://github.com/OGKevin))
1345

1446
**Closed issues:**
1547

@@ -20,7 +52,6 @@
2052
- Feature/make sure headers are correctly cased bunq/sdk python\#51 [\#57](https://github.com/bunq/sdk_python/pull/57) ([OGKevin](https://github.com/OGKevin))
2153
- Feature/improve decoder bunq/sdk python\#42 [\#56](https://github.com/bunq/sdk_python/pull/56) ([OGKevin](https://github.com/OGKevin))
2254
- Renamed camelCase methods. \(bunq/sdk\_python\#45\) [\#48](https://github.com/bunq/sdk_python/pull/48) ([OGKevin](https://github.com/OGKevin))
23-
- Generated CHANGELOG.md :clap:. \(bunq/sdk\_python\#46\) [\#47](https://github.com/bunq/sdk_python/pull/47) ([OGKevin](https://github.com/OGKevin))
2455

2556
## [0.12.3](https://github.com/bunq/sdk_python/tree/0.12.3) (2017-11-15)
2657
[Full Changelog](https://github.com/bunq/sdk_python/compare/0.12.2...0.12.3)

bunq/sdk/client.py

Lines changed: 29 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,23 @@ 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 self._ERROR_COULD_NOT_DETERMINE_RESPONSE_ID_HEADER;
288+
262289
def put(self, uri_relative, request_bytes, custom_headers):
263290
"""
264291
:type uri_relative: str

bunq/sdk/context.py

Lines changed: 156 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import aenum
44
from Cryptodome.PublicKey import RSA
55

6-
from bunq.sdk.model import core
76
from bunq.sdk import security
7+
from bunq.sdk.exception import BunqException
88
from bunq.sdk.json import converter
9+
from bunq.sdk.model import core
10+
from bunq.sdk.model.device_server_internal import DeviceServerInternal
911
from bunq.sdk.model.generated import endpoint
1012

1113

@@ -119,13 +121,11 @@ def _register_device(self, device_description,
119121
:rtype: None
120122
"""
121123

122-
endpoint.DeviceServer.create(
123-
self,
124-
{
125-
endpoint.DeviceServer.FIELD_DESCRIPTION: device_description,
126-
endpoint.DeviceServer.FIELD_SECRET: self.api_key,
127-
endpoint.DeviceServer.FIELD_PERMITTED_IPS: permitted_ips,
128-
}
124+
DeviceServerInternal.create(
125+
device_description,
126+
self.api_key,
127+
permitted_ips,
128+
api_context=self
129129
)
130130

131131
def _initialize_session(self):
@@ -136,8 +136,9 @@ def _initialize_session(self):
136136
session_server = core.SessionServer.create(self).value
137137
token = session_server.token.token
138138
expiry_time = self._get_expiry_timestamp(session_server)
139+
user_id = session_server.get_referenced_object().id_
139140

140-
self._session_context = SessionContext(token, expiry_time)
141+
self._session_context = SessionContext(token, expiry_time, user_id)
141142

142143
@classmethod
143144
def _get_expiry_timestamp(cls, session_server):
@@ -377,16 +378,18 @@ class SessionContext(object):
377378
"""
378379
:type _token: str
379380
:type _expiry_time: datetime.datetime
381+
:type _user_id: int
380382
"""
381383

382-
def __init__(self, token, expiry_time):
384+
def __init__(self, token, expiry_time, user_id):
383385
"""
384386
:type token: str
385387
:type expiry_time: datetime.datetime
386388
"""
387389

388390
self._token = token
389391
self._expiry_time = expiry_time
392+
self._user_id = user_id
390393

391394
@property
392395
def token(self):
@@ -403,3 +406,146 @@ def expiry_time(self):
403406
"""
404407

405408
return self._expiry_time
409+
410+
@property
411+
def user_id(self):
412+
return self._user_id
413+
414+
415+
class UserContext(object):
416+
_ERROR_UNEXPECTED_USER_INSTANCE = '"{}" is unexpected user instance.'
417+
_ERROR_NO_ACTIVE_MONETARY_ACCOUNT_FOUND = \
418+
'No active monetary account found.'
419+
_STATUS_ACTIVE = 'ACTIVE'
420+
421+
def __init__(self, user_id):
422+
"""
423+
:type user_id: int
424+
"""
425+
426+
self._user_id = user_id
427+
self._user_person = None
428+
self._user_company = None
429+
self._primary_monetary_account = None
430+
431+
user_object = endpoint.User.list().value[0].get_referenced_object()
432+
self._set_user(user_object)
433+
434+
def _set_user(self, user):
435+
if isinstance(user, endpoint.UserPerson):
436+
self._user_person = user
437+
438+
elif isinstance(user, endpoint.UserCompany):
439+
self._user_company = user
440+
441+
else:
442+
raise BunqException(
443+
self._ERROR_UNEXPECTED_USER_INSTANCE.format(user.__class__))
444+
445+
def init_main_monetary_account(self):
446+
all_monetary_account = endpoint.MonetaryAccountBank.list().value
447+
448+
for account in all_monetary_account:
449+
if account.status == self._STATUS_ACTIVE:
450+
self._primary_monetary_account = account
451+
452+
return
453+
454+
raise BunqException(self._ERROR_NO_ACTIVE_MONETARY_ACCOUNT_FOUND)
455+
456+
@property
457+
def user_id(self):
458+
return self._user_id
459+
460+
def is_only_user_person_set(self):
461+
"""
462+
:rtype: bool
463+
"""
464+
465+
return self._user_person is not None and self._user_company is None
466+
467+
def is_only_user_company_set(self):
468+
"""
469+
:rtype: bool
470+
"""
471+
472+
return self._user_company is not None and self._user_person is None
473+
474+
def is_both_user_type_set(self):
475+
"""
476+
:rtype: bool
477+
"""
478+
479+
return self._user_company is not None and self._user_person is not None
480+
481+
@property
482+
def user_company(self):
483+
"""
484+
:rtype: endpoint.UserCompany
485+
"""
486+
487+
return self._user_company
488+
489+
@property
490+
def user_person(self):
491+
"""
492+
:rtype: endpoint.UserPerson
493+
"""
494+
495+
return self._user_person
496+
497+
@property
498+
def primary_monetary_account(self):
499+
"""
500+
:rtype: endpoint.MonetaryAccountBank
501+
"""
502+
503+
return self._primary_monetary_account
504+
505+
506+
class BunqContext(object):
507+
_ERROR_CLASS_SHOULD_NOT_BE_INITIALIZED = \
508+
'This class should not be instantiated'
509+
_ERROR_API_CONTEXT_HAS_NOT_BEEN_LOADED = \
510+
'ApiContext has not been loaded. Please load ApiContext in BunqContext'
511+
_ERROR_USER_CONTEXT_HAS_NOT_BEEN_LOADED = \
512+
'UserContext has not been loaded, please load this' \
513+
' by loading ApiContext.'
514+
515+
_api_context = None
516+
_user_context = None
517+
518+
def __init__(self):
519+
raise TypeError(self._ERROR_CLASS_SHOULD_NOT_BE_INITIALIZED)
520+
521+
@classmethod
522+
def load_api_context(cls, api_context):
523+
"""
524+
:type api_context: ApiContext
525+
"""
526+
527+
cls._api_context = api_context
528+
cls._user_context = UserContext(api_context.session_context.user_id)
529+
cls._user_context.init_main_monetary_account()
530+
531+
@classmethod
532+
def api_context(cls):
533+
"""
534+
:rtype: ApiContext
535+
"""
536+
537+
if cls._api_context is not None:
538+
return cls._api_context
539+
540+
raise BunqException(cls._ERROR_API_CONTEXT_HAS_NOT_BEEN_LOADED)
541+
542+
@classmethod
543+
def user_context(cls):
544+
"""
545+
:rtype: UserContext
546+
"""
547+
548+
if cls._user_context is not None:
549+
return cls._user_context
550+
551+
raise BunqException(cls._ERROR_USER_CONTEXT_HAS_NOT_BEEN_LOADED)

0 commit comments

Comments
 (0)