diaspora-event-sdk 0.2.6__py3-none-any.whl → 0.2.8__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.
@@ -0,0 +1,271 @@
1
+ # Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
2
+ # Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
5
+ # may not use this file except in compliance with the License. A copy of
6
+ # the License is located at
7
+ #
8
+ # http://aws.amazon.com/apache2.0/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is
11
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
+ # ANY KIND, either express or implied. See the License for the specific
13
+ # language governing permissions and limitations under the License.
14
+ # import functools
15
+ import logging
16
+ from collections.abc import Mapping
17
+
18
+ from .compat import (
19
+ HTTPHeaders,
20
+ MutableMapping,
21
+ urlencode,
22
+ urlparse,
23
+ )
24
+ from .exceptions import UnseekableStreamError
25
+ from .utils import determine_content_length
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class AWSRequestPreparer:
31
+ """
32
+ This class performs preparation on AWSRequest objects similar to that of
33
+ the PreparedRequest class does in the requests library. However, the logic
34
+ has been boiled down to meet the specific use cases in botocore. Of note
35
+ there are the following differences:
36
+ This class does not heavily prepare the URL. Requests performed many
37
+ validations and corrections to ensure the URL is properly formatted.
38
+ Botocore either performs these validations elsewhere or otherwise
39
+ consistently provides well formatted URLs.
40
+
41
+ This class does not heavily prepare the body. Body preperation is
42
+ simple and supports only the cases that we document: bytes and
43
+ file-like objects to determine the content-length. This will also
44
+ additionally prepare a body that is a dict to be url encoded params
45
+ string as some signers rely on this. Finally, this class does not
46
+ support multipart file uploads.
47
+
48
+ This class does not prepare the method, auth or cookies.
49
+ """
50
+
51
+ def prepare(self, original):
52
+ method = original.method
53
+ url = self._prepare_url(original)
54
+ body = self._prepare_body(original)
55
+ headers = self._prepare_headers(original, body)
56
+ stream_output = original.stream_output
57
+
58
+ return AWSPreparedRequest(method, url, headers, body, stream_output)
59
+
60
+ def _prepare_url(self, original):
61
+ url = original.url
62
+ if original.params:
63
+ url_parts = urlparse(url)
64
+ delim = '&' if url_parts.query else '?'
65
+ if isinstance(original.params, Mapping):
66
+ params_to_encode = list(original.params.items())
67
+ else:
68
+ params_to_encode = original.params
69
+ params = urlencode(params_to_encode, doseq=True)
70
+ url = delim.join((url, params))
71
+ return url
72
+
73
+ def _prepare_headers(self, original, prepared_body=None):
74
+ headers = HeadersDict(original.headers.items())
75
+
76
+ # If the transfer encoding or content length is already set, use that
77
+ if 'Transfer-Encoding' in headers or 'Content-Length' in headers:
78
+ return headers
79
+
80
+ # Ensure we set the content length when it is expected
81
+ if original.method not in ('GET', 'HEAD', 'OPTIONS'):
82
+ length = self._determine_content_length(prepared_body)
83
+ if length is not None:
84
+ headers['Content-Length'] = str(length)
85
+ else:
86
+ # Failed to determine content length, using chunked
87
+ # NOTE: This shouldn't ever happen in practice
88
+ body_type = type(prepared_body)
89
+ logger.debug('Failed to determine length of %s', body_type)
90
+ headers['Transfer-Encoding'] = 'chunked'
91
+
92
+ return headers
93
+
94
+ def _to_utf8(self, item):
95
+ key, value = item
96
+ if isinstance(key, str):
97
+ key = key.encode('utf-8')
98
+ if isinstance(value, str):
99
+ value = value.encode('utf-8')
100
+ return key, value
101
+
102
+ def _prepare_body(self, original):
103
+ """Prepares the given HTTP body data."""
104
+ body = original.data
105
+ if body == b'':
106
+ body = None
107
+
108
+ if isinstance(body, dict):
109
+ params = [self._to_utf8(item) for item in body.items()]
110
+ body = urlencode(params, doseq=True)
111
+
112
+ return body
113
+
114
+ def _determine_content_length(self, body):
115
+ return determine_content_length(body)
116
+
117
+
118
+ class AWSRequest:
119
+ """Represents the elements of an HTTP request.
120
+
121
+ This class was originally inspired by requests.models.Request, but has been
122
+ boiled down to meet the specific use cases in botocore. That being said this
123
+ class (even in requests) is effectively a named-tuple.
124
+ """
125
+
126
+ _REQUEST_PREPARER_CLS = AWSRequestPreparer
127
+
128
+ def __init__(
129
+ self,
130
+ method=None,
131
+ url=None,
132
+ headers=None,
133
+ data=None,
134
+ params=None,
135
+ auth_path=None,
136
+ stream_output=False,
137
+ ):
138
+ self._request_preparer = self._REQUEST_PREPARER_CLS()
139
+
140
+ # Default empty dicts for dict params.
141
+ params = {} if params is None else params
142
+
143
+ self.method = method
144
+ self.url = url
145
+ self.headers = HTTPHeaders()
146
+ self.data = data
147
+ self.params = params
148
+ self.auth_path = auth_path
149
+ self.stream_output = stream_output
150
+
151
+ if headers is not None:
152
+ for key, value in headers.items():
153
+ self.headers[key] = value
154
+
155
+ # This is a dictionary to hold information that is used when
156
+ # processing the request. What is inside of ``context`` is open-ended.
157
+ # For example, it may have a timestamp key that is used for holding
158
+ # what the timestamp is when signing the request. Note that none
159
+ # of the information that is inside of ``context`` is directly
160
+ # sent over the wire; the information is only used to assist in
161
+ # creating what is sent over the wire.
162
+ self.context = {}
163
+
164
+ def prepare(self):
165
+ """Constructs a :class:`AWSPreparedRequest <AWSPreparedRequest>`."""
166
+ return self._request_preparer.prepare(self)
167
+
168
+ @property
169
+ def body(self):
170
+ body = self.prepare().body
171
+ if isinstance(body, str):
172
+ body = body.encode('utf-8')
173
+ return body
174
+
175
+
176
+ class AWSPreparedRequest:
177
+ """A data class representing a finalized request to be sent over the wire.
178
+
179
+ Requests at this stage should be treated as final, and the properties of
180
+ the request should not be modified.
181
+
182
+ :ivar method: The HTTP Method
183
+ :ivar url: The full url
184
+ :ivar headers: The HTTP headers to send.
185
+ :ivar body: The HTTP body.
186
+ :ivar stream_output: If the response for this request should be streamed.
187
+ """
188
+
189
+ def __init__(self, method, url, headers, body, stream_output):
190
+ self.method = method
191
+ self.url = url
192
+ self.headers = headers
193
+ self.body = body
194
+ self.stream_output = stream_output
195
+
196
+ def __repr__(self):
197
+ fmt = (
198
+ '<AWSPreparedRequest stream_output=%s, method=%s, url=%s, '
199
+ 'headers=%s>'
200
+ )
201
+ return fmt % (self.stream_output, self.method, self.url, self.headers)
202
+
203
+ def reset_stream(self):
204
+ """Resets the streaming body to it's initial position.
205
+
206
+ If the request contains a streaming body (a streamable file-like object)
207
+ seek to the object's initial position to ensure the entire contents of
208
+ the object is sent. This is a no-op for static bytes-like body types.
209
+ """
210
+ # Trying to reset a stream when there is a no stream will
211
+ # just immediately return. It's not an error, it will produce
212
+ # the same result as if we had actually reset the stream (we'll send
213
+ # the entire body contents again if we need to).
214
+ # Same case if the body is a string/bytes/bytearray type.
215
+
216
+ non_seekable_types = (bytes, str, bytearray)
217
+ if self.body is None or isinstance(self.body, non_seekable_types):
218
+ return
219
+ try:
220
+ logger.debug("Rewinding stream: %s", self.body)
221
+ self.body.seek(0)
222
+ except Exception as e:
223
+ logger.debug("Unable to rewind stream: %s", e)
224
+ raise UnseekableStreamError(stream_object=self.body)
225
+
226
+
227
+ class _HeaderKey:
228
+ def __init__(self, key):
229
+ self._key = key
230
+ self._lower = key.lower()
231
+
232
+ def __hash__(self):
233
+ return hash(self._lower)
234
+
235
+ def __eq__(self, other):
236
+ return isinstance(other, _HeaderKey) and self._lower == other._lower
237
+
238
+ def __str__(self):
239
+ return self._key
240
+
241
+ def __repr__(self):
242
+ return repr(self._key)
243
+
244
+
245
+ class HeadersDict(MutableMapping):
246
+ """A case-insenseitive dictionary to represent HTTP headers."""
247
+
248
+ def __init__(self, *args, **kwargs):
249
+ self._dict = {}
250
+ self.update(*args, **kwargs)
251
+
252
+ def __setitem__(self, key, value):
253
+ self._dict[_HeaderKey(key)] = value
254
+
255
+ def __getitem__(self, key):
256
+ return self._dict[_HeaderKey(key)]
257
+
258
+ def __delitem__(self, key):
259
+ del self._dict[_HeaderKey(key)]
260
+
261
+ def __iter__(self):
262
+ return (str(key) for key in self._dict)
263
+
264
+ def __len__(self):
265
+ return len(self._dict)
266
+
267
+ def __repr__(self):
268
+ return repr(self._dict)
269
+
270
+ def copy(self):
271
+ return HeadersDict(self.items())
@@ -0,0 +1,352 @@
1
+ # Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ from urllib.parse import (
15
+ quote,
16
+ urlencode,
17
+ unquote,
18
+ unquote_plus,
19
+ urlparse,
20
+ urlsplit,
21
+ urlunsplit,
22
+ urljoin,
23
+ parse_qsl,
24
+ parse_qs,
25
+ )
26
+ import json
27
+ from itertools import zip_longest
28
+ from email.utils import formatdate
29
+ from base64 import encodebytes
30
+ from io import IOBase as _IOBase
31
+ from http.client import HTTPResponse
32
+ import copy
33
+ import datetime
34
+ import sys
35
+ import inspect
36
+ import warnings
37
+ import hashlib
38
+ from http.client import HTTPMessage
39
+ import logging
40
+ import shlex
41
+ import re
42
+ import os
43
+ from collections import OrderedDict
44
+ from collections.abc import MutableMapping
45
+ from math import floor
46
+
47
+ # from dateutil.tz import tzlocal
48
+ # from urllib3 import exceptions
49
+
50
+ from .exceptions import MD5UnavailableError
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ class HTTPHeaders(HTTPMessage):
56
+ pass
57
+
58
+
59
+ file_type = _IOBase
60
+ zip = zip
61
+
62
+ # In python3, unquote takes a str() object, url decodes it,
63
+ # then takes the bytestring and decodes it to utf-8.
64
+ unquote_str = unquote_plus
65
+
66
+
67
+ def set_socket_timeout(http_response, timeout):
68
+ """Set the timeout of the socket from an HTTPResponse.
69
+
70
+ :param http_response: An instance of ``httplib.HTTPResponse``
71
+
72
+ """
73
+ http_response._fp.fp.raw._sock.settimeout(timeout)
74
+
75
+
76
+ def accepts_kwargs(func):
77
+ # In python3.4.1, there's backwards incompatible
78
+ # changes when using getargspec with functools.partials.
79
+ return inspect.getfullargspec(func)[2]
80
+
81
+
82
+ def ensure_unicode(s, encoding=None, errors=None):
83
+ # NOOP in Python 3, because every string is already unicode
84
+ return s
85
+
86
+
87
+ def ensure_bytes(s, encoding='utf-8', errors='strict'):
88
+ if isinstance(s, str):
89
+ return s.encode(encoding, errors)
90
+ if isinstance(s, bytes):
91
+ return s
92
+ raise ValueError(f"Expected str or bytes, received {type(s)}.")
93
+
94
+
95
+ try:
96
+ import xml.etree.cElementTree as ETree
97
+ except ImportError:
98
+ # cElementTree does not exist from Python3.9+
99
+ import xml.etree.ElementTree as ETree
100
+ XMLParseError = ETree.ParseError
101
+
102
+
103
+ # def filter_ssl_warnings():
104
+ # # Ignore warnings related to SNI as it is not being used in validations.
105
+ # warnings.filterwarnings(
106
+ # 'ignore',
107
+ # message="A true SSLContext object is not available.*",
108
+ # category=exceptions.InsecurePlatformWarning,
109
+ # module=r".*urllib3\.util\.ssl_",
110
+ # )
111
+
112
+
113
+ @classmethod
114
+ def from_dict(cls, d):
115
+ new_instance = cls()
116
+ for key, value in d.items():
117
+ new_instance[key] = value
118
+ return new_instance
119
+
120
+
121
+ @classmethod
122
+ def from_pairs(cls, pairs):
123
+ new_instance = cls()
124
+ for key, value in pairs:
125
+ new_instance[key] = value
126
+ return new_instance
127
+
128
+
129
+ HTTPHeaders.from_dict = from_dict
130
+ HTTPHeaders.from_pairs = from_pairs
131
+
132
+
133
+ def copy_kwargs(kwargs):
134
+ """
135
+ This used to be a compat shim for 2.6 but is now just an alias.
136
+ """
137
+ copy_kwargs = copy.copy(kwargs)
138
+ return copy_kwargs
139
+
140
+
141
+ def total_seconds(delta):
142
+ """
143
+ Returns the total seconds in a ``datetime.timedelta``.
144
+
145
+ This used to be a compat shim for 2.6 but is now just an alias.
146
+
147
+ :param delta: The timedelta object
148
+ :type delta: ``datetime.timedelta``
149
+ """
150
+ return delta.total_seconds()
151
+
152
+
153
+ # Checks to see if md5 is available on this system. A given system might not
154
+ # have access to it for various reasons, such as FIPS mode being enabled.
155
+ try:
156
+ hashlib.md5()
157
+ MD5_AVAILABLE = True
158
+ except ValueError:
159
+ MD5_AVAILABLE = False
160
+
161
+
162
+ def get_md5(*args, **kwargs):
163
+ """
164
+ Attempts to get an md5 hashing object.
165
+
166
+ :param args: Args to pass to the MD5 constructor
167
+ :param kwargs: Key word arguments to pass to the MD5 constructor
168
+ :return: An MD5 hashing object if available. If it is unavailable, None
169
+ is returned if raise_error_if_unavailable is set to False.
170
+ """
171
+ if MD5_AVAILABLE:
172
+ return hashlib.md5(*args, **kwargs)
173
+ else:
174
+ raise MD5UnavailableError()
175
+
176
+
177
+ def compat_shell_split(s, platform=None):
178
+ if platform is None:
179
+ platform = sys.platform
180
+
181
+ if platform == "win32":
182
+ return _windows_shell_split(s)
183
+ else:
184
+ return shlex.split(s)
185
+
186
+
187
+ def _windows_shell_split(s):
188
+ """Splits up a windows command as the built-in command parser would.
189
+
190
+ Windows has potentially bizarre rules depending on where you look. When
191
+ spawning a process via the Windows C runtime (which is what python does
192
+ when you call popen) the rules are as follows:
193
+
194
+ https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
195
+
196
+ To summarize:
197
+
198
+ * Only space and tab are valid delimiters
199
+ * Double quotes are the only valid quotes
200
+ * Backslash is interpreted literally unless it is part of a chain that
201
+ leads up to a double quote. Then the backslashes escape the backslashes,
202
+ and if there is an odd number the final backslash escapes the quote.
203
+
204
+ :param s: The command string to split up into parts.
205
+ :return: A list of command components.
206
+ """
207
+ if not s:
208
+ return []
209
+
210
+ components = []
211
+ buff = []
212
+ is_quoted = False
213
+ num_backslashes = 0
214
+ for character in s:
215
+ if character == '\\':
216
+ # We can't simply append backslashes because we don't know if
217
+ # they are being used as escape characters or not. Instead we
218
+ # keep track of how many we've encountered and handle them when
219
+ # we encounter a different character.
220
+ num_backslashes += 1
221
+ elif character == '"':
222
+ if num_backslashes > 0:
223
+ # The backslashes are in a chain leading up to a double
224
+ # quote, so they are escaping each other.
225
+ buff.append('\\' * int(floor(num_backslashes / 2)))
226
+ remainder = num_backslashes % 2
227
+ num_backslashes = 0
228
+ if remainder == 1:
229
+ # The number of backslashes is uneven, so they are also
230
+ # escaping the double quote, so it needs to be added to
231
+ # the current component buffer.
232
+ buff.append('"')
233
+ continue
234
+
235
+ # We've encountered a double quote that is not escaped,
236
+ # so we toggle is_quoted.
237
+ is_quoted = not is_quoted
238
+
239
+ # If there are quotes, then we may want an empty string. To be
240
+ # safe, we add an empty string to the buffer so that we make
241
+ # sure it sticks around if there's nothing else between quotes.
242
+ # If there is other stuff between quotes, the empty string will
243
+ # disappear during the joining process.
244
+ buff.append('')
245
+ elif character in [' ', '\t'] and not is_quoted:
246
+ # Since the backslashes aren't leading up to a quote, we put in
247
+ # the exact number of backslashes.
248
+ if num_backslashes > 0:
249
+ buff.append('\\' * num_backslashes)
250
+ num_backslashes = 0
251
+
252
+ # Excess whitespace is ignored, so only add the components list
253
+ # if there is anything in the buffer.
254
+ if buff:
255
+ components.append(''.join(buff))
256
+ buff = []
257
+ else:
258
+ # Since the backslashes aren't leading up to a quote, we put in
259
+ # the exact number of backslashes.
260
+ if num_backslashes > 0:
261
+ buff.append('\\' * num_backslashes)
262
+ num_backslashes = 0
263
+ buff.append(character)
264
+
265
+ # Quotes must be terminated.
266
+ if is_quoted:
267
+ raise ValueError(f"No closing quotation in string: {s}")
268
+
269
+ # There may be some leftover backslashes, so we need to add them in.
270
+ # There's no quote so we add the exact number.
271
+ if num_backslashes > 0:
272
+ buff.append('\\' * num_backslashes)
273
+
274
+ # Add the final component in if there is anything in the buffer.
275
+ if buff:
276
+ components.append(''.join(buff))
277
+
278
+ return components
279
+
280
+
281
+ # def get_tzinfo_options():
282
+ # # Due to dateutil/dateutil#197, Windows may fail to parse times in the past
283
+ # # with the system clock. We can alternatively fallback to tzwininfo when
284
+ # # this happens, which will get time info from the Windows registry.
285
+ # if sys.platform == 'win32':
286
+ # from dateutil.tz import tzwinlocal
287
+
288
+ # return (tzlocal, tzwinlocal)
289
+ # else:
290
+ # return (tzlocal,)
291
+
292
+
293
+ # Detect if CRT is available for use
294
+ try:
295
+ import awscrt.auth
296
+
297
+ # Allow user opt-out if needed
298
+ disabled = os.environ.get('BOTO_DISABLE_CRT', "false")
299
+ HAS_CRT = not disabled.lower() == 'true'
300
+ except ImportError:
301
+ HAS_CRT = False
302
+
303
+
304
+ ########################################################
305
+ # urllib3 compat backports #
306
+ ########################################################
307
+
308
+ # Vendoring IPv6 validation regex patterns from urllib3
309
+ # https://github.com/urllib3/urllib3/blob/7e856c0/src/urllib3/util/url.py
310
+ IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"
311
+ IPV4_RE = re.compile("^" + IPV4_PAT + "$")
312
+ HEX_PAT = "[0-9A-Fa-f]{1,4}"
313
+ LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT)
314
+ _subs = {"hex": HEX_PAT, "ls32": LS32_PAT}
315
+ _variations = [
316
+ # 6( h16 ":" ) ls32
317
+ "(?:%(hex)s:){6}%(ls32)s",
318
+ # "::" 5( h16 ":" ) ls32
319
+ "::(?:%(hex)s:){5}%(ls32)s",
320
+ # [ h16 ] "::" 4( h16 ":" ) ls32
321
+ "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s",
322
+ # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
323
+ "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s",
324
+ # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
325
+ "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s",
326
+ # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
327
+ "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s",
328
+ # [ *4( h16 ":" ) h16 ] "::" ls32
329
+ "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s",
330
+ # [ *5( h16 ":" ) h16 ] "::" h16
331
+ "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s",
332
+ # [ *6( h16 ":" ) h16 ] "::"
333
+ "(?:(?:%(hex)s:){0,6}%(hex)s)?::",
334
+ ]
335
+
336
+ UNRESERVED_PAT = (
337
+ r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~"
338
+ )
339
+ IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")"
340
+ ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+"
341
+ IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]"
342
+ IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$")
343
+
344
+ # These are the characters that are stripped by post-bpo-43882 urlparse().
345
+ UNSAFE_URL_CHARS = frozenset('\t\r\n')
346
+
347
+ # Detect if gzip is available for use
348
+ try:
349
+ import gzip
350
+ HAS_GZIP = True
351
+ except ImportError:
352
+ HAS_GZIP = False
@@ -0,0 +1,66 @@
1
+ # Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
2
+ # Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
5
+ # may not use this file except in compliance with the License. A copy of
6
+ # the License is located at
7
+ #
8
+ # http://aws.amazon.com/apache2.0/
9
+ #
10
+ # or in the "license" file accompanying this file. This file is
11
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
+ # ANY KIND, either express or implied. See the License for the specific
13
+ # language governing permissions and limitations under the License.
14
+
15
+ import logging
16
+ from collections import namedtuple
17
+
18
+ from .compat import ensure_unicode
19
+
20
+ logger = logging.getLogger(__name__)
21
+ ReadOnlyCredentials = namedtuple(
22
+ 'ReadOnlyCredentials', ['access_key', 'secret_key', 'token']
23
+ )
24
+
25
+ _DEFAULT_MANDATORY_REFRESH_TIMEOUT = 10 * 60 # 10 min
26
+ _DEFAULT_ADVISORY_REFRESH_TIMEOUT = 15 * 60 # 15 min
27
+
28
+
29
+ class Credentials:
30
+ """
31
+ Holds the credentials needed to authenticate requests.
32
+
33
+ :param str access_key: The access key part of the credentials.
34
+ :param str secret_key: The secret key part of the credentials.
35
+ :param str token: The security token, valid only for session credentials.
36
+ :param str method: A string which identifies where the credentials
37
+ were found.
38
+ """
39
+
40
+ def __init__(self, access_key, secret_key, token=None, method=None):
41
+ self.access_key = access_key
42
+ self.secret_key = secret_key
43
+ self.token = token
44
+
45
+ if method is None:
46
+ method = 'explicit'
47
+ self.method = method
48
+
49
+ self._normalize()
50
+
51
+ def _normalize(self):
52
+ # Keys would sometimes (accidentally) contain non-ascii characters.
53
+ # It would cause a confusing UnicodeDecodeError in Python 2.
54
+ # We explicitly convert them into unicode to avoid such error.
55
+ #
56
+ # Eventually the service will decide whether to accept the credential.
57
+ # This also complies with the behavior in Python 3.
58
+ # self.access_key = botocore.compat.ensure_unicode(self.access_key)
59
+ # self.secret_key = botocore.compat.ensure_unicode(self.secret_key)
60
+ self.access_key = ensure_unicode(self.access_key)
61
+ self.secret_key = ensure_unicode(self.secret_key)
62
+
63
+ def get_frozen_credentials(self):
64
+ return ReadOnlyCredentials(
65
+ self.access_key, self.secret_key, self.token
66
+ )