deceit 0.4__tar.gz → 0.7__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.
- {deceit-0.4 → deceit-0.7}/PKG-INFO +1 -1
- {deceit-0.4 → deceit-0.7}/deceit/api_client.py +73 -8
- {deceit-0.4 → deceit-0.7}/deceit/exceptions.py +12 -3
- {deceit-0.4 → deceit-0.7}/deceit.egg-info/PKG-INFO +1 -1
- {deceit-0.4 → deceit-0.7}/deceit.egg-info/requires.txt +4 -0
- {deceit-0.4 → deceit-0.7}/dev.txt +2 -0
- {deceit-0.4 → deceit-0.7}/setup.py +3 -1
- {deceit-0.4 → deceit-0.7}/MANIFEST.in +0 -0
- {deceit-0.4 → deceit-0.7}/README.md +0 -0
- {deceit-0.4 → deceit-0.7}/deceit/__init__.py +0 -0
- {deceit-0.4 → deceit-0.7}/deceit/adapters.py +0 -0
- {deceit-0.4 → deceit-0.7}/deceit/encoders.py +0 -0
- {deceit-0.4 → deceit-0.7}/deceit.egg-info/SOURCES.txt +0 -0
- {deceit-0.4 → deceit-0.7}/deceit.egg-info/dependency_links.txt +0 -0
- {deceit-0.4 → deceit-0.7}/deceit.egg-info/top_level.txt +0 -0
- {deceit-0.4 → deceit-0.7}/setup.cfg +0 -0
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import posixpath
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from time import time
|
|
4
6
|
from typing import Optional, Dict, Any
|
|
7
|
+
from pytz import utc
|
|
5
8
|
import requests
|
|
6
9
|
from requests import PreparedRequest
|
|
7
10
|
from requests.models import Request, Response
|
|
8
|
-
|
|
11
|
+
from oauthlib.oauth2 import BackendApplicationClient
|
|
12
|
+
from oauthlib.oauth2.rfc6749.tokens import prepare_bearer_headers
|
|
13
|
+
from requests_oauthlib.oauth2_session import OAuth2Session
|
|
9
14
|
from .adapters import RetryAdapter
|
|
10
15
|
from .encoders import JsonEncoder
|
|
11
16
|
from .exceptions import ApiException
|
|
12
17
|
|
|
13
18
|
|
|
14
|
-
|
|
15
19
|
class ApiClient:
|
|
16
20
|
def __init__(self, *args, base_url=None, default_timeout=None,
|
|
17
21
|
exception_class=None, **kwargs):
|
|
18
22
|
self.base_url = base_url
|
|
19
|
-
|
|
23
|
+
self.default_timeout = default_timeout
|
|
24
|
+
self.adapter = RetryAdapter(timeout=self.default_timeout)
|
|
20
25
|
self.session = requests.Session()
|
|
21
|
-
self.session.mount('https://', adapter)
|
|
22
|
-
self.session.mount('http://', adapter)
|
|
26
|
+
self.session.mount('https://', self.adapter)
|
|
27
|
+
self.session.mount('http://', self.adapter)
|
|
23
28
|
self.log = logging.getLogger(__name__)
|
|
24
29
|
self.exception_class = exception_class or ApiException
|
|
25
30
|
|
|
@@ -61,16 +66,17 @@ class ApiClient:
|
|
|
61
66
|
"""
|
|
62
67
|
headers = self.headers()
|
|
63
68
|
headers.update(kwargs.pop('headers', None) or {})
|
|
64
|
-
timeout = kwargs.pop('timeout', None)
|
|
69
|
+
timeout = kwargs.pop('timeout', None) or self.default_timeout
|
|
65
70
|
send_kwargs = {}
|
|
66
71
|
if timeout is not None:
|
|
67
72
|
send_kwargs['timeout'] = timeout
|
|
68
73
|
base_url = self.base_url
|
|
69
74
|
url = posixpath.join(base_url, route) if base_url else route
|
|
75
|
+
body = None
|
|
70
76
|
if json_data:
|
|
71
77
|
body = json.dumps(json_data, cls=JsonEncoder)
|
|
72
78
|
headers.setdefault('content-type', 'application/json')
|
|
73
|
-
|
|
79
|
+
elif form_data:
|
|
74
80
|
body = form_data
|
|
75
81
|
headers.setdefault('content-type', 'application/x-www-form-urlencoded')
|
|
76
82
|
request = Request(
|
|
@@ -97,7 +103,7 @@ class ApiClient:
|
|
|
97
103
|
raise self.exception_class.from_response(response)
|
|
98
104
|
try:
|
|
99
105
|
return response.json()
|
|
100
|
-
except json.JSONDecodeError:
|
|
106
|
+
except json.JSONDecodeError: # pragma: nocover
|
|
101
107
|
pass
|
|
102
108
|
return None
|
|
103
109
|
|
|
@@ -123,3 +129,62 @@ class ApiClient:
|
|
|
123
129
|
return self.send(
|
|
124
130
|
'patch', route, params=params, form_data=form_data,
|
|
125
131
|
json_data=json_data, **kwargs)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class BackendOauth2Client(ApiClient):
|
|
135
|
+
def __init__(self, conf, *args, base_url=None, default_timeout=None,
|
|
136
|
+
exception_class=None, token_url=None,
|
|
137
|
+
client_id=None, password=None, scopes=None,
|
|
138
|
+
include_client_id=None, **kwargs):
|
|
139
|
+
super().__init__(
|
|
140
|
+
base_url=base_url or conf.base_url or None,
|
|
141
|
+
default_timeout=default_timeout or conf.default_timeout or None,
|
|
142
|
+
exception_class=exception_class, **kwargs)
|
|
143
|
+
self.token_url = token_url or conf.token_url
|
|
144
|
+
self.client_id = client_id or conf.client_id
|
|
145
|
+
self.token_expires_at = time()
|
|
146
|
+
self.client_secret = password or conf.client_secret or conf.password
|
|
147
|
+
self.scopes = scopes or conf.scopes or []
|
|
148
|
+
self.session = OAuth2Session(client=BackendApplicationClient(
|
|
149
|
+
client_id=self.client_id,
|
|
150
|
+
scope=self.scope))
|
|
151
|
+
self.include_client_id = include_client_id or conf.get('include_client_id')
|
|
152
|
+
self.session.mount('https://', self.adapter)
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def scope(self):
|
|
156
|
+
if self.scopes:
|
|
157
|
+
scopes = ' '.join(self.scopes)
|
|
158
|
+
return scopes
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def fetch_token(self, **kwargs):
|
|
162
|
+
tm_now = time()
|
|
163
|
+
self.session.fetch_token(
|
|
164
|
+
self.token_url,
|
|
165
|
+
client_id=self.client_id,
|
|
166
|
+
client_secret=self.client_secret,
|
|
167
|
+
include_client_id=self.include_client_id,
|
|
168
|
+
scope=self.scope,
|
|
169
|
+
**kwargs)
|
|
170
|
+
expires_at = self.session.token.get('expires_at')
|
|
171
|
+
if not expires_at:
|
|
172
|
+
# microsoft apis can use `expires_on`
|
|
173
|
+
expires_at = self.session.token.get('expires_on')
|
|
174
|
+
if not expires_at:
|
|
175
|
+
# some other apis use `expires_in`
|
|
176
|
+
expires_at = self.session.token.get('expires_in') + tm_now
|
|
177
|
+
self.token_expires_at = expires_at - 300
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def now(cls):
|
|
181
|
+
return datetime.now(utc)
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def expired(self):
|
|
185
|
+
return time() >= self.token_expires_at
|
|
186
|
+
|
|
187
|
+
def presend(self, request: PreparedRequest):
|
|
188
|
+
if self.expired:
|
|
189
|
+
self.fetch_token()
|
|
190
|
+
prepare_bearer_headers(self.session.access_token, request.headers)
|
|
@@ -3,12 +3,14 @@ from requests.models import Response
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class ApiException(Exception):
|
|
6
|
-
def __init__(self, status_code=None, content=None, text=None, data=None
|
|
7
|
-
|
|
6
|
+
def __init__(self, status_code=None, content=None, text=None, data=None,
|
|
7
|
+
headers=None):
|
|
8
|
+
super().__init__(status_code, content, text, data, headers)
|
|
8
9
|
self.status_code = status_code
|
|
9
10
|
self.content = content
|
|
10
11
|
self.text = text
|
|
11
12
|
self.data = data
|
|
13
|
+
self.headers = headers
|
|
12
14
|
|
|
13
15
|
def __str__(self):
|
|
14
16
|
klass = self.__class__.__name__.lower()
|
|
@@ -23,7 +25,12 @@ class ApiException(Exception):
|
|
|
23
25
|
body = json.dumps(self.data, indent=2)
|
|
24
26
|
else:
|
|
25
27
|
body = self.text
|
|
26
|
-
|
|
28
|
+
st_headers = ''
|
|
29
|
+
if self.headers:
|
|
30
|
+
st = json.dumps(self.headers, indent=2)
|
|
31
|
+
st_headers = f'[{klass}] [{self.status_code}] / headers => {st}'
|
|
32
|
+
p.text(f'[{klass}] [{self.status_code}] => {body}' + st_headers)
|
|
33
|
+
p.text()
|
|
27
34
|
|
|
28
35
|
@classmethod
|
|
29
36
|
def from_response(cls, response: Response):
|
|
@@ -33,10 +40,12 @@ class ApiException(Exception):
|
|
|
33
40
|
response.content,
|
|
34
41
|
response.text,
|
|
35
42
|
response.json(),
|
|
43
|
+
response.headers,
|
|
36
44
|
)
|
|
37
45
|
except json.JSONDecodeError:
|
|
38
46
|
return cls(
|
|
39
47
|
response.status_code,
|
|
40
48
|
response.content,
|
|
41
49
|
response.text,
|
|
50
|
+
headers=response.headers,
|
|
42
51
|
)
|
|
@@ -7,7 +7,7 @@ from importlib.resources import open_text
|
|
|
7
7
|
# Version info -- read without importing
|
|
8
8
|
_locals = {}
|
|
9
9
|
|
|
10
|
-
version = '0.
|
|
10
|
+
version = '0.7'
|
|
11
11
|
|
|
12
12
|
# PyYAML ships a split Python 2/3 codebase. Unfortunately, some pip versions
|
|
13
13
|
# attempt to interpret both halves of PyYAML, yielding SyntaxErrors. Thus, we
|
|
@@ -70,6 +70,8 @@ setup(
|
|
|
70
70
|
],
|
|
71
71
|
install_requires=[
|
|
72
72
|
'requests',
|
|
73
|
+
'requests_oauthlib',
|
|
74
|
+
'pytz',
|
|
73
75
|
],
|
|
74
76
|
extras_require=extras,
|
|
75
77
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|