mangopay4-python-sdk 3.52.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mangopay4_python_sdk-3.52.0/DESCRIPTION.md +1 -0
- mangopay4_python_sdk-3.52.0/LICENSE +22 -0
- mangopay4_python_sdk-3.52.0/MANIFEST.in +8 -0
- mangopay4_python_sdk-3.52.0/Makefile +8 -0
- mangopay4_python_sdk-3.52.0/PKG-INFO +53 -0
- mangopay4_python_sdk-3.52.0/README.md +53 -0
- mangopay4_python_sdk-3.52.0/mangopay/__init__.py +27 -0
- mangopay4_python_sdk-3.52.0/mangopay/api.py +381 -0
- mangopay4_python_sdk-3.52.0/mangopay/auth.py +130 -0
- mangopay4_python_sdk-3.52.0/mangopay/base.py +289 -0
- mangopay4_python_sdk-3.52.0/mangopay/compat.py +16 -0
- mangopay4_python_sdk-3.52.0/mangopay/constants.py +466 -0
- mangopay4_python_sdk-3.52.0/mangopay/exceptions.py +26 -0
- mangopay4_python_sdk-3.52.0/mangopay/fields.py +1345 -0
- mangopay4_python_sdk-3.52.0/mangopay/page.py +17 -0
- mangopay4_python_sdk-3.52.0/mangopay/query.py +280 -0
- mangopay4_python_sdk-3.52.0/mangopay/ratelimit.py +5 -0
- mangopay4_python_sdk-3.52.0/mangopay/resources.py +3219 -0
- mangopay4_python_sdk-3.52.0/mangopay/signals.py +14 -0
- mangopay4_python_sdk-3.52.0/mangopay/utils.py +1582 -0
- mangopay4_python_sdk-3.52.0/mangopay4_python_sdk.egg-info/PKG-INFO +53 -0
- mangopay4_python_sdk-3.52.0/mangopay4_python_sdk.egg-info/SOURCES.txt +92 -0
- mangopay4_python_sdk-3.52.0/mangopay4_python_sdk.egg-info/dependency_links.txt +1 -0
- mangopay4_python_sdk-3.52.0/mangopay4_python_sdk.egg-info/entry_points.txt +2 -0
- mangopay4_python_sdk-3.52.0/mangopay4_python_sdk.egg-info/requires.txt +25 -0
- mangopay4_python_sdk-3.52.0/mangopay4_python_sdk.egg-info/top_level.txt +1 -0
- mangopay4_python_sdk-3.52.0/setup.cfg +7 -0
- mangopay4_python_sdk-3.52.0/setup.py +43 -0
- mangopay4_python_sdk-3.52.0/tests/__init__.py +6 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/card.json +16 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/cardregistrations.json +16 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/cardregistrations_update.json +14 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/declarative_user.json +26 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/legal_user.json +28 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/legal_user_wallet.json +14 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/legal_user_wallet_89.json +14 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/legal_user_wallet_99.json +14 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/list_ubo_declarations.json +12 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/natural_user.json +25 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/natural_user_wallet.json +14 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/natural_user_wallet_9.json +14 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/ubo.json +20 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/ubo_declaration.json +10 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/ubo_declaration_submit.json +10 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/ubo_update.json +20 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/user_list_2_per_page.json +18 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/user_list_2_per_page_page1.json +18 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/user_list_3_per_page_page2.json +26 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/user_list_full.json +92 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/user_list_page1.json +82 -0
- mangopay4_python_sdk-3.52.0/tests/fixtures/user_wallet.json +14 -0
- mangopay4_python_sdk-3.52.0/tests/images/image.jpg +0 -0
- mangopay4_python_sdk-3.52.0/tests/mocks.py +304 -0
- mangopay4_python_sdk-3.52.0/tests/resources/TestKycPageFile.png +0 -0
- mangopay4_python_sdk-3.52.0/tests/resources/settlement_sample.csv +8 -0
- mangopay4_python_sdk-3.52.0/tests/resources.py +9 -0
- mangopay4_python_sdk-3.52.0/tests/settings.py +15 -0
- mangopay4_python_sdk-3.52.0/tests/test_bankaccounts.py +501 -0
- mangopay4_python_sdk-3.52.0/tests/test_banking_aliases.py +93 -0
- mangopay4_python_sdk-3.52.0/tests/test_base.py +827 -0
- mangopay4_python_sdk-3.52.0/tests/test_cards.py +365 -0
- mangopay4_python_sdk-3.52.0/tests/test_client_wallets.py +200 -0
- mangopay4_python_sdk-3.52.0/tests/test_clients.py +64 -0
- mangopay4_python_sdk-3.52.0/tests/test_conversion.py +204 -0
- mangopay4_python_sdk-3.52.0/tests/test_country_authorizations.py +57 -0
- mangopay4_python_sdk-3.52.0/tests/test_deposits.py +69 -0
- mangopay4_python_sdk-3.52.0/tests/test_disputes.py +381 -0
- mangopay4_python_sdk-3.52.0/tests/test_documentconsult.py +176 -0
- mangopay4_python_sdk-3.52.0/tests/test_emoney.py +26 -0
- mangopay4_python_sdk-3.52.0/tests/test_idempotency.py +57 -0
- mangopay4_python_sdk-3.52.0/tests/test_identity_verification.py +44 -0
- mangopay4_python_sdk-3.52.0/tests/test_kyc_document.py +37 -0
- mangopay4_python_sdk-3.52.0/tests/test_kycs.py +312 -0
- mangopay4_python_sdk-3.52.0/tests/test_mandates.py +97 -0
- mangopay4_python_sdk-3.52.0/tests/test_notifications.py +277 -0
- mangopay4_python_sdk-3.52.0/tests/test_payins.py +2470 -0
- mangopay4_python_sdk-3.52.0/tests/test_payouts.py +208 -0
- mangopay4_python_sdk-3.52.0/tests/test_preauthorizations.py +850 -0
- mangopay4_python_sdk-3.52.0/tests/test_rate_limit.py +14 -0
- mangopay4_python_sdk-3.52.0/tests/test_recipients.py +191 -0
- mangopay4_python_sdk-3.52.0/tests/test_refunds.py +422 -0
- mangopay4_python_sdk-3.52.0/tests/test_reports.py +117 -0
- mangopay4_python_sdk-3.52.0/tests/test_reports_v2.py +133 -0
- mangopay4_python_sdk-3.52.0/tests/test_repudiations.py +15 -0
- mangopay4_python_sdk-3.52.0/tests/test_settlements.py +38 -0
- mangopay4_python_sdk-3.52.0/tests/test_tokens.py +61 -0
- mangopay4_python_sdk-3.52.0/tests/test_transactions.py +216 -0
- mangopay4_python_sdk-3.52.0/tests/test_transfers.py +226 -0
- mangopay4_python_sdk-3.52.0/tests/test_ubos.py +102 -0
- mangopay4_python_sdk-3.52.0/tests/test_users.py +990 -0
- mangopay4_python_sdk-3.52.0/tests/test_utils.py +49 -0
- mangopay4_python_sdk-3.52.0/tests/test_virtual_account.py +52 -0
- mangopay4_python_sdk-3.52.0/tests/test_wallets.py +356 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This SDK is a client library for interacting with the Mangopay API.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 MANGOPAY
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mangopay4-python-sdk
|
|
3
|
+
Version: 3.52.0
|
|
4
|
+
Summary: A client library written in python to work with mangopay v2 api
|
|
5
|
+
Home-page: https://github.com/Mangopay/mangopay2-python-sdk
|
|
6
|
+
Author: Mangopay (www.mangopay.com)
|
|
7
|
+
Author-email: support@mangopay.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: mangopay api development emoney sdk
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: requests
|
|
19
|
+
Requires-Dist: simplejson
|
|
20
|
+
Requires-Dist: blinker
|
|
21
|
+
Requires-Dist: six
|
|
22
|
+
Requires-Dist: pytz
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: responses; extra == "dev"
|
|
25
|
+
Requires-Dist: nose; extra == "dev"
|
|
26
|
+
Requires-Dist: coverage; extra == "dev"
|
|
27
|
+
Requires-Dist: httplib2; extra == "dev"
|
|
28
|
+
Requires-Dist: pyopenssl; extra == "dev"
|
|
29
|
+
Requires-Dist: ndg-httpsclient; extra == "dev"
|
|
30
|
+
Requires-Dist: pyasn1; extra == "dev"
|
|
31
|
+
Requires-Dist: exam; extra == "dev"
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: responses; extra == "test"
|
|
34
|
+
Requires-Dist: nose; extra == "test"
|
|
35
|
+
Requires-Dist: coverage; extra == "test"
|
|
36
|
+
Requires-Dist: httplib2; extra == "test"
|
|
37
|
+
Requires-Dist: pyopenssl; extra == "test"
|
|
38
|
+
Requires-Dist: ndg-httpsclient; extra == "test"
|
|
39
|
+
Requires-Dist: pyasn1; extra == "test"
|
|
40
|
+
Requires-Dist: exam; extra == "test"
|
|
41
|
+
Dynamic: author
|
|
42
|
+
Dynamic: author-email
|
|
43
|
+
Dynamic: classifier
|
|
44
|
+
Dynamic: description
|
|
45
|
+
Dynamic: home-page
|
|
46
|
+
Dynamic: keywords
|
|
47
|
+
Dynamic: license
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
Dynamic: provides-extra
|
|
50
|
+
Dynamic: requires-dist
|
|
51
|
+
Dynamic: summary
|
|
52
|
+
|
|
53
|
+
This SDK is a client library for interacting with the Mangopay API.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Mangopay Python SDK
|
|
2
|
+
=================================================
|
|
3
|
+
|
|
4
|
+
MangopaySDK is a Python client library to work with
|
|
5
|
+
[Mangopay REST API](http://docs.mangopay.com/api-references/).
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Installation
|
|
9
|
+
------------
|
|
10
|
+
|
|
11
|
+
mangopaysdk requires:
|
|
12
|
+
|
|
13
|
+
* [requests](https://pypi.python.org/pypi/requests)
|
|
14
|
+
* [blinker](https://pypi.python.org/pypi/blinker)
|
|
15
|
+
|
|
16
|
+
To install the package:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
pip install mangopay4-python-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
By installing this package with [pip](https://pypi.python.org/pypi/pip), all dependencies will be installed for you.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Documentation
|
|
26
|
+
-------------
|
|
27
|
+
|
|
28
|
+
For documentation and examples of usage for the Mangopay API please refer to: [API references](https://docs.mangopay.com/api-references/)
|
|
29
|
+
|
|
30
|
+
See [docs](https://github.com/Mangopay/mangopay2-python-sdk/blob/master/docs/usage.rst) to find examples.
|
|
31
|
+
|
|
32
|
+
License
|
|
33
|
+
-------------------------------------------------
|
|
34
|
+
MangopaySDK is distributed under MIT license, see LICENSE file.
|
|
35
|
+
|
|
36
|
+
Contact
|
|
37
|
+
-------------------------------------------------
|
|
38
|
+
Report bugs or suggest features using [issue tracker at GitHub](https://github.com/Mangopay/mangopay2-python-sdk/issues).
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
Account creation
|
|
42
|
+
-------------------------------------------------
|
|
43
|
+
You can get yourself a free sandbox account or sign up for a production account by [registering on the Mangopay site](https://www.mangopay.com/start/) (note that validation of your production account involves several steps, so think about doing it in advance of when you actually want to go live).
|
|
44
|
+
|
|
45
|
+
Inspiration
|
|
46
|
+
-----------
|
|
47
|
+
|
|
48
|
+
The data model of python-mangopay is highly inspired by [peewee](https://github.com/coleifer/peewee).
|
|
49
|
+
|
|
50
|
+
Credits
|
|
51
|
+
-----------
|
|
52
|
+
|
|
53
|
+
The inital basis of this SDK was generously coded and donated by [Ulule](https://www.ulule.com/)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
client_id = None
|
|
2
|
+
apikey = None
|
|
3
|
+
api_url = 'https://api.mangopay.com/v2.01/'
|
|
4
|
+
api_sandbox_url = 'https://api.sandbox.mangopay.com/v2.01/'
|
|
5
|
+
temp_dir = None
|
|
6
|
+
api_version = 2.01
|
|
7
|
+
sandbox = True
|
|
8
|
+
uk_header_flag = False
|
|
9
|
+
|
|
10
|
+
package_version = None
|
|
11
|
+
try:
|
|
12
|
+
with open('./setup.py', 'r') as f:
|
|
13
|
+
for line in f:
|
|
14
|
+
if line.startswith(' version'):
|
|
15
|
+
package_version = line.split('=')[1].replace("'", "").replace(",", "").replace("\n", "")
|
|
16
|
+
except:
|
|
17
|
+
None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from .api import APIRequest # noqa
|
|
21
|
+
from .utils import memoize
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_default_handler():
|
|
25
|
+
return APIRequest()
|
|
26
|
+
|
|
27
|
+
get_default_handler = memoize(_get_default_handler, {}, 0)
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
from __future__ import unicode_literals
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import platform
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
import six
|
|
11
|
+
from requests.exceptions import ConnectionError, Timeout
|
|
12
|
+
|
|
13
|
+
import mangopay
|
|
14
|
+
from mangopay.auth import AuthorizationTokenManager
|
|
15
|
+
from mangopay.ratelimit import RateLimit
|
|
16
|
+
from .exceptions import APIError, DecodeError
|
|
17
|
+
from .signals import request_finished, request_started, request_error
|
|
18
|
+
from .utils import reraise_as
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import urllib.parse as urlrequest
|
|
22
|
+
except ImportError:
|
|
23
|
+
import urllib as urlrequest
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import simplejson as json
|
|
27
|
+
except ImportError:
|
|
28
|
+
import json
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger('mangopay')
|
|
32
|
+
|
|
33
|
+
requests_session = requests.Session()
|
|
34
|
+
rate_limits = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class APIRequest(object):
|
|
38
|
+
|
|
39
|
+
def __init__(self, client_id=None, apikey=None, api_url=None, api_sandbox_url=None, sandbox=None,
|
|
40
|
+
timeout=30.0, storage_strategy=None, proxies=None, uk_header_flag=False):
|
|
41
|
+
global rate_limits
|
|
42
|
+
rate_limits = None
|
|
43
|
+
if (sandbox is None and mangopay.sandbox) or sandbox:
|
|
44
|
+
self.api_url = api_sandbox_url or mangopay.api_sandbox_url
|
|
45
|
+
else:
|
|
46
|
+
self.api_url = api_url or mangopay.api_url
|
|
47
|
+
|
|
48
|
+
self.client_id = client_id or mangopay.client_id
|
|
49
|
+
self.apikey = apikey or mangopay.apikey
|
|
50
|
+
self.auth_manager = AuthorizationTokenManager(self, storage_strategy)
|
|
51
|
+
self.timeout = timeout
|
|
52
|
+
self.proxies = proxies
|
|
53
|
+
self.uk_header_flag = uk_header_flag or mangopay.uk_header_flag
|
|
54
|
+
|
|
55
|
+
def set_rate_limit(self, rate_limit):
|
|
56
|
+
global rate_limits
|
|
57
|
+
rate_limits = rate_limit
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_rate_limits(self):
|
|
61
|
+
return rate_limits
|
|
62
|
+
|
|
63
|
+
def request(self, method, url, data=None, idempotency_key=None, oauth_request=False, without_client_id=False,
|
|
64
|
+
is_v3=False, **params):
|
|
65
|
+
return self.custom_request(method, url, data, idempotency_key, oauth_request, True,
|
|
66
|
+
without_client_id, is_v3, **params)
|
|
67
|
+
|
|
68
|
+
def multipart_request(self, method, url, file, file_name, idempotency_key=None, oauth_request=False, is_v3=False):
|
|
69
|
+
return self.custom_multipart_request(method, url, file, file_name, idempotency_key, oauth_request, is_v3)
|
|
70
|
+
|
|
71
|
+
def custom_request(self, method, url, data=None, idempotency_key=None, oauth_request=False,
|
|
72
|
+
is_mangopay_request=False, without_client_id=False, is_v3=False, **params):
|
|
73
|
+
params = params or {}
|
|
74
|
+
|
|
75
|
+
headers = {}
|
|
76
|
+
|
|
77
|
+
if is_mangopay_request:
|
|
78
|
+
headers['User-Agent'] = 'Mangopay-SDK/' + str(
|
|
79
|
+
mangopay.package_version) + ' (Python/' + platform.python_version() + ')'
|
|
80
|
+
if oauth_request:
|
|
81
|
+
headers['Authorization'] = self.auth_manager.basic_token()
|
|
82
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
83
|
+
else:
|
|
84
|
+
headers['Authorization'] = self.auth_manager.get_token()
|
|
85
|
+
headers['Content-Type'] = 'application/json'
|
|
86
|
+
|
|
87
|
+
if idempotency_key:
|
|
88
|
+
headers['Idempotency-Key'] = idempotency_key
|
|
89
|
+
|
|
90
|
+
if self.uk_header_flag:
|
|
91
|
+
logger.warning('uk_header_flag is deprecated and will be removed in future versions')
|
|
92
|
+
headers['x-tenant-id'] = 'uk'
|
|
93
|
+
else:
|
|
94
|
+
if "data_XXX" in params:
|
|
95
|
+
params[str("data")] = params[str("data_XXX")]
|
|
96
|
+
params.__delitem__(str("data_XXX"))
|
|
97
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
98
|
+
|
|
99
|
+
# truncated_data = None
|
|
100
|
+
|
|
101
|
+
encoded_params = urlrequest.urlencode(params)
|
|
102
|
+
|
|
103
|
+
if is_mangopay_request:
|
|
104
|
+
if oauth_request or without_client_id:
|
|
105
|
+
url = self.api_url + url
|
|
106
|
+
else:
|
|
107
|
+
url = self._absolute_url(url, encoded_params)
|
|
108
|
+
else:
|
|
109
|
+
url = '%s?%s' % (url, encoded_params)
|
|
110
|
+
|
|
111
|
+
if is_v3:
|
|
112
|
+
url = url.replace('v2.01', 'V3.0')
|
|
113
|
+
|
|
114
|
+
if data or data == {}:
|
|
115
|
+
# truncated_data = truncatechars(copy.copy(data))
|
|
116
|
+
|
|
117
|
+
data = json.dumps(data, default=lambda x: x.to_api_json())
|
|
118
|
+
|
|
119
|
+
logger.debug('DATA[IN -> %s]\n\t- headers: %s\n\t- content: %s', url, headers, data)
|
|
120
|
+
|
|
121
|
+
ts = time.time()
|
|
122
|
+
|
|
123
|
+
# signal:
|
|
124
|
+
request_started.send(url=url, data=data, headers=headers, method=method)
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
result = requests_session.request(method, url,
|
|
128
|
+
data=data,
|
|
129
|
+
headers=headers,
|
|
130
|
+
timeout=self.timeout,
|
|
131
|
+
proxies=self.proxies)
|
|
132
|
+
except ConnectionError as e:
|
|
133
|
+
msg = '{}'.format(e)
|
|
134
|
+
|
|
135
|
+
if msg:
|
|
136
|
+
msg = '%s: %s' % (type(e).__name__, msg)
|
|
137
|
+
else:
|
|
138
|
+
msg = type(e).__name__
|
|
139
|
+
|
|
140
|
+
reraise_as(APIError(msg))
|
|
141
|
+
|
|
142
|
+
except Timeout as e:
|
|
143
|
+
msg = '{}'.format(e)
|
|
144
|
+
|
|
145
|
+
if msg:
|
|
146
|
+
msg = '%s: %s' % (type(e).__name__, msg)
|
|
147
|
+
else:
|
|
148
|
+
msg = type(e).__name__
|
|
149
|
+
|
|
150
|
+
reraise_as(APIError(msg))
|
|
151
|
+
laps = time.time() - ts
|
|
152
|
+
|
|
153
|
+
# signal:
|
|
154
|
+
request_finished.send(url=url,
|
|
155
|
+
data=data,
|
|
156
|
+
headers=headers,
|
|
157
|
+
method=method,
|
|
158
|
+
result=result,
|
|
159
|
+
laps=laps)
|
|
160
|
+
|
|
161
|
+
logger.debug('DATA[OUT -> %s][%2.3f seconds]\n\t- status_code: %s\n\t- headers: %s\n\t- content: %s',
|
|
162
|
+
url,
|
|
163
|
+
laps,
|
|
164
|
+
result.status_code,
|
|
165
|
+
result.headers,
|
|
166
|
+
result.text if hasattr(result, 'text') else result.content
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
self.read_response_headers(result.headers)
|
|
170
|
+
|
|
171
|
+
if result.status_code not in (requests.codes.ok, requests.codes.not_found,
|
|
172
|
+
requests.codes.created, requests.codes.accepted,
|
|
173
|
+
requests.codes.no_content):
|
|
174
|
+
self._create_apierror(result, url=url, data=data, method=method)
|
|
175
|
+
elif result.status_code == requests.codes.no_content or (result.status_code == requests.codes.ok and result.content == b''):
|
|
176
|
+
return result, None
|
|
177
|
+
else:
|
|
178
|
+
if result.content:
|
|
179
|
+
try:
|
|
180
|
+
content = result.content
|
|
181
|
+
|
|
182
|
+
if six.PY3:
|
|
183
|
+
content = content.decode('utf-8')
|
|
184
|
+
|
|
185
|
+
return result, json.loads(content)
|
|
186
|
+
except ValueError:
|
|
187
|
+
if result.content.startswith(b'data='):
|
|
188
|
+
return result.content
|
|
189
|
+
self._create_decodeerror(result, url=url)
|
|
190
|
+
else:
|
|
191
|
+
self._create_decodeerror(result, url=url)
|
|
192
|
+
|
|
193
|
+
def custom_multipart_request(self, method, url, file, file_name, idempotency_key=None, oauth_request=False, is_v3=False):
|
|
194
|
+
if not isinstance(file, bytes):
|
|
195
|
+
raise TypeError("The 'file' parameter must be of type 'bytes'.")
|
|
196
|
+
|
|
197
|
+
headers = {'User-Agent': 'Mangopay-SDK/' + str(
|
|
198
|
+
mangopay.package_version) + ' (Python/' + platform.python_version() + ')'}
|
|
199
|
+
|
|
200
|
+
if oauth_request:
|
|
201
|
+
headers['Authorization'] = self.auth_manager.basic_token()
|
|
202
|
+
else:
|
|
203
|
+
headers['Authorization'] = self.auth_manager.get_token()
|
|
204
|
+
|
|
205
|
+
if idempotency_key:
|
|
206
|
+
headers['Idempotency-Key'] = idempotency_key
|
|
207
|
+
|
|
208
|
+
if self.uk_header_flag:
|
|
209
|
+
logger.warning('uk_header_flag is deprecated and will be removed in future versions')
|
|
210
|
+
headers['x-tenant-id'] = 'uk'
|
|
211
|
+
|
|
212
|
+
if oauth_request:
|
|
213
|
+
url = self.api_url + url
|
|
214
|
+
else:
|
|
215
|
+
url = self._absolute_url(url, '')
|
|
216
|
+
|
|
217
|
+
if is_v3:
|
|
218
|
+
url = url.replace('v2.01', 'V3.0')
|
|
219
|
+
|
|
220
|
+
files = {'file': (file_name, file)}
|
|
221
|
+
|
|
222
|
+
logger.debug('DATA[IN -> %s]\n\t- headers: %s\n\t- file: %s', url, headers, 'provided' if file else 'none')
|
|
223
|
+
ts = time.time()
|
|
224
|
+
|
|
225
|
+
request_started.send(url=url, data=None, headers=headers, method=method)
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
result = requests_session.request(method, url,
|
|
229
|
+
files=files,
|
|
230
|
+
headers=headers,
|
|
231
|
+
timeout=self.timeout,
|
|
232
|
+
proxies=self.proxies)
|
|
233
|
+
except ConnectionError as e:
|
|
234
|
+
msg = f'{type(e).__name__}: {e}' if str(e) else type(e).__name__
|
|
235
|
+
reraise_as(APIError(msg))
|
|
236
|
+
|
|
237
|
+
except Timeout as e:
|
|
238
|
+
msg = f'{type(e).__name__}: {e}' if str(e) else type(e).__name__
|
|
239
|
+
reraise_as(APIError(msg))
|
|
240
|
+
|
|
241
|
+
laps = time.time() - ts
|
|
242
|
+
|
|
243
|
+
request_finished.send(url=url,
|
|
244
|
+
data=None,
|
|
245
|
+
headers=headers,
|
|
246
|
+
method=method,
|
|
247
|
+
result=result,
|
|
248
|
+
laps=laps)
|
|
249
|
+
|
|
250
|
+
logger.debug('DATA[OUT -> %s][%2.3f seconds]\n\t- status_code: %s\n\t- headers: %s\n\t- content: %s',
|
|
251
|
+
url,
|
|
252
|
+
laps,
|
|
253
|
+
result.status_code,
|
|
254
|
+
result.headers,
|
|
255
|
+
result.text if hasattr(result, 'text') else result.content
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
self.read_response_headers(result.headers)
|
|
259
|
+
|
|
260
|
+
if result.status_code not in (requests.codes.ok, requests.codes.not_found,
|
|
261
|
+
requests.codes.created, requests.codes.accepted,
|
|
262
|
+
requests.codes.no_content):
|
|
263
|
+
self._create_apierror(result, url=url, data=None, method=method)
|
|
264
|
+
elif result.status_code == requests.codes.no_content or (
|
|
265
|
+
result.status_code == requests.codes.ok and result.content == b''):
|
|
266
|
+
return result, None
|
|
267
|
+
else:
|
|
268
|
+
if result.content:
|
|
269
|
+
try:
|
|
270
|
+
content = result.content
|
|
271
|
+
if six.PY3:
|
|
272
|
+
content = content.decode('utf-8')
|
|
273
|
+
return result, json.loads(content)
|
|
274
|
+
except ValueError:
|
|
275
|
+
if result.content.startswith(b'data='):
|
|
276
|
+
return result.content
|
|
277
|
+
self._create_decodeerror(result, url=url)
|
|
278
|
+
else:
|
|
279
|
+
self._create_decodeerror(result, url=url)
|
|
280
|
+
|
|
281
|
+
def read_response_headers(self, headers):
|
|
282
|
+
rate_limit_reset = headers.get('x-ratelimit-reset')
|
|
283
|
+
rate_limit_remaining = headers.get('x-ratelimit-remaining')
|
|
284
|
+
rate_limit_made = headers.get('x-ratelimit')
|
|
285
|
+
|
|
286
|
+
if rate_limit_reset is not None and rate_limit_remaining is not None and rate_limit_made is not None:
|
|
287
|
+
rate_limit_reset = rate_limit_reset.split(',')
|
|
288
|
+
rate_limit_remaining = rate_limit_remaining.split(',')
|
|
289
|
+
rate_limit_made = rate_limit_made.split(',')
|
|
290
|
+
|
|
291
|
+
if len(rate_limit_reset) == len(rate_limit_remaining) and len(rate_limit_reset) == len(rate_limit_made):
|
|
292
|
+
current_time = int(time.time())
|
|
293
|
+
updated_rate_limits = []
|
|
294
|
+
|
|
295
|
+
for i, rlr in enumerate(rate_limit_reset):
|
|
296
|
+
rate_limit = RateLimit()
|
|
297
|
+
number_of_minutes = (int(rate_limit_reset[i].strip()) - current_time) / 60
|
|
298
|
+
|
|
299
|
+
if number_of_minutes <= 15:
|
|
300
|
+
rate_limit.interval_minutes = 15
|
|
301
|
+
elif number_of_minutes <= 30:
|
|
302
|
+
rate_limit.interval_minutes = 30
|
|
303
|
+
elif number_of_minutes <= 60:
|
|
304
|
+
rate_limit.interval_minutes = 60
|
|
305
|
+
elif number_of_minutes <= 60 * 24:
|
|
306
|
+
rate_limit.interval_minutes = 60 * 24
|
|
307
|
+
|
|
308
|
+
rate_limit.reset_time_millis = int(rate_limit_reset[i].strip())
|
|
309
|
+
rate_limit.calls_remaining = int(rate_limit_remaining[i].strip())
|
|
310
|
+
rate_limit.calls_made = int(rate_limit_made[i].strip())
|
|
311
|
+
updated_rate_limits.append(rate_limit)
|
|
312
|
+
|
|
313
|
+
if len(updated_rate_limits) > 0:
|
|
314
|
+
self.set_rate_limit(updated_rate_limits)
|
|
315
|
+
|
|
316
|
+
else:
|
|
317
|
+
logger.debug("Could not set rate limits: headers length should be the same")
|
|
318
|
+
else:
|
|
319
|
+
logger.debug("Could not set rate limits: missing headers")
|
|
320
|
+
|
|
321
|
+
def _absolute_url(self, url, encoded_params):
|
|
322
|
+
pattern = '%s%s%s'
|
|
323
|
+
|
|
324
|
+
if encoded_params:
|
|
325
|
+
pattern = '%s%s?%s'
|
|
326
|
+
|
|
327
|
+
return pattern % (self.api_url, self._construct_api_url(url), encoded_params)
|
|
328
|
+
|
|
329
|
+
def _construct_api_url(self, relative_url):
|
|
330
|
+
return '%s%s' % (self.client_id, relative_url)
|
|
331
|
+
|
|
332
|
+
def _create_apierror(self, result, url=None, data=None, method=None):
|
|
333
|
+
text = result.text if hasattr(result, 'text') else result.content
|
|
334
|
+
|
|
335
|
+
status_code = result.status_code
|
|
336
|
+
|
|
337
|
+
headers = result.headers
|
|
338
|
+
|
|
339
|
+
logger.error('API ERROR: status_code: %s | url: %s | method: %s | data: %r | headers: %s | content: %s',
|
|
340
|
+
status_code,
|
|
341
|
+
url,
|
|
342
|
+
method,
|
|
343
|
+
data,
|
|
344
|
+
headers,
|
|
345
|
+
text,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
request_error.send(url=url, status_code=status_code, headers=headers)
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
content = result.json()
|
|
352
|
+
except ValueError:
|
|
353
|
+
content = None
|
|
354
|
+
|
|
355
|
+
raise APIError(text, code=status_code, content=content, headers=headers)
|
|
356
|
+
|
|
357
|
+
def _create_decodeerror(self, result, url=None):
|
|
358
|
+
|
|
359
|
+
text = result.text if hasattr(result, 'text') else result.content
|
|
360
|
+
|
|
361
|
+
status_code = result.status_code
|
|
362
|
+
|
|
363
|
+
headers = result.headers
|
|
364
|
+
|
|
365
|
+
logger.error('DECODE ERROR: status_code: %s | headers: %s | content: %s',
|
|
366
|
+
status_code,
|
|
367
|
+
headers,
|
|
368
|
+
text,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
request_error.send(url=url, status_code=status_code, headers=headers)
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
content = result.json()
|
|
375
|
+
except ValueError:
|
|
376
|
+
content = None
|
|
377
|
+
|
|
378
|
+
raise DecodeError(text,
|
|
379
|
+
code=status_code,
|
|
380
|
+
headers=headers,
|
|
381
|
+
content=content)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import stat
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
import six
|
|
10
|
+
|
|
11
|
+
import mangopay
|
|
12
|
+
from mangopay.exceptions import AuthenticationError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StorageStrategyBase(object):
|
|
16
|
+
|
|
17
|
+
def get(self, env_key):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
def store(self, token, env_key):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StaticStorageStrategy(StorageStrategyBase):
|
|
25
|
+
|
|
26
|
+
_dict = dict()
|
|
27
|
+
|
|
28
|
+
def get(self, env_key):
|
|
29
|
+
return StaticStorageStrategy._dict[env_key]
|
|
30
|
+
|
|
31
|
+
def store(self, token, env_key):
|
|
32
|
+
StaticStorageStrategy._dict[env_key] = token
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FileStorageStrategy(StorageStrategyBase):
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
if mangopay.temp_dir is None:
|
|
39
|
+
mangopay.temp_dir = tempfile.gettempdir()
|
|
40
|
+
|
|
41
|
+
def get(self, env_key):
|
|
42
|
+
cache_path = os.path.join(mangopay.temp_dir, "cached-data." + env_key + ".py")
|
|
43
|
+
|
|
44
|
+
if not os.path.exists(cache_path):
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
with open(cache_path, 'rb') as f:
|
|
48
|
+
serialized_obj = f.read().decode('UTF-8')
|
|
49
|
+
try:
|
|
50
|
+
cached = json.loads(serialized_obj[1:])
|
|
51
|
+
except:
|
|
52
|
+
cached = None
|
|
53
|
+
|
|
54
|
+
return cached
|
|
55
|
+
|
|
56
|
+
def store(self, token, env_key):
|
|
57
|
+
cache_path = os.path.join(mangopay.temp_dir, "cached-data." + env_key + ".py")
|
|
58
|
+
if token is None:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
with open(cache_path, 'w') as f:
|
|
62
|
+
os.chmod(cache_path, stat.S_IRUSR | stat.S_IWUSR)
|
|
63
|
+
serialized_obj = "#" + json.dumps(token)
|
|
64
|
+
f.write(serialized_obj)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AuthorizationTokenManager(object):
|
|
68
|
+
|
|
69
|
+
def __init__(self, handler, storage_strategy):
|
|
70
|
+
self._handler = handler
|
|
71
|
+
self.authorization = ApiAuthorization(handler)
|
|
72
|
+
if storage_strategy:
|
|
73
|
+
self.storage_strategy = storage_strategy
|
|
74
|
+
else:
|
|
75
|
+
self.storage_strategy = StaticStorageStrategy()
|
|
76
|
+
|
|
77
|
+
def basic_token(self):
|
|
78
|
+
return self.authorization.basic_token()
|
|
79
|
+
|
|
80
|
+
def get_token(self):
|
|
81
|
+
try:
|
|
82
|
+
token = self.storage_strategy.get(self.get_evn_key())
|
|
83
|
+
except KeyError:
|
|
84
|
+
token = None
|
|
85
|
+
|
|
86
|
+
if not token or not token['timestamp'] or token['timestamp'] <= time.time():
|
|
87
|
+
auth_result = self.authorization.oauth_token()
|
|
88
|
+
if auth_result[0].status_code == 200:
|
|
89
|
+
token = auth_result[1]
|
|
90
|
+
else:
|
|
91
|
+
return None
|
|
92
|
+
token['timestamp'] = time.time() + (int(token['expires_in']) - 30)
|
|
93
|
+
self.set_token(token)
|
|
94
|
+
|
|
95
|
+
return token['token_type'] + ' ' + token['access_token']
|
|
96
|
+
|
|
97
|
+
def set_token(self, token):
|
|
98
|
+
self.storage_strategy.store(token, self.get_evn_key())
|
|
99
|
+
|
|
100
|
+
def get_evn_key(self):
|
|
101
|
+
return hashlib.md5((self._handler.client_id + self._handler.api_url +
|
|
102
|
+
self._handler.apikey).encode('utf-8')).hexdigest()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ApiAuthorization(object):
|
|
106
|
+
|
|
107
|
+
def __init__(self, handler):
|
|
108
|
+
self._handler = handler
|
|
109
|
+
|
|
110
|
+
def oauth_token(self):
|
|
111
|
+
result = self._handler.request(method='POST',
|
|
112
|
+
url='oauth/token',
|
|
113
|
+
data={'grant_type': 'client_credentials'},
|
|
114
|
+
oauth_request=True)
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
def basic_token(self):
|
|
118
|
+
if self._handler.client_id is None or self._handler.apikey is None:
|
|
119
|
+
raise AuthenticationError(
|
|
120
|
+
'Authentication failed. (Please set your Mangopay API username '
|
|
121
|
+
'and password using "mangopay.client_id = CLIENT_ID" '
|
|
122
|
+
'and "mangopay.apikey = APIKEY").')
|
|
123
|
+
|
|
124
|
+
credentials = '%s:%s' % (self._handler.client_id, self._handler.apikey)
|
|
125
|
+
credentials = base64.b64encode(credentials.encode('ascii'))
|
|
126
|
+
|
|
127
|
+
if six.PY3:
|
|
128
|
+
credentials = credentials.decode('utf-8')
|
|
129
|
+
|
|
130
|
+
return 'Basic %s' % credentials
|