edgegrid-python 1.3.1__py3-none-any.whl → 2.0.0__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 +3 -18
- akamai/edgegrid/edgegrid.py +132 -109
- akamai/edgegrid/edgerc.py +5 -28
- akamai/edgegrid/test/conftest.py +44 -0
- akamai/edgegrid/test/edgerc_that_doesnt_parse +1 -1
- akamai/edgegrid/test/test_edgegrid.py +373 -392
- akamai/edgegrid/test/testdata.json +16 -195
- edgegrid_python-2.0.0-py3.13-nspkg.pth +1 -0
- {edgegrid_python-1.3.1.dist-info → edgegrid_python-2.0.0.dist-info}/LICENSE +1 -1
- edgegrid_python-2.0.0.dist-info/METADATA +304 -0
- edgegrid_python-2.0.0.dist-info/RECORD +16 -0
- {edgegrid_python-1.3.1.dist-info → edgegrid_python-2.0.0.dist-info}/WHEEL +1 -1
- edgegrid_python-1.3.1-py3.10-nspkg.pth +0 -1
- edgegrid_python-1.3.1.dist-info/METADATA +0 -22
- edgegrid_python-1.3.1.dist-info/RECORD +0 -15
- {edgegrid_python-1.3.1.dist-info → edgegrid_python-2.0.0.dist-info}/namespace_packages.txt +0 -0
- {edgegrid_python-1.3.1.dist-info → edgegrid_python-2.0.0.dist-info}/top_level.txt +0 -0
akamai/edgegrid/__init__.py
CHANGED
|
@@ -32,25 +32,10 @@ usage:
|
|
|
32
32
|
|
|
33
33
|
from .edgegrid import EdgeGridAuth
|
|
34
34
|
from .edgerc import EdgeRc
|
|
35
|
+
|
|
35
36
|
__all__ = ['EdgeGridAuth', 'EdgeRc']
|
|
36
37
|
|
|
37
38
|
__title__ = 'edgegrid-python'
|
|
38
|
-
__version__ = '
|
|
39
|
-
__author__ = 'Jonathan Landis <jlandis@akamai.com>'
|
|
40
|
-
__maintainer__ = 'Akamai Developer Experience team <dl-devexp-eng@akamai.com>'
|
|
39
|
+
__version__ = '2.0.0'
|
|
41
40
|
__license__ = 'Apache 2.0'
|
|
42
|
-
__copyright__ = 'Copyright
|
|
43
|
-
|
|
44
|
-
# Copyright 2021 Akamai Technologies, Inc. All Rights Reserved
|
|
45
|
-
#
|
|
46
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
47
|
-
# you may not use this file except in compliance with the License.
|
|
48
|
-
# You may obtain a copy of the License at
|
|
49
|
-
#
|
|
50
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
51
|
-
#
|
|
52
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
53
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
54
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
55
|
-
# See the License for the specific language governing permissions and
|
|
56
|
-
# limitations under the License.
|
|
41
|
+
__copyright__ = 'Copyright 2024 Akamai Technologies'
|
akamai/edgegrid/edgegrid.py
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
# Original author: Jonathan Landis <jlandis@akamai.com>
|
|
4
|
-
# Package maintainer: Akamai Developer Experience team <dl-devexp-eng@akamai.com>
|
|
5
|
-
#
|
|
6
|
-
# For more information visit https://developer.akamai.com
|
|
7
|
-
|
|
8
|
-
# Copyright 2021 Akamai Technologies, Inc. All Rights Reserved
|
|
9
|
-
#
|
|
10
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
11
|
-
# you may not use this file except in compliance with the License.
|
|
12
|
-
# You may obtain a copy of the License at
|
|
13
|
-
#
|
|
14
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
15
|
-
#
|
|
16
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
17
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
-
# See the License for the specific language governing permissions and
|
|
20
|
-
# limitations under the License.
|
|
1
|
+
# pylint: disable=too-many-arguments,missing-function-docstring
|
|
2
|
+
"""EdgeGrid requests Auth handler"""
|
|
21
3
|
|
|
22
4
|
import logging
|
|
23
5
|
import uuid
|
|
@@ -25,19 +7,13 @@ import hashlib
|
|
|
25
7
|
import hmac
|
|
26
8
|
import base64
|
|
27
9
|
import re
|
|
28
|
-
import sys
|
|
29
10
|
import os
|
|
30
|
-
from requests.auth import AuthBase
|
|
31
11
|
from time import gmtime, strftime
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
from requests.auth import AuthBase
|
|
32
15
|
|
|
33
|
-
|
|
34
|
-
# python3
|
|
35
|
-
from urllib.parse import urlparse
|
|
36
|
-
else:
|
|
37
|
-
# python2.7
|
|
38
|
-
from urlparse import urlparse
|
|
39
|
-
import urllib3.contrib.pyopenssl
|
|
40
|
-
urllib3.contrib.pyopenssl.inject_into_urllib3()
|
|
16
|
+
from .edgerc import EdgeRc
|
|
41
17
|
|
|
42
18
|
logger = logging.getLogger(__name__)
|
|
43
19
|
|
|
@@ -45,6 +21,7 @@ __all__ = ['EdgeGridAuth']
|
|
|
45
21
|
|
|
46
22
|
|
|
47
23
|
def eg_timestamp():
|
|
24
|
+
"""Generates EdgeGrid compatible timestamp"""
|
|
48
25
|
return strftime('%Y%m%dT%H:%M:%S+0000', gmtime())
|
|
49
26
|
|
|
50
27
|
|
|
@@ -61,26 +38,84 @@ def base64_hmac_sha256(data, key):
|
|
|
61
38
|
).decode('utf8')
|
|
62
39
|
|
|
63
40
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return multipart_body
|
|
41
|
+
def base64_sha256(data):
|
|
42
|
+
digest = hashlib.sha256(data).digest()
|
|
43
|
+
return base64.b64encode(digest).decode('utf8')
|
|
68
44
|
|
|
69
45
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
"""
|
|
73
61
|
try:
|
|
74
|
-
|
|
75
|
-
except
|
|
76
|
-
|
|
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
|
|
77
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'))
|
|
78
108
|
|
|
79
|
-
def get_prepared_body_len(prepared_body):
|
|
80
109
|
try:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
84
119
|
|
|
85
120
|
|
|
86
121
|
class EdgeGridAuth(AuthBase):
|
|
@@ -99,7 +134,7 @@ class EdgeGridAuth(AuthBase):
|
|
|
99
134
|
"""
|
|
100
135
|
|
|
101
136
|
def __init__(self, client_token, client_secret, access_token,
|
|
102
|
-
headers_to_sign=(), max_body=131072):
|
|
137
|
+
*, headers_to_sign=(), max_body=131072):
|
|
103
138
|
"""Initialize authentication using the given parameters from the Akamai OPEN APIs
|
|
104
139
|
Interface:
|
|
105
140
|
|
|
@@ -112,12 +147,13 @@ class EdgeGridAuth(AuthBase):
|
|
|
112
147
|
specific APIs. (default 131072)
|
|
113
148
|
|
|
114
149
|
"""
|
|
150
|
+
# pylint: disable=invalid-name
|
|
115
151
|
self.ah = EdgeGridAuthHeaders(
|
|
116
152
|
client_token,
|
|
117
153
|
client_secret,
|
|
118
154
|
access_token,
|
|
119
|
-
headers_to_sign,
|
|
120
|
-
max_body
|
|
155
|
+
headers_to_sign=headers_to_sign,
|
|
156
|
+
max_body=max_body
|
|
121
157
|
)
|
|
122
158
|
|
|
123
159
|
@staticmethod
|
|
@@ -131,21 +167,20 @@ class EdgeGridAuth(AuthBase):
|
|
|
131
167
|
default is 'default')
|
|
132
168
|
|
|
133
169
|
"""
|
|
134
|
-
from .edgerc import EdgeRc
|
|
135
170
|
if isinstance(rcinput, EdgeRc):
|
|
136
|
-
|
|
171
|
+
edgerc = rcinput
|
|
137
172
|
else:
|
|
138
|
-
|
|
173
|
+
edgerc = EdgeRc(rcinput)
|
|
139
174
|
|
|
140
175
|
return EdgeGridAuth(
|
|
141
|
-
client_token=
|
|
142
|
-
client_secret=
|
|
143
|
-
access_token=
|
|
144
|
-
headers_to_sign=
|
|
145
|
-
max_body=
|
|
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')
|
|
146
181
|
)
|
|
147
182
|
|
|
148
|
-
def handle_redirect(self, res, **
|
|
183
|
+
def handle_redirect(self, res, **_):
|
|
149
184
|
if res.is_redirect:
|
|
150
185
|
redirect_location = res.headers['location']
|
|
151
186
|
|
|
@@ -154,34 +189,24 @@ class EdgeGridAuth(AuthBase):
|
|
|
154
189
|
request_to_sign.url = redirect_location
|
|
155
190
|
|
|
156
191
|
res.request.headers['Authorization'] = self.ah.make_auth_header(
|
|
157
|
-
request_to_sign
|
|
158
|
-
request_to_sign.headers,
|
|
159
|
-
request_to_sign.method,
|
|
160
|
-
request_to_sign.body,
|
|
161
|
-
eg_timestamp(),
|
|
162
|
-
new_nonce()
|
|
163
|
-
)
|
|
192
|
+
request_to_sign, eg_timestamp(), new_nonce())
|
|
164
193
|
|
|
165
194
|
def __call__(self, r):
|
|
166
195
|
timestamp = eg_timestamp()
|
|
167
196
|
nonce = new_nonce()
|
|
168
197
|
|
|
169
|
-
r.headers['Authorization'] = self.ah.make_auth_header(
|
|
170
|
-
r.url,
|
|
171
|
-
r.headers,
|
|
172
|
-
r.method,
|
|
173
|
-
r.body,
|
|
174
|
-
timestamp,
|
|
175
|
-
nonce
|
|
176
|
-
)
|
|
198
|
+
r.headers['Authorization'] = self.ah.make_auth_header(r, timestamp, nonce)
|
|
177
199
|
r.register_hook('response', self.handle_redirect)
|
|
178
200
|
return r
|
|
179
201
|
|
|
180
202
|
|
|
181
|
-
class EdgeGridAuthHeaders
|
|
182
|
-
|
|
203
|
+
class EdgeGridAuthHeaders:
|
|
204
|
+
"""
|
|
205
|
+
A class for preparing requests authentication headers needed for
|
|
206
|
+
Akamai {OPEN} EdgeGrid support.
|
|
207
|
+
"""
|
|
183
208
|
def __init__(self, client_token, client_secret, access_token,
|
|
184
|
-
headers_to_sign=(), max_body=131072):
|
|
209
|
+
*, headers_to_sign=(), max_body=131072):
|
|
185
210
|
self.client_token = client_token
|
|
186
211
|
self.client_secret = client_secret
|
|
187
212
|
self.access_token = access_token
|
|
@@ -197,38 +222,36 @@ class EdgeGridAuthHeaders():
|
|
|
197
222
|
spaces_re = re.compile('\\s+')
|
|
198
223
|
|
|
199
224
|
# note: r.headers is a case-insensitive dict and self.headers_to_sign
|
|
200
|
-
# should already be
|
|
225
|
+
# should already be in lowercase at this point
|
|
226
|
+
# pylint: disable=consider-using-f-string
|
|
201
227
|
return '\t'.join([
|
|
202
228
|
"%s:%s" % (h, spaces_re.sub(' ', headers[h].strip()))
|
|
203
229
|
for h in self.headers_to_sign if h in headers
|
|
204
230
|
])
|
|
205
231
|
|
|
206
232
|
def make_content_hash(self, body, method):
|
|
233
|
+
logger.debug("body is '%s'", body)
|
|
207
234
|
content_hash = ""
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if get_prepared_body_len(prepared_body) > self.max_body:
|
|
214
|
-
logger.debug(
|
|
215
|
-
"data length %d is larger than maximum %d",
|
|
216
|
-
get_prepared_body_len(prepared_body), self.max_body
|
|
217
|
-
)
|
|
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)
|
|
218
240
|
try:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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)
|
|
228
250
|
logger.debug("content hash is '%s'", content_hash)
|
|
229
251
|
return content_hash
|
|
230
252
|
|
|
231
|
-
|
|
253
|
+
@staticmethod
|
|
254
|
+
def get_header_versions(header=None):
|
|
232
255
|
if header is None:
|
|
233
256
|
header = {}
|
|
234
257
|
|
|
@@ -242,7 +265,7 @@ class EdgeGridAuthHeaders():
|
|
|
242
265
|
akamai_cli_command_version = os.getenv('AKAMAI_CLI_COMMAND_VERSION')
|
|
243
266
|
if akamai_cli_command and akamai_cli_command_version:
|
|
244
267
|
version_header += " AkamaiCLI-" + akamai_cli_command + \
|
|
245
|
-
|
|
268
|
+
"/" + akamai_cli_command_version
|
|
246
269
|
|
|
247
270
|
if version_header != '':
|
|
248
271
|
if 'User-Agent' not in header:
|
|
@@ -252,37 +275,37 @@ class EdgeGridAuthHeaders():
|
|
|
252
275
|
|
|
253
276
|
return header
|
|
254
277
|
|
|
255
|
-
def make_data_to_sign(self,
|
|
256
|
-
parsed_url = urlparse(url)
|
|
278
|
+
def make_data_to_sign(self, request, auth_header):
|
|
279
|
+
parsed_url = urlparse(request.url)
|
|
257
280
|
|
|
258
|
-
if headers.get('Host', False):
|
|
259
|
-
netloc = headers['Host']
|
|
281
|
+
if request.headers.get('Host', False):
|
|
282
|
+
netloc = request.headers['Host']
|
|
260
283
|
else:
|
|
261
284
|
netloc = parsed_url.netloc
|
|
262
285
|
|
|
263
|
-
self.get_header_versions(headers)
|
|
286
|
+
self.get_header_versions(request.headers)
|
|
264
287
|
|
|
265
288
|
data_to_sign = '\t'.join([
|
|
266
|
-
method,
|
|
289
|
+
request.method,
|
|
267
290
|
parsed_url.scheme,
|
|
268
291
|
netloc,
|
|
269
292
|
# Note: relative URL constraints are handled by requests when it sets up 'r'
|
|
270
293
|
parsed_url.path + (';' + parsed_url.params if parsed_url.params else "") +
|
|
271
294
|
('?' + parsed_url.query if parsed_url.query else ""),
|
|
272
|
-
self.canonicalize_headers(headers),
|
|
273
|
-
self.make_content_hash(body or '', method),
|
|
295
|
+
self.canonicalize_headers(request.headers),
|
|
296
|
+
self.make_content_hash(request.body or '', request.method),
|
|
274
297
|
auth_header
|
|
275
298
|
])
|
|
276
299
|
logger.debug('data to sign: %s', '\\t'.join(data_to_sign.split('\t')))
|
|
277
300
|
return data_to_sign
|
|
278
301
|
|
|
279
|
-
def sign_request(self,
|
|
302
|
+
def sign_request(self, request, timestamp, auth_header):
|
|
280
303
|
return base64_hmac_sha256(
|
|
281
|
-
self.make_data_to_sign(
|
|
304
|
+
self.make_data_to_sign(request, auth_header),
|
|
282
305
|
self.make_signing_key(timestamp)
|
|
283
306
|
)
|
|
284
307
|
|
|
285
|
-
def make_auth_header(self,
|
|
308
|
+
def make_auth_header(self, request, timestamp, nonce):
|
|
286
309
|
kvps = [
|
|
287
310
|
('client_token', self.client_token),
|
|
288
311
|
('access_token', self.access_token),
|
|
@@ -290,11 +313,11 @@ class EdgeGridAuthHeaders():
|
|
|
290
313
|
('nonce', nonce),
|
|
291
314
|
]
|
|
292
315
|
auth_header = "EG1-HMAC-SHA256 " + \
|
|
293
|
-
';'.join(["
|
|
316
|
+
';'.join([f"{k}={v}" for k, v in kvps]) + ';'
|
|
294
317
|
logger.debug('unsigned authorization header: %s', auth_header)
|
|
295
318
|
|
|
296
319
|
signed_auth_header = auth_header + \
|
|
297
|
-
'signature=' + self.sign_request(
|
|
320
|
+
'signature=' + self.sign_request(request, timestamp, auth_header)
|
|
298
321
|
|
|
299
322
|
logger.debug('signed authorization header: %s', signed_auth_header)
|
|
300
323
|
return signed_auth_header
|
akamai/edgegrid/edgerc.py
CHANGED
|
@@ -1,35 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
# Copyright 2021 Akamai Technologies, Inc. All Rights Reserved
|
|
4
|
-
#
|
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
# you may not use this file except in compliance with the License.
|
|
7
|
-
# You may obtain a copy of the License at
|
|
8
|
-
#
|
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
#
|
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
# See the License for the specific language governing permissions and
|
|
15
|
-
# limitations under the License.
|
|
1
|
+
"""Support for .edgerc file format"""
|
|
16
2
|
|
|
17
3
|
import logging
|
|
18
|
-
import
|
|
4
|
+
from configparser import ConfigParser
|
|
19
5
|
from os.path import expanduser
|
|
20
6
|
|
|
21
|
-
if sys.version_info[0] >= 3:
|
|
22
|
-
# python3
|
|
23
|
-
from configparser import ConfigParser
|
|
24
|
-
else:
|
|
25
|
-
# python2.7
|
|
26
|
-
from ConfigParser import ConfigParser
|
|
27
|
-
|
|
28
|
-
|
|
29
7
|
logger = logging.getLogger(__name__)
|
|
30
8
|
|
|
31
9
|
|
|
32
10
|
class EdgeRc(ConfigParser):
|
|
11
|
+
"""Class for managing .edgerc files"""
|
|
33
12
|
def __init__(self, filename):
|
|
34
13
|
ConfigParser.__init__(self,
|
|
35
14
|
{'client_token': '',
|
|
@@ -50,11 +29,9 @@ class EdgeRc(ConfigParser):
|
|
|
50
29
|
|
|
51
30
|
def getlist(self, section, option):
|
|
52
31
|
"""
|
|
53
|
-
returns the named option as a list, splitting the original value
|
|
54
|
-
by ','
|
|
32
|
+
returns the named option as a list, splitting the original value by ','
|
|
55
33
|
"""
|
|
56
34
|
value = self.get(section, option)
|
|
57
35
|
if value:
|
|
58
36
|
return value.split(',')
|
|
59
|
-
|
|
60
|
-
return None
|
|
37
|
+
return None
|
|
@@ -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
|