1
1
import uuid
2
-
3
- # Due to compatibility requirements, we are importing a class here.
4
- try :
5
- from json import JSONDecodeError
6
- except ImportError :
7
- from simplejson import JSONDecodeError
2
+ from json import JSONDecodeError
3
+ from urllib .parse import urlencode
8
4
9
5
import requests
10
6
11
- from bunq .sdk .json import converter
12
- from bunq .sdk import security
13
7
from bunq .sdk import context
14
8
from bunq .sdk import exception
9
+ from bunq .sdk import security
10
+ from bunq .sdk .json import converter
15
11
16
12
17
13
class ApiClient (object ):
@@ -35,7 +31,7 @@ class ApiClient(object):
35
31
HEADER_AUTHENTICATION = 'X-Bunq-Client-Authentication'
36
32
37
33
# Default header values
38
- _USER_AGENT_BUNQ = 'bunq-sdk-python/0.10 .0'
34
+ _USER_AGENT_BUNQ = 'bunq-sdk-python/0.11 .0'
39
35
_GEOLOCATION_ZERO = '0 0 0 0 NL'
40
36
_LANGUAGE_EN_US = 'en_US'
41
37
_REGION_NL_NL = 'nl_NL'
@@ -47,6 +43,9 @@ class ApiClient(object):
47
43
_METHOD_GET = 'GET'
48
44
_METHOD_DELETE = 'DELETE'
49
45
46
+ # Delimiter between path and params in URL
47
+ _DELIMITER_URL_QUERY = '?'
48
+
50
49
# Status code for successful execution
51
50
_STATUS_CODE_OK = 200
52
51
@@ -76,30 +75,35 @@ def post(self, uri_relative, request_bytes, custom_headers):
76
75
self ._METHOD_POST ,
77
76
uri_relative ,
78
77
request_bytes ,
78
+ {},
79
79
custom_headers
80
80
)
81
81
82
- def _request (self , method , uri_relative , request_bytes , custom_headers ):
82
+ def _request (self , method , uri_relative , request_bytes , params ,
83
+ custom_headers ):
83
84
"""
84
85
:type method: str
85
86
:type uri_relative: str
86
87
:type request_bytes: bytes
88
+ :type params: dict[str, str]
87
89
:type custom_headers: dict[str, str]
88
90
89
91
:return: BunqResponseRaw
90
92
"""
91
93
94
+ uri_relative_with_params = self ._append_params_to_uri (uri_relative ,
95
+ params )
92
96
self ._api_context .ensure_session_active ()
93
97
all_headers = self ._get_all_headers (
94
98
method ,
95
- uri_relative ,
99
+ uri_relative_with_params ,
96
100
request_bytes ,
97
101
custom_headers
98
102
)
99
103
100
104
response = requests .request (
101
105
method ,
102
- self ._get_uri_full (uri_relative ),
106
+ self ._get_uri_full (uri_relative_with_params ),
103
107
data = request_bytes ,
104
108
headers = all_headers ,
105
109
proxies = {self ._FIELD_PROXY_HTTPS : self ._api_context .proxy_url }
@@ -117,6 +121,20 @@ def _request(self, method, uri_relative, request_bytes, custom_headers):
117
121
118
122
return self ._create_bunq_response_raw (response )
119
123
124
+ @classmethod
125
+ def _append_params_to_uri (cls , uri , params ):
126
+ """
127
+ :type uri: str
128
+ :type params: dict[str, str]
129
+
130
+ :rtype: str
131
+ """
132
+
133
+ if params :
134
+ return uri + cls ._DELIMITER_URL_QUERY + urlencode (params )
135
+
136
+ return uri
137
+
120
138
def _get_all_headers (self , method , endpoint , request_bytes , custom_headers ):
121
139
"""
122
140
:type method: str
@@ -242,12 +260,14 @@ def put(self, uri_relative, request_bytes, custom_headers):
242
260
self ._METHOD_PUT ,
243
261
uri_relative ,
244
262
request_bytes ,
263
+ {},
245
264
custom_headers
246
265
)
247
266
248
- def get (self , uri_relative , custom_headers ):
267
+ def get (self , uri_relative , params , custom_headers ):
249
268
"""
250
269
:type uri_relative: str
270
+ :type params: dict[str, str]
251
271
:type custom_headers: dict[str, str]
252
272
253
273
:rtype: BunqResponseRaw
@@ -257,6 +277,7 @@ def get(self, uri_relative, custom_headers):
257
277
self ._METHOD_GET ,
258
278
uri_relative ,
259
279
self ._BYTES_EMPTY ,
280
+ params ,
260
281
custom_headers
261
282
)
262
283
@@ -272,6 +293,7 @@ def delete(self, uri_relative, custom_headers):
272
293
self ._METHOD_DELETE ,
273
294
uri_relative ,
274
295
self ._BYTES_EMPTY ,
296
+ {},
275
297
custom_headers
276
298
)
277
299
@@ -312,16 +334,19 @@ class BunqResponse(object):
312
334
"""
313
335
:type _value: T
314
336
:type _headers: dict[str, str]
337
+ :type _pagination: Pagination|None
315
338
"""
316
339
317
- def __init__ (self , value , headers ):
340
+ def __init__ (self , value , headers , pagination = None ):
318
341
"""
319
342
:type value: T
320
343
:type headers: dict[str, str]
344
+ :type pagination Pagination|None
321
345
"""
322
346
323
347
self ._value = value
324
348
self ._headers = headers
349
+ self ._pagination = pagination
325
350
326
351
@property
327
352
def value (self ):
@@ -338,3 +363,125 @@ def headers(self):
338
363
"""
339
364
340
365
return self ._headers
366
+
367
+ @property
368
+ def pagination (self ):
369
+ """
370
+ :rtype: Pagination
371
+ """
372
+
373
+ return self ._pagination
374
+
375
+
376
+ class Pagination (object ):
377
+ """
378
+ :type older_id: int|None
379
+ :type newer_id: int|None
380
+ :type future_id: int|None
381
+ :type count: int|None
382
+ """
383
+
384
+ # Error constants
385
+ _ERROR_NO_PREVIOUS_PAGE = 'Could not generate previous page URL params: ' \
386
+ 'there is no previous page.'
387
+ _ERROR_NO_NEXT_PAGE = 'Could not generate next page URL params: ' \
388
+ 'there is no next page.'
389
+
390
+ # URL Param constants
391
+ PARAM_OLDER_ID = 'older_id'
392
+ PARAM_NEWER_ID = 'newer_id'
393
+ PARAM_COUNT = 'count'
394
+
395
+ def __init__ (self ):
396
+ self .older_id = None
397
+ self .newer_id = None
398
+ self .future_id = None
399
+ self .count = None
400
+
401
+ @property
402
+ def url_params_previous_page (self ):
403
+ """
404
+ :rtype: dict[str, str]
405
+ """
406
+
407
+ self .assert_has_previous_page ()
408
+
409
+ params = {self .PARAM_OLDER_ID : str (self .older_id )}
410
+ self ._add_count_to_params_if_needed (params )
411
+
412
+ return params
413
+
414
+ def assert_has_previous_page (self ):
415
+ """
416
+ :raise: exception.BunqException
417
+ """
418
+
419
+ if not self .has_previous_page ():
420
+ raise exception .BunqException (self ._ERROR_NO_PREVIOUS_PAGE )
421
+
422
+ def has_previous_page (self ):
423
+ """
424
+ :rtype: bool
425
+ """
426
+
427
+ return self .older_id is not None
428
+
429
+ @property
430
+ def url_params_count_only (self ):
431
+ """
432
+ :rtype: dict[str, str]
433
+ """
434
+
435
+ params = {}
436
+ self ._add_count_to_params_if_needed (params )
437
+
438
+ return params
439
+
440
+ def _add_count_to_params_if_needed (self , params ):
441
+ """
442
+ :type params: dict[str, str]
443
+
444
+ :rtype: None
445
+ """
446
+
447
+ if self .count is not None :
448
+ params [self .PARAM_COUNT ] = str (self .count )
449
+
450
+ def has_next_page_assured (self ):
451
+ """
452
+ :rtype: bool
453
+ """
454
+
455
+ return self .newer_id is not None
456
+
457
+ @property
458
+ def url_params_next_page (self ):
459
+ """
460
+ :rtype: dict[str, str]
461
+ """
462
+
463
+ self .assert_has_next_page ()
464
+
465
+ params = {self .PARAM_NEWER_ID : str (self ._next_id )}
466
+ self ._add_count_to_params_if_needed (params )
467
+
468
+ return params
469
+
470
+ def assert_has_next_page (self ):
471
+ """
472
+ :raise: exception.BunqException
473
+ """
474
+
475
+ if self ._next_id is None :
476
+ raise exception .BunqException (self ._ERROR_NO_NEXT_PAGE )
477
+
478
+ @property
479
+ def _next_id (self ):
480
+ """
481
+ :rtype: int
482
+ """
483
+
484
+ if self .has_next_page_assured ():
485
+ return self .newer_id
486
+
487
+ return self .future_id
0 commit comments