edgegrid-python 2.0.4__py3-none-any.whl → 2.0.6__py3-none-any.whl
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.
- akamai/edgegrid/__init__.py +2 -2
- build/lib/akamai/edgegrid/__init__.py +41 -0
- build/lib/akamai/edgegrid/edgegrid.py +323 -0
- build/lib/akamai/edgegrid/edgerc.py +37 -0
- build/lib/akamai/edgegrid/test/__init__.py +0 -0
- build/lib/akamai/edgegrid/test/conftest.py +44 -0
- build/lib/akamai/edgegrid/test/test_edgegrid.py +390 -0
- build/lib/ci/check_namespace_pkg.py +33 -0
- build/lib/ci/coverage_report.py +71 -0
- build/lib/examples/create-credentials.py +34 -0
- build/lib/examples/delete-credentials.py +35 -0
- build/lib/examples/get-credentials.py +37 -0
- build/lib/examples/update-credentials.py +47 -0
- ci/check_namespace_pkg.py +33 -0
- ci/coverage_report.py +71 -0
- {edgegrid_python-2.0.4.dist-info → edgegrid_python-2.0.6.dist-info}/METADATA +3 -3
- edgegrid_python-2.0.6.dist-info/RECORD +34 -0
- {edgegrid_python-2.0.4.dist-info → edgegrid_python-2.0.6.dist-info}/WHEEL +1 -1
- {edgegrid_python-2.0.4.dist-info → edgegrid_python-2.0.6.dist-info}/licenses/LICENSE +1 -1
- edgegrid_python-2.0.6.dist-info/top_level.txt +4 -0
- examples/create-credentials.py +34 -0
- examples/delete-credentials.py +35 -0
- examples/get-credentials.py +37 -0
- examples/update-credentials.py +47 -0
- edgegrid_python-2.0.4-py3.13-nspkg.pth +0 -1
- edgegrid_python-2.0.4.dist-info/RECORD +0 -18
- edgegrid_python-2.0.4.dist-info/namespace_packages.txt +0 -1
- edgegrid_python-2.0.4.dist-info/top_level.txt +0 -1
akamai/edgegrid/__init__.py
CHANGED
|
@@ -36,6 +36,6 @@ from .edgerc import EdgeRc
|
|
|
36
36
|
__all__ = ['EdgeGridAuth', 'EdgeRc']
|
|
37
37
|
|
|
38
38
|
__title__ = 'edgegrid-python'
|
|
39
|
-
__version__ = '2.0.
|
|
39
|
+
__version__ = '2.0.6'
|
|
40
40
|
__license__ = 'Apache 2.0'
|
|
41
|
-
__copyright__ = 'Copyright
|
|
41
|
+
__copyright__ = 'Copyright 2026 Akamai Technologies'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
akamai.edgegrid
|
|
3
|
+
~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
This library provides an authentication handler for Requests that implements the
|
|
6
|
+
Akamai {OPEN} EdgeGrid client authentication protocol as
|
|
7
|
+
specified by https://developer.akamai.com/introduction/Client_Auth.html.
|
|
8
|
+
For more information visit https://developer.akamai.com.
|
|
9
|
+
|
|
10
|
+
usage:
|
|
11
|
+
|
|
12
|
+
>>> import requests
|
|
13
|
+
>>> from akamai.edgegrid import EdgeGridAuth
|
|
14
|
+
>>> from urlparse import urljoin
|
|
15
|
+
|
|
16
|
+
>>> baseurl = 'https://akaa-WWWWWWWWWWWW.luna.akamaiapis.net/'
|
|
17
|
+
>>> s = requests.Session()
|
|
18
|
+
>>> s.auth = EdgeGridAuth(
|
|
19
|
+
client_token='akab-XXXXXXXXXXXXXXXXXXXXXXX',
|
|
20
|
+
client_secret='YYYYYYYYYYYYYYYYYYYYYYYYYY',
|
|
21
|
+
access_token='akab-ZZZZZZZZZZZZZZZZZZZZZZZZZZZ'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
... now you have a requests session object that can be used to make {OPEN} requests
|
|
25
|
+
|
|
26
|
+
>>> result = s.get(urljoin(baseurl, '/diagnostic-tools/v1/locations'))
|
|
27
|
+
>>> result.status_code
|
|
28
|
+
200
|
|
29
|
+
>>> result.json()['locations'][0]
|
|
30
|
+
Hongkong, Hong Kong
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from .edgegrid import EdgeGridAuth
|
|
34
|
+
from .edgerc import EdgeRc
|
|
35
|
+
|
|
36
|
+
__all__ = ['EdgeGridAuth', 'EdgeRc']
|
|
37
|
+
|
|
38
|
+
__title__ = 'edgegrid-python'
|
|
39
|
+
__version__ = '2.0.6'
|
|
40
|
+
__license__ = 'Apache 2.0'
|
|
41
|
+
__copyright__ = 'Copyright 2026 Akamai Technologies'
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# pylint: disable=too-many-arguments,missing-function-docstring
|
|
2
|
+
"""EdgeGrid requests Auth handler"""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import uuid
|
|
6
|
+
import hashlib
|
|
7
|
+
import hmac
|
|
8
|
+
import base64
|
|
9
|
+
import re
|
|
10
|
+
import os
|
|
11
|
+
from time import gmtime, strftime
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
from requests.auth import AuthBase
|
|
15
|
+
|
|
16
|
+
from .edgerc import EdgeRc
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
__all__ = ['EdgeGridAuth']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def eg_timestamp():
|
|
24
|
+
"""Generates EdgeGrid compatible timestamp"""
|
|
25
|
+
return strftime('%Y%m%dT%H:%M:%S+0000', gmtime())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def new_nonce():
|
|
29
|
+
return uuid.uuid4()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def base64_hmac_sha256(data, key):
|
|
33
|
+
return base64.b64encode(
|
|
34
|
+
hmac.new(
|
|
35
|
+
key.encode('utf8'),
|
|
36
|
+
data.encode('utf8'),
|
|
37
|
+
hashlib.sha256).digest()
|
|
38
|
+
).decode('utf8')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def base64_sha256(data):
|
|
42
|
+
digest = hashlib.sha256(data).digest()
|
|
43
|
+
return base64.b64encode(digest).decode('utf8')
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def read_stream_and_rewind(f, max_read):
|
|
47
|
+
"""Reads up to read_max bytes from a python file object (like _io.BufferedReader)
|
|
48
|
+
or a MultipartEncoder object, then rewinds the stream.
|
|
49
|
+
|
|
50
|
+
The read() method of these objects is decorated by httpie with a side-effect code which
|
|
51
|
+
prints the body content to stdout when the 'B' option is specified for --print. However,
|
|
52
|
+
it does not trigger in the pre-request phase when this plugin is executed. Still, after reading
|
|
53
|
+
we must set the stream position to the beginning to not impact subsequent reads outside
|
|
54
|
+
the plugin. (We don't assume we can be passed a partially read stream to the plugin.)
|
|
55
|
+
|
|
56
|
+
Raises TypeError if read() or seek() is not supported by f or f._buffer. May potentially raise
|
|
57
|
+
OSError for any failed I/O operation, in particular io.UnsupportedOperation if the stream is
|
|
58
|
+
not seekable (e.g. is a pipe which we don't expect here as httpie reads pipe contents and
|
|
59
|
+
sets body as bytes).
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
res = f.read(max_read)
|
|
63
|
+
except AttributeError as exc:
|
|
64
|
+
raise TypeError(f'akamai.edgegrid: unexpected body type: {type(f).__name__}') from exc
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
f.seek(0)
|
|
68
|
+
except AttributeError:
|
|
69
|
+
# a MultipartEncoder
|
|
70
|
+
try:
|
|
71
|
+
# During read(), MultipartEncoder lazily loads its upload parts into self._buffer
|
|
72
|
+
# depending on the requested number of bytes. Then a regular read() on self._buffer
|
|
73
|
+
# is performed. Therefore, rewinding self._buffer effectively rewinds the whole
|
|
74
|
+
# MultipartEncoder content.
|
|
75
|
+
# pylint: disable=protected-access
|
|
76
|
+
f._buffer.seek(0)
|
|
77
|
+
except AttributeError as exc:
|
|
78
|
+
raise TypeError(f'akamai.edgegrid: unexpected body type: {type(f).__name__}') from exc
|
|
79
|
+
return res
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def read_body_content(body, max_body):
|
|
83
|
+
"""The body argument may be one of the following:
|
|
84
|
+
1. bytes object
|
|
85
|
+
2. str object
|
|
86
|
+
3. _io.BufferedReader object for body input from file
|
|
87
|
+
4. requests_toolbelt.MultipartEncoder object for multipart form requests
|
|
88
|
+
5. httpie.uploads.ChunkedUploadStream object for chunked transfer encoding
|
|
89
|
+
(when --chunked, currently not supported)
|
|
90
|
+
May raise TypeError for unexpected input type or OSError for I/O operations.
|
|
91
|
+
"""
|
|
92
|
+
if isinstance(body, bytes):
|
|
93
|
+
return body[:max_body]
|
|
94
|
+
if isinstance(body, str):
|
|
95
|
+
return body.encode('utf8')[:max_body]
|
|
96
|
+
return read_stream_and_rewind(body, max_body)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def determine_body_len(body):
|
|
100
|
+
"""May raise exception if body appears to be a file (is not a str, bytes or MultipartEncoder)
|
|
101
|
+
but either:
|
|
102
|
+
- has no fileno method (TypeError)
|
|
103
|
+
- raises OSError while trying to calculate the length using the file descriptor"""
|
|
104
|
+
if isinstance(body, bytes):
|
|
105
|
+
return len(body)
|
|
106
|
+
if isinstance(body, str):
|
|
107
|
+
return len(body.encode('utf8'))
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# a MultipartEncoder?
|
|
111
|
+
return body.len
|
|
112
|
+
except AttributeError:
|
|
113
|
+
# a file object?
|
|
114
|
+
try:
|
|
115
|
+
return os.stat(body.fileno()).st_size
|
|
116
|
+
except AttributeError as exc:
|
|
117
|
+
raise TypeError(
|
|
118
|
+
f'akamai.edgegrid: unexpected body type: {type(body).__name__}') from exc
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class EdgeGridAuth(AuthBase):
|
|
122
|
+
"""A Requests authentication handler that provides Akamai {OPEN} EdgeGrid support.
|
|
123
|
+
|
|
124
|
+
Basic Usage::
|
|
125
|
+
>>> import requests
|
|
126
|
+
>>> from akamai.edgegrid import EdgeGridAuth
|
|
127
|
+
>>> s = requests.Session()
|
|
128
|
+
>>> s.auth = EdgeGridAuth(
|
|
129
|
+
client_token='cccccccccccccccccc',
|
|
130
|
+
client_secret='sssssssssssssssss',
|
|
131
|
+
access_token='aaaaaaaaaaaaaaaaa'
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self, client_token, client_secret, access_token,
|
|
137
|
+
*, headers_to_sign=(), max_body=131072):
|
|
138
|
+
"""Initialize authentication using the given parameters from the Akamai OPEN APIs
|
|
139
|
+
Interface:
|
|
140
|
+
|
|
141
|
+
:param client_token: Client token provided by "Credentials" ui
|
|
142
|
+
:param client_secret: Client secret provided by "Credentials" ui
|
|
143
|
+
:param access_token: Access token provided by "Authorizations" ui
|
|
144
|
+
:param headers_to_sign: An ordered list header names that will be included in
|
|
145
|
+
the signature. This will be provided by specific APIs. (default [])
|
|
146
|
+
:param max_body: Maximum content body size for POST requests. This will be provided by
|
|
147
|
+
specific APIs. (default 131072)
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
# pylint: disable=invalid-name
|
|
151
|
+
self.ah = EdgeGridAuthHeaders(
|
|
152
|
+
client_token,
|
|
153
|
+
client_secret,
|
|
154
|
+
access_token,
|
|
155
|
+
headers_to_sign=headers_to_sign,
|
|
156
|
+
max_body=max_body
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def from_edgerc(rcinput, section='default'):
|
|
161
|
+
"""
|
|
162
|
+
Returns an EdgeGridAuth object from the configuration from the given section
|
|
163
|
+
of the given edgerc file.
|
|
164
|
+
|
|
165
|
+
:param rcinput: EdgeRc instance or path to the edgerc file
|
|
166
|
+
:param section: the section to use (this is the [bracketed] part of the edgerc,
|
|
167
|
+
default is 'default')
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
if isinstance(rcinput, EdgeRc):
|
|
171
|
+
edgerc = rcinput
|
|
172
|
+
else:
|
|
173
|
+
edgerc = EdgeRc(rcinput)
|
|
174
|
+
|
|
175
|
+
return EdgeGridAuth(
|
|
176
|
+
client_token=edgerc.get(section, 'client_token'),
|
|
177
|
+
client_secret=edgerc.get(section, 'client_secret'),
|
|
178
|
+
access_token=edgerc.get(section, 'access_token'),
|
|
179
|
+
headers_to_sign=edgerc.getlist(section, 'headers_to_sign'),
|
|
180
|
+
max_body=edgerc.getint(section, 'max_body')
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def handle_redirect(self, res, **_):
|
|
184
|
+
if res.is_redirect:
|
|
185
|
+
redirect_location = res.headers['location']
|
|
186
|
+
|
|
187
|
+
logger.debug("signing the redirected url: %s", redirect_location)
|
|
188
|
+
request_to_sign = res.request.copy()
|
|
189
|
+
request_to_sign.url = redirect_location
|
|
190
|
+
|
|
191
|
+
res.request.headers['Authorization'] = self.ah.make_auth_header(
|
|
192
|
+
request_to_sign, eg_timestamp(), new_nonce())
|
|
193
|
+
|
|
194
|
+
def __call__(self, r):
|
|
195
|
+
timestamp = eg_timestamp()
|
|
196
|
+
nonce = new_nonce()
|
|
197
|
+
|
|
198
|
+
r.headers['Authorization'] = self.ah.make_auth_header(r, timestamp, nonce)
|
|
199
|
+
r.register_hook('response', self.handle_redirect)
|
|
200
|
+
return r
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class EdgeGridAuthHeaders:
|
|
204
|
+
"""
|
|
205
|
+
A class for preparing requests authentication headers needed for
|
|
206
|
+
Akamai {OPEN} EdgeGrid support.
|
|
207
|
+
"""
|
|
208
|
+
def __init__(self, client_token, client_secret, access_token,
|
|
209
|
+
*, headers_to_sign=(), max_body=131072):
|
|
210
|
+
self.client_token = client_token
|
|
211
|
+
self.client_secret = client_secret
|
|
212
|
+
self.access_token = access_token
|
|
213
|
+
self.headers_to_sign = [h.lower() for h in headers_to_sign]
|
|
214
|
+
self.max_body = max_body
|
|
215
|
+
|
|
216
|
+
def make_signing_key(self, timestamp):
|
|
217
|
+
signing_key = base64_hmac_sha256(timestamp, self.client_secret)
|
|
218
|
+
logger.debug('signing key: %s', signing_key)
|
|
219
|
+
return signing_key
|
|
220
|
+
|
|
221
|
+
def canonicalize_headers(self, headers):
|
|
222
|
+
spaces_re = re.compile('\\s+')
|
|
223
|
+
|
|
224
|
+
# note: r.headers is a case-insensitive dict and self.headers_to_sign
|
|
225
|
+
# should already be in lowercase at this point
|
|
226
|
+
# pylint: disable=consider-using-f-string
|
|
227
|
+
return '\t'.join([
|
|
228
|
+
"%s:%s" % (h, spaces_re.sub(' ', headers[h].strip()))
|
|
229
|
+
for h in self.headers_to_sign if h in headers
|
|
230
|
+
])
|
|
231
|
+
|
|
232
|
+
def make_content_hash(self, body, method):
|
|
233
|
+
logger.debug("body is '%s'", body)
|
|
234
|
+
content_hash = ""
|
|
235
|
+
if method == 'POST':
|
|
236
|
+
buf = read_body_content(body, self.max_body)
|
|
237
|
+
if buf:
|
|
238
|
+
logger.debug("signing content: %s", buf)
|
|
239
|
+
content_hash = base64_sha256(buf)
|
|
240
|
+
try:
|
|
241
|
+
body_len = determine_body_len(body)
|
|
242
|
+
if body_len > self.max_body:
|
|
243
|
+
logger.debug(
|
|
244
|
+
"data length %d is larger than maximum %d "
|
|
245
|
+
"and will be truncated for computing the hash",
|
|
246
|
+
body_len, self.max_body)
|
|
247
|
+
except (TypeError, OSError) as e:
|
|
248
|
+
# body length is needed only for debugging: just log a possible exception
|
|
249
|
+
logger.warning("cannot determine length of request body=%s: %s", body, e)
|
|
250
|
+
logger.debug("content hash is '%s'", content_hash)
|
|
251
|
+
return content_hash
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def get_header_versions(header=None):
|
|
255
|
+
if header is None:
|
|
256
|
+
header = {}
|
|
257
|
+
|
|
258
|
+
version_header = ''
|
|
259
|
+
akamai_cli = os.getenv('AKAMAI_CLI')
|
|
260
|
+
akamai_cli_version = os.getenv('AKAMAI_CLI_VERSION')
|
|
261
|
+
if akamai_cli and akamai_cli_version:
|
|
262
|
+
version_header += " AkamaiCLI/" + akamai_cli_version
|
|
263
|
+
|
|
264
|
+
akamai_cli_command = os.getenv('AKAMAI_CLI_COMMAND')
|
|
265
|
+
akamai_cli_command_version = os.getenv('AKAMAI_CLI_COMMAND_VERSION')
|
|
266
|
+
if akamai_cli_command and akamai_cli_command_version:
|
|
267
|
+
version_header += " AkamaiCLI-" + akamai_cli_command + \
|
|
268
|
+
"/" + akamai_cli_command_version
|
|
269
|
+
|
|
270
|
+
if version_header != '':
|
|
271
|
+
if 'User-Agent' not in header:
|
|
272
|
+
header['User-Agent'] = version_header.strip()
|
|
273
|
+
else:
|
|
274
|
+
header['User-Agent'] += version_header
|
|
275
|
+
|
|
276
|
+
return header
|
|
277
|
+
|
|
278
|
+
def make_data_to_sign(self, request, auth_header):
|
|
279
|
+
parsed_url = urlparse(request.url)
|
|
280
|
+
|
|
281
|
+
if request.headers.get('Host', False):
|
|
282
|
+
netloc = request.headers['Host']
|
|
283
|
+
else:
|
|
284
|
+
netloc = parsed_url.netloc
|
|
285
|
+
|
|
286
|
+
self.get_header_versions(request.headers)
|
|
287
|
+
|
|
288
|
+
data_to_sign = '\t'.join([
|
|
289
|
+
request.method,
|
|
290
|
+
parsed_url.scheme,
|
|
291
|
+
netloc,
|
|
292
|
+
# Note: relative URL constraints are handled by requests when it sets up 'r'
|
|
293
|
+
parsed_url.path + (';' + parsed_url.params if parsed_url.params else "") +
|
|
294
|
+
('?' + parsed_url.query if parsed_url.query else ""),
|
|
295
|
+
self.canonicalize_headers(request.headers),
|
|
296
|
+
self.make_content_hash(request.body or '', request.method),
|
|
297
|
+
auth_header
|
|
298
|
+
])
|
|
299
|
+
logger.debug('data to sign: %s', '\\t'.join(data_to_sign.split('\t')))
|
|
300
|
+
return data_to_sign
|
|
301
|
+
|
|
302
|
+
def sign_request(self, request, timestamp, auth_header):
|
|
303
|
+
return base64_hmac_sha256(
|
|
304
|
+
self.make_data_to_sign(request, auth_header),
|
|
305
|
+
self.make_signing_key(timestamp)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def make_auth_header(self, request, timestamp, nonce):
|
|
309
|
+
kvps = [
|
|
310
|
+
('client_token', self.client_token),
|
|
311
|
+
('access_token', self.access_token),
|
|
312
|
+
('timestamp', timestamp),
|
|
313
|
+
('nonce', nonce),
|
|
314
|
+
]
|
|
315
|
+
auth_header = "EG1-HMAC-SHA256 " + \
|
|
316
|
+
';'.join([f"{k}={v}" for k, v in kvps]) + ';'
|
|
317
|
+
logger.debug('unsigned authorization header: %s', auth_header)
|
|
318
|
+
|
|
319
|
+
signed_auth_header = auth_header + \
|
|
320
|
+
'signature=' + self.sign_request(request, timestamp, auth_header)
|
|
321
|
+
|
|
322
|
+
logger.debug('signed authorization header: %s', signed_auth_header)
|
|
323
|
+
return signed_auth_header
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Support for .edgerc file format"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from configparser import ConfigParser
|
|
5
|
+
from os.path import expanduser
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EdgeRc(ConfigParser):
|
|
11
|
+
"""Class for managing .edgerc files"""
|
|
12
|
+
def __init__(self, filename):
|
|
13
|
+
ConfigParser.__init__(self,
|
|
14
|
+
{'client_token': '',
|
|
15
|
+
'client_secret': '',
|
|
16
|
+
'host': '',
|
|
17
|
+
'access_token': '',
|
|
18
|
+
'max_body': '131072',
|
|
19
|
+
'headers_to_sign': 'None'})
|
|
20
|
+
logger.debug("loading edgerc from %s", filename)
|
|
21
|
+
|
|
22
|
+
self.read(expanduser(filename))
|
|
23
|
+
|
|
24
|
+
logger.debug("successfully loaded edgerc")
|
|
25
|
+
|
|
26
|
+
def optionxform(self, optionstr):
|
|
27
|
+
"""support both max_body and max-body style keys"""
|
|
28
|
+
return optionstr.replace('-', '_')
|
|
29
|
+
|
|
30
|
+
def getlist(self, section, option):
|
|
31
|
+
"""
|
|
32
|
+
returns the named option as a list, splitting the original value by ','
|
|
33
|
+
"""
|
|
34
|
+
value = self.get(section, option)
|
|
35
|
+
if value:
|
|
36
|
+
return value.split(',')
|
|
37
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# pylint: disable=missing-function-docstring
|
|
2
|
+
"""Unit tests helpers"""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
test_dir = os.path.abspath(os.path.dirname(__file__))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def cases():
|
|
12
|
+
with open(f'{test_dir}/testcases.json', encoding="utf-8") as data:
|
|
13
|
+
data = json.load(data)
|
|
14
|
+
return data
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(scope="package")
|
|
18
|
+
def testdata():
|
|
19
|
+
with open(f'{test_dir}/testdata.json', encoding="utf-8") as data:
|
|
20
|
+
data = json.load(data)
|
|
21
|
+
return data
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def names(tests):
|
|
25
|
+
result = []
|
|
26
|
+
for test in tests:
|
|
27
|
+
result.append(test["testName"])
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def multipart_fields():
|
|
33
|
+
with open(f'{test_dir}/sample_file.txt', "rb") as f:
|
|
34
|
+
result = {
|
|
35
|
+
"foo": "bar",
|
|
36
|
+
"baz": ("sample_file.txt", f),
|
|
37
|
+
}
|
|
38
|
+
yield result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def sample_file():
|
|
43
|
+
with open(f'{test_dir}/sample_file.txt', "rb") as f:
|
|
44
|
+
yield f
|