baiducloud-python-sdk-core 0.0.1__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.
- baiducloud_python_sdk_core/__init__.py +24 -0
- baiducloud_python_sdk_core/abstract_model.py +33 -0
- baiducloud_python_sdk_core/auth/__init__.py +0 -0
- baiducloud_python_sdk_core/auth/bce_credentials.py +27 -0
- baiducloud_python_sdk_core/auth/bce_v1_signer.py +92 -0
- baiducloud_python_sdk_core/bce_base_client.py +90 -0
- baiducloud_python_sdk_core/bce_client_configuration.py +87 -0
- baiducloud_python_sdk_core/bce_response.py +61 -0
- baiducloud_python_sdk_core/compat.py +125 -0
- baiducloud_python_sdk_core/exception.py +52 -0
- baiducloud_python_sdk_core/http/__init__.py +0 -0
- baiducloud_python_sdk_core/http/bce_http_client.py +280 -0
- baiducloud_python_sdk_core/http/handler.py +76 -0
- baiducloud_python_sdk_core/http/http_content_types.py +18 -0
- baiducloud_python_sdk_core/http/http_headers.py +122 -0
- baiducloud_python_sdk_core/http/http_methods.py +25 -0
- baiducloud_python_sdk_core/protocol.py +25 -0
- baiducloud_python_sdk_core/region.py +27 -0
- baiducloud_python_sdk_core/retry/__init__.py +0 -0
- baiducloud_python_sdk_core/retry/retry_policy.py +133 -0
- baiducloud_python_sdk_core/utils.py +762 -0
- baiducloud_python_sdk_core-0.0.1.dist-info/METADATA +85 -0
- baiducloud_python_sdk_core-0.0.1.dist-info/RECORD +26 -0
- baiducloud_python_sdk_core-0.0.1.dist-info/WHEEL +5 -0
- baiducloud_python_sdk_core-0.0.1.dist-info/licenses/LICENSE +177 -0
- baiducloud_python_sdk_core-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
# Copyright 2014 Baidu, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
4
|
+
# except in compliance with the License. You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software distributed under the
|
|
9
|
+
# License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
|
10
|
+
# either express or implied. See the License for the specific language governing permissions
|
|
11
|
+
# and limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
This module provide some tools for bce client.
|
|
15
|
+
"""
|
|
16
|
+
# str() generator unicode,bytes() for ASCII
|
|
17
|
+
from __future__ import print_function
|
|
18
|
+
from __future__ import absolute_import
|
|
19
|
+
from builtins import str, bytes
|
|
20
|
+
from future.utils import iteritems, iterkeys, itervalues
|
|
21
|
+
from baiducloud_python_sdk_core import compat
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import datetime
|
|
26
|
+
import hashlib
|
|
27
|
+
import base64
|
|
28
|
+
import string
|
|
29
|
+
import sys
|
|
30
|
+
try:
|
|
31
|
+
from urllib.parse import urlparse
|
|
32
|
+
except ImportError:
|
|
33
|
+
from urlparse import urlparse
|
|
34
|
+
from Crypto.Cipher import AES
|
|
35
|
+
import baiducloud_python_sdk_core
|
|
36
|
+
from baiducloud_python_sdk_core.http import http_headers
|
|
37
|
+
from typing import Any, Dict, List, Union
|
|
38
|
+
import codecs
|
|
39
|
+
|
|
40
|
+
DEFAULT_CNAME_LIKE_LIST = [b".cdn.bcebos.com"]
|
|
41
|
+
DEFAULT_BOS_DOMAIN_SUFFIX = b'bcebos.com'
|
|
42
|
+
HTTP_PROTOCOL_HEAD = b'http'
|
|
43
|
+
|
|
44
|
+
def get_md5_from_fp(fp, offset=0, length=-1, buf_size=8192):
|
|
45
|
+
"""
|
|
46
|
+
Get MD5 from file by fp.
|
|
47
|
+
|
|
48
|
+
:type fp: FileIO
|
|
49
|
+
:param fp: None
|
|
50
|
+
|
|
51
|
+
:type offset: long
|
|
52
|
+
:param offset: None
|
|
53
|
+
|
|
54
|
+
:type length: long
|
|
55
|
+
:param length: None
|
|
56
|
+
=======================
|
|
57
|
+
:return:
|
|
58
|
+
**file_size, MD(encode by base64)**
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
origin_offset = fp.tell()
|
|
62
|
+
if offset:
|
|
63
|
+
fp.seek(offset)
|
|
64
|
+
md5 = hashlib.md5()
|
|
65
|
+
while True:
|
|
66
|
+
bytes_to_read = buf_size
|
|
67
|
+
if bytes_to_read > length > 0:
|
|
68
|
+
bytes_to_read = length
|
|
69
|
+
buf = fp.read(bytes_to_read)
|
|
70
|
+
if not buf:
|
|
71
|
+
break
|
|
72
|
+
md5.update(buf)
|
|
73
|
+
if length > 0:
|
|
74
|
+
length -= len(buf)
|
|
75
|
+
if length == 0:
|
|
76
|
+
break
|
|
77
|
+
fp.seek(origin_offset)
|
|
78
|
+
return base64.standard_b64encode(md5.digest())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_canonical_time(timestamp=0):
|
|
82
|
+
"""
|
|
83
|
+
Get cannonical time.
|
|
84
|
+
|
|
85
|
+
:type timestamp: int
|
|
86
|
+
:param timestamp: None
|
|
87
|
+
=======================
|
|
88
|
+
:return:
|
|
89
|
+
**string of canonical_time**
|
|
90
|
+
"""
|
|
91
|
+
if timestamp == 0:
|
|
92
|
+
utctime = datetime.datetime.utcnow()
|
|
93
|
+
else:
|
|
94
|
+
utctime = datetime.datetime.utcfromtimestamp(timestamp)
|
|
95
|
+
return b"%04d-%02d-%02dT%02d:%02d:%02dZ" % (
|
|
96
|
+
utctime.year, utctime.month, utctime.day,
|
|
97
|
+
utctime.hour, utctime.minute, utctime.second)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def is_ip(s):
|
|
101
|
+
"""
|
|
102
|
+
Check a string whether is a legal ip address.
|
|
103
|
+
|
|
104
|
+
:type s: string
|
|
105
|
+
:param s: None
|
|
106
|
+
=======================
|
|
107
|
+
:return:
|
|
108
|
+
**Boolean**
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
tmp_list = s.split(b':')
|
|
112
|
+
s = tmp_list[0]
|
|
113
|
+
if s == b'localhost':
|
|
114
|
+
return True
|
|
115
|
+
tmp_list = s.split(b'.')
|
|
116
|
+
if len(tmp_list) != 4:
|
|
117
|
+
return False
|
|
118
|
+
else:
|
|
119
|
+
for i in tmp_list:
|
|
120
|
+
if int(i) < 0 or int(i) > 255:
|
|
121
|
+
return False
|
|
122
|
+
except:
|
|
123
|
+
return False
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def convert_to_standard_string(input_string):
|
|
128
|
+
"""
|
|
129
|
+
Encode a string to utf-8.
|
|
130
|
+
|
|
131
|
+
:type input_string: string
|
|
132
|
+
:param input_string: None
|
|
133
|
+
=======================
|
|
134
|
+
:return:
|
|
135
|
+
**string**
|
|
136
|
+
"""
|
|
137
|
+
#if isinstance(input_string, str):
|
|
138
|
+
# return input_string.encode(baiducloud_python_sdk_core.DEFAULT_ENCODING)
|
|
139
|
+
#elif isinstance(input_string, bytes):
|
|
140
|
+
# return input_string
|
|
141
|
+
#else:
|
|
142
|
+
# return str(input_string).encode("utf-8")
|
|
143
|
+
return compat.convert_to_bytes(input_string)
|
|
144
|
+
|
|
145
|
+
def convert_header2map(header_list):
|
|
146
|
+
"""
|
|
147
|
+
Transfer a header list to dict
|
|
148
|
+
|
|
149
|
+
:type s: list
|
|
150
|
+
:param s: None
|
|
151
|
+
=======================
|
|
152
|
+
:return:
|
|
153
|
+
**dict**
|
|
154
|
+
"""
|
|
155
|
+
header_map = {}
|
|
156
|
+
for a, b in header_list:
|
|
157
|
+
if isinstance(a, bytes):
|
|
158
|
+
a = a.strip(b'\"')
|
|
159
|
+
if isinstance(b, bytes):
|
|
160
|
+
b = b.strip(b'\"')
|
|
161
|
+
header_map[a] = b
|
|
162
|
+
return header_map
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def safe_get_element(name, container):
|
|
166
|
+
"""
|
|
167
|
+
Get element from dict which the lower of key and name are equal.
|
|
168
|
+
|
|
169
|
+
:type name: string
|
|
170
|
+
:param name: None
|
|
171
|
+
|
|
172
|
+
:type container: dict
|
|
173
|
+
:param container: None
|
|
174
|
+
=======================
|
|
175
|
+
:return:
|
|
176
|
+
**Value**
|
|
177
|
+
"""
|
|
178
|
+
for k, v in iteritems(container):
|
|
179
|
+
if k.strip().lower() == name.strip().lower():
|
|
180
|
+
return v
|
|
181
|
+
return ""
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def check_redirect(res):
|
|
185
|
+
"""
|
|
186
|
+
Check whether the response is redirect.
|
|
187
|
+
|
|
188
|
+
:type res: HttpResponse
|
|
189
|
+
:param res: None
|
|
190
|
+
|
|
191
|
+
:return:
|
|
192
|
+
**Boolean**
|
|
193
|
+
"""
|
|
194
|
+
is_redirect = False
|
|
195
|
+
try:
|
|
196
|
+
if res.status == 301 or res.status == 302:
|
|
197
|
+
is_redirect = True
|
|
198
|
+
except:
|
|
199
|
+
pass
|
|
200
|
+
return is_redirect
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _get_normalized_char_list():
|
|
204
|
+
""""
|
|
205
|
+
:return:
|
|
206
|
+
**ASCII string**
|
|
207
|
+
"""
|
|
208
|
+
ret = ['%%%02X' % i for i in range(256)]
|
|
209
|
+
for ch in string.ascii_letters + string.digits + '.~-_':
|
|
210
|
+
ret[ord(ch)] = ch
|
|
211
|
+
if isinstance(ret[0], str):
|
|
212
|
+
ret = [s.encode("utf-8") for s in ret]
|
|
213
|
+
return ret
|
|
214
|
+
_NORMALIZED_CHAR_LIST = _get_normalized_char_list()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def normalize_string(in_str, encoding_slash=True):
|
|
218
|
+
"""
|
|
219
|
+
Encode in_str.
|
|
220
|
+
When encoding_slash is True, don't encode skip_chars, vice versa.
|
|
221
|
+
|
|
222
|
+
:type in_str: string
|
|
223
|
+
:param in_str: None
|
|
224
|
+
|
|
225
|
+
:type encoding_slash: Bool
|
|
226
|
+
:param encoding_slash: None
|
|
227
|
+
===============================
|
|
228
|
+
:return:
|
|
229
|
+
**ASCII string**
|
|
230
|
+
"""
|
|
231
|
+
tmp = []
|
|
232
|
+
for ch in convert_to_standard_string(in_str):
|
|
233
|
+
# on python3, ch is int type
|
|
234
|
+
sep = ''
|
|
235
|
+
index = -1
|
|
236
|
+
if isinstance(ch, int):
|
|
237
|
+
# on py3
|
|
238
|
+
sep = chr(ch).encode("utf-8")
|
|
239
|
+
index = ch
|
|
240
|
+
else:
|
|
241
|
+
sep = ch
|
|
242
|
+
index = ord(ch)
|
|
243
|
+
if sep == b'/' and not encoding_slash:
|
|
244
|
+
tmp.append(b'/')
|
|
245
|
+
else:
|
|
246
|
+
tmp.append(_NORMALIZED_CHAR_LIST[index])
|
|
247
|
+
return (b'').join(tmp)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def append_uri(base_uri, *path_components):
|
|
251
|
+
"""
|
|
252
|
+
Append path_components to the end of base_uri in order, and ignore all empty strings and None
|
|
253
|
+
|
|
254
|
+
:param base_uri: None
|
|
255
|
+
:type base_uri: string
|
|
256
|
+
|
|
257
|
+
:param path_components: None
|
|
258
|
+
|
|
259
|
+
:return: the final url
|
|
260
|
+
:rtype: str
|
|
261
|
+
"""
|
|
262
|
+
tmp = [base_uri]
|
|
263
|
+
for path in path_components:
|
|
264
|
+
if path:
|
|
265
|
+
tmp.append(normalize_string(path, False))
|
|
266
|
+
if len(tmp) > 1:
|
|
267
|
+
tmp[0] = tmp[0].rstrip(b'/')
|
|
268
|
+
tmp[-1] = tmp[-1].lstrip(b'/')
|
|
269
|
+
for i in range(1, len(tmp) - 1):
|
|
270
|
+
tmp[i] = tmp[i].strip(b'/')
|
|
271
|
+
return (b'/').join(tmp)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def check_bucket_valid(bucket):
|
|
275
|
+
"""
|
|
276
|
+
Check bucket name whether is legal.
|
|
277
|
+
|
|
278
|
+
:type bucket: string
|
|
279
|
+
:param bucket: None
|
|
280
|
+
=======================
|
|
281
|
+
:return:
|
|
282
|
+
**Boolean**
|
|
283
|
+
"""
|
|
284
|
+
alphabet = "abcdefghijklmnopqrstuvwxyz0123456789-"
|
|
285
|
+
if len(bucket) < 3 or len(bucket) > 63:
|
|
286
|
+
return False
|
|
287
|
+
if bucket[-1] == "-" or bucket[-1] == "_":
|
|
288
|
+
return False
|
|
289
|
+
if not (('a' <= bucket[0] <= 'z') or ('0' <= bucket[0] <= '9')):
|
|
290
|
+
return False
|
|
291
|
+
for i in bucket:
|
|
292
|
+
if not i in alphabet:
|
|
293
|
+
return False
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def guess_content_type_by_file_name(file_name):
|
|
298
|
+
"""
|
|
299
|
+
Get file type by filename.
|
|
300
|
+
|
|
301
|
+
:type file_name: string
|
|
302
|
+
:param file_name: None
|
|
303
|
+
=======================
|
|
304
|
+
:return:
|
|
305
|
+
**Type Value**
|
|
306
|
+
"""
|
|
307
|
+
mime_map = dict()
|
|
308
|
+
mime_map["js"] = "application/javascript"
|
|
309
|
+
mime_map["xlsx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
310
|
+
mime_map["xltx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.template"
|
|
311
|
+
mime_map["potx"] = "application/vnd.openxmlformats-officedocument.presentationml.template"
|
|
312
|
+
mime_map["ppsx"] = "application/vnd.openxmlformats-officedocument.presentationml.slideshow"
|
|
313
|
+
mime_map["pptx"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
314
|
+
mime_map["sldx"] = "application/vnd.openxmlformats-officedocument.presentationml.slide"
|
|
315
|
+
mime_map["docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
316
|
+
mime_map["dotx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.template"
|
|
317
|
+
mime_map["xlam"] = "application/vnd.ms-excel.addin.macroEnabled.12"
|
|
318
|
+
mime_map["xlsb"] = "application/vnd.ms-excel.sheet.binary.macroEnabled.12"
|
|
319
|
+
try:
|
|
320
|
+
file_name = compat.convert_to_string(file_name)
|
|
321
|
+
name = os.path.basename(file_name.lower())
|
|
322
|
+
suffix = name.split('.')[-1]
|
|
323
|
+
if suffix in iterkeys(mime_map):
|
|
324
|
+
mime_type = mime_map[suffix]
|
|
325
|
+
else:
|
|
326
|
+
import mimetypes
|
|
327
|
+
|
|
328
|
+
mimetypes.init()
|
|
329
|
+
mime_type = mimetypes.types_map.get("." + suffix, 'application/octet-stream')
|
|
330
|
+
except:
|
|
331
|
+
mime_type = 'application/octet-stream'
|
|
332
|
+
if not mime_type:
|
|
333
|
+
mime_type = 'application/octet-stream'
|
|
334
|
+
|
|
335
|
+
return compat.convert_to_bytes(mime_type)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
_first_cap_regex = re.compile('(.)([A-Z][a-z]+)')
|
|
339
|
+
_number_cap_regex = re.compile('([a-z])([0-9]{2,})')
|
|
340
|
+
_end_cap_regex = re.compile('([a-z0-9])([A-Z])')
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def pythonize_name(name):
|
|
344
|
+
"""Convert camel case to a "pythonic" name.
|
|
345
|
+
Examples::
|
|
346
|
+
pythonize_name('CamelCase') -> 'camel_case'
|
|
347
|
+
pythonize_name('already_pythonized') -> 'already_pythonized'
|
|
348
|
+
pythonize_name('HTTPRequest') -> 'http_request'
|
|
349
|
+
pythonize_name('HTTPStatus200Ok') -> 'http_status_200_ok'
|
|
350
|
+
pythonize_name('UPPER') -> 'upper'
|
|
351
|
+
pythonize_name('ContentMd5')->'content_md5'
|
|
352
|
+
pythonize_name('') -> ''
|
|
353
|
+
"""
|
|
354
|
+
if name == "eTag":
|
|
355
|
+
return "etag"
|
|
356
|
+
s1 = _first_cap_regex.sub(r'\1_\2', name)
|
|
357
|
+
s2 = _number_cap_regex.sub(r'\1_\2', s1)
|
|
358
|
+
return _end_cap_regex.sub(r'\1_\2', s2).lower()
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def get_canonical_querystring(params, for_signature):
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
:param params:
|
|
365
|
+
:param for_signature:
|
|
366
|
+
:return:
|
|
367
|
+
"""
|
|
368
|
+
if params is None:
|
|
369
|
+
return ''
|
|
370
|
+
result = []
|
|
371
|
+
for k, v in iteritems(params):
|
|
372
|
+
if not for_signature or k.lower != http_headers.AUTHORIZATION.lower():
|
|
373
|
+
if v is None:
|
|
374
|
+
v = ''
|
|
375
|
+
result.append(b'%s=%s' % (normalize_string(k), normalize_string(v)))
|
|
376
|
+
result.sort()
|
|
377
|
+
return (b'&').join(result)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def print_object(obj):
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
:param obj:
|
|
384
|
+
:return:
|
|
385
|
+
"""
|
|
386
|
+
tmp = []
|
|
387
|
+
for k, v in iteritems(obj.__dict__):
|
|
388
|
+
if not k.startswith('__') and k != "raw_data":
|
|
389
|
+
if isinstance(v, bytes):
|
|
390
|
+
tmp.append("%s:'%s'" % (k, v))
|
|
391
|
+
# str is unicode
|
|
392
|
+
elif isinstance(v, str):
|
|
393
|
+
tmp.append("%s:u'%s'" % (k, v))
|
|
394
|
+
else:
|
|
395
|
+
tmp.append('%s:%s' % (k, v))
|
|
396
|
+
return '{%s}' % ','.join(tmp)
|
|
397
|
+
|
|
398
|
+
class Expando(object):
|
|
399
|
+
"""
|
|
400
|
+
Expandable class
|
|
401
|
+
"""
|
|
402
|
+
def __init__(self, attr_dict=None):
|
|
403
|
+
if attr_dict:
|
|
404
|
+
self.__dict__.update(attr_dict)
|
|
405
|
+
|
|
406
|
+
def __getattr__(self, item):
|
|
407
|
+
if item.startswith('__'):
|
|
408
|
+
raise AttributeError
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
def __repr__(self):
|
|
412
|
+
return print_object(self)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def dict_to_python_object(d):
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
:param d:
|
|
419
|
+
:return:
|
|
420
|
+
"""
|
|
421
|
+
attr = {}
|
|
422
|
+
for k, v in iteritems(d):
|
|
423
|
+
if not isinstance(k, compat.string_types):
|
|
424
|
+
k = compat.convert_to_string(k)
|
|
425
|
+
k = pythonize_name(k)
|
|
426
|
+
attr[k] = v
|
|
427
|
+
return Expando(attr)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def required(**types):
|
|
431
|
+
"""
|
|
432
|
+
decorator of input param check
|
|
433
|
+
:param types:
|
|
434
|
+
:return:
|
|
435
|
+
"""
|
|
436
|
+
def _required(f):
|
|
437
|
+
def _decorated(*args, **kwds):
|
|
438
|
+
for i, v in enumerate(args):
|
|
439
|
+
if f.__code__.co_varnames[i] in types:
|
|
440
|
+
if v is None:
|
|
441
|
+
raise ValueError('arg "%s" should not be None' %
|
|
442
|
+
(f.__code__.co_varnames[i]))
|
|
443
|
+
if not isinstance(v, types[f.__code__.co_varnames[i]]):
|
|
444
|
+
raise TypeError('arg "%s"= %r does not match %s' %
|
|
445
|
+
(f.__code__.co_varnames[i],
|
|
446
|
+
v,
|
|
447
|
+
types[f.__code__.co_varnames[i]]))
|
|
448
|
+
for k, v in iteritems(kwds):
|
|
449
|
+
if k in types:
|
|
450
|
+
if v is None:
|
|
451
|
+
raise ValueError('arg "%s" should not be None' % k)
|
|
452
|
+
if not isinstance(v, types[k]):
|
|
453
|
+
raise TypeError('arg "%s"= %r does not match %s' % (k, v, types[k]))
|
|
454
|
+
return f(*args, **kwds)
|
|
455
|
+
_decorated.__name__ = f.__name__
|
|
456
|
+
return _decorated
|
|
457
|
+
return _required
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def parse_host_port(endpoint, default_protocol):
|
|
461
|
+
"""
|
|
462
|
+
parse protocol, host, port from endpoint in config
|
|
463
|
+
|
|
464
|
+
:type: string
|
|
465
|
+
:param endpoint: endpoint in config
|
|
466
|
+
|
|
467
|
+
:type: baiducloud_python_sdk_core.protocol.HTTP or baiducloud_python_sdk_core.protocol.HTTPS
|
|
468
|
+
:param default_protocol: if there is no scheme in endpoint,
|
|
469
|
+
we will use this protocol as default
|
|
470
|
+
:return: tuple of protocol, host, port
|
|
471
|
+
"""
|
|
472
|
+
# netloc should begin with // according to RFC1808
|
|
473
|
+
if b"//" not in endpoint:
|
|
474
|
+
endpoint = b"//" + endpoint
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
# scheme in endpoint dominates input default_protocol
|
|
478
|
+
parse_result = urlparse(
|
|
479
|
+
endpoint,
|
|
480
|
+
compat.convert_to_bytes(default_protocol.name))
|
|
481
|
+
except Exception as e:
|
|
482
|
+
raise ValueError('Invalid endpoint:%s, error:%s' % (endpoint,
|
|
483
|
+
compat.convert_to_string(e)))
|
|
484
|
+
|
|
485
|
+
if parse_result.scheme == compat.convert_to_bytes(baiducloud_python_sdk_core.protocol.HTTP.name):
|
|
486
|
+
protocol = baiducloud_python_sdk_core.protocol.HTTP
|
|
487
|
+
port = baiducloud_python_sdk_core.protocol.HTTP.default_port
|
|
488
|
+
elif parse_result.scheme == compat.convert_to_bytes(baiducloud_python_sdk_core.protocol.HTTPS.name):
|
|
489
|
+
protocol = baiducloud_python_sdk_core.protocol.HTTPS
|
|
490
|
+
port = baiducloud_python_sdk_core.protocol.HTTPS.default_port
|
|
491
|
+
else:
|
|
492
|
+
raise ValueError('Unsupported protocol %s' % parse_result.scheme)
|
|
493
|
+
host = parse_result.hostname
|
|
494
|
+
if parse_result.port is not None:
|
|
495
|
+
port = parse_result.port
|
|
496
|
+
|
|
497
|
+
return protocol, host, port
|
|
498
|
+
|
|
499
|
+
"""
|
|
500
|
+
def aes128_encrypt_16char_key(adminpass, secretkey):
|
|
501
|
+
|
|
502
|
+
#Python2:encrypt admin password by AES128
|
|
503
|
+
|
|
504
|
+
pad_it = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
|
|
505
|
+
key = secretkey[0:16]
|
|
506
|
+
mode = AES.MODE_ECB
|
|
507
|
+
cryptor = AES.new(key, mode, key)
|
|
508
|
+
cipheradminpass = cryptor.encrypt(pad_it(adminpass)).encode('hex')
|
|
509
|
+
return cipheradminpass
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def aes128_encrypt_16char_key(adminpass, secretkey):
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
:param adminpass: adminpass
|
|
517
|
+
:param secretkey: secretkey
|
|
518
|
+
:return: cipheradminpass
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
# Python3: encrypt admin password by AES128
|
|
522
|
+
|
|
523
|
+
pad_it = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
|
|
524
|
+
key = secretkey[0:16]
|
|
525
|
+
mode = AES.MODE_ECB
|
|
526
|
+
cryptor = AES.new(key, mode)
|
|
527
|
+
pad_admin = pad_it(adminpass)
|
|
528
|
+
byte_pad_admin = pad_admin.encode(encoding='utf-8')
|
|
529
|
+
|
|
530
|
+
cryptoradminpass = cryptor.encrypt(byte_pad_admin)
|
|
531
|
+
#print(cryptoradminpass)
|
|
532
|
+
|
|
533
|
+
#cipheradminpass = cryptor.encrypt(byte_pad_admin).encode('hex')
|
|
534
|
+
byte_cipheradminpass = codecs.encode(cryptoradminpass, 'hex_codec')
|
|
535
|
+
#print(byte_cipheradminpass)
|
|
536
|
+
|
|
537
|
+
cipheradminpass = byte_cipheradminpass.decode(encoding='utf-8')
|
|
538
|
+
#print(cipheradminpass)
|
|
539
|
+
|
|
540
|
+
return cipheradminpass
|
|
541
|
+
|
|
542
|
+
def is_cname_like_host(host):
|
|
543
|
+
"""
|
|
544
|
+
:param host: custom domain
|
|
545
|
+
:return: domain end with cdn endpoint or not
|
|
546
|
+
"""
|
|
547
|
+
if host is None:
|
|
548
|
+
return False
|
|
549
|
+
for suffix in DEFAULT_CNAME_LIKE_LIST:
|
|
550
|
+
if host.lower().endswith(suffix):
|
|
551
|
+
return True
|
|
552
|
+
return False
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def is_custom_host(host, bucket_name):
|
|
556
|
+
"""
|
|
557
|
+
custom host : xxx.region.bcebos.com
|
|
558
|
+
: return: custom, domain or not
|
|
559
|
+
"""
|
|
560
|
+
if host is None or bucket_name is None:
|
|
561
|
+
return False
|
|
562
|
+
|
|
563
|
+
host_split = host.split(b'.')
|
|
564
|
+
# split http head
|
|
565
|
+
return host.lower().startswith(compat.convert_to_bytes(bucket_name.lower())) \
|
|
566
|
+
and len(host_split) == 4 and is_bos_suffixed_host(host)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def is_bos_suffixed_host(host):
|
|
570
|
+
"""
|
|
571
|
+
:param host: bos endpoint
|
|
572
|
+
:return: bos endpoint or not
|
|
573
|
+
"""
|
|
574
|
+
if host is None:
|
|
575
|
+
return False
|
|
576
|
+
if host.endswith(b'/'):
|
|
577
|
+
check_host = host[:-1]
|
|
578
|
+
else:
|
|
579
|
+
check_host = host
|
|
580
|
+
|
|
581
|
+
return check_host.lower().endswith(DEFAULT_BOS_DOMAIN_SUFFIX)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def check_ipv4(ipAddr):
|
|
585
|
+
"""
|
|
586
|
+
:param ipAddr: ip address
|
|
587
|
+
:return: true or false
|
|
588
|
+
"""
|
|
589
|
+
compile_ip=re.compile(b'((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}')
|
|
590
|
+
return compile_ip.match(ipAddr)
|
|
591
|
+
|
|
592
|
+
def _get_data_size(data):
|
|
593
|
+
if hasattr(data, '__len__'):
|
|
594
|
+
return len(data)
|
|
595
|
+
|
|
596
|
+
if hasattr(data, 'len'):
|
|
597
|
+
return data.len
|
|
598
|
+
|
|
599
|
+
if hasattr(data, 'seek') and hasattr(data, 'tell'):
|
|
600
|
+
return file_object_remaining_bytes(data)
|
|
601
|
+
|
|
602
|
+
return None
|
|
603
|
+
|
|
604
|
+
def file_object_remaining_bytes(fileobj):
|
|
605
|
+
current = fileobj.tell()
|
|
606
|
+
|
|
607
|
+
fileobj.seek(0, os.SEEK_END)
|
|
608
|
+
end = fileobj.tell()
|
|
609
|
+
fileobj.seek(current, os.SEEK_SET)
|
|
610
|
+
|
|
611
|
+
return end - current
|
|
612
|
+
|
|
613
|
+
def _invoke_progress_callback(progress_callback, consumed_bytes, total_bytes):
|
|
614
|
+
if progress_callback:
|
|
615
|
+
progress_callback(consumed_bytes, total_bytes)
|
|
616
|
+
|
|
617
|
+
def make_progress_adapter(data, progress_callback, size=None):
|
|
618
|
+
"""return a adapter,when reading 'data', that is, calling read or iterating
|
|
619
|
+
over it Call the progress callback function
|
|
620
|
+
|
|
621
|
+
:param data: bytes,file object or iterable
|
|
622
|
+
:param progress_callback: callback function, ref:`_default_progress_callback`
|
|
623
|
+
:param size: size of `data`
|
|
624
|
+
|
|
625
|
+
:return: callback function adapter
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
if size is None:
|
|
629
|
+
size = _get_data_size(data)
|
|
630
|
+
|
|
631
|
+
if size is None:
|
|
632
|
+
raise ValueError('{0} is not a file object'.format(data.__class__.__name__))
|
|
633
|
+
|
|
634
|
+
return _BytesAndFileAdapter(data, progress_callback, size)
|
|
635
|
+
|
|
636
|
+
_CHUNK_SIZE = 8 * 1024
|
|
637
|
+
|
|
638
|
+
class _BytesAndFileAdapter(object):
|
|
639
|
+
"""With this adapter, you can add progress monitoring to 'data'.
|
|
640
|
+
|
|
641
|
+
:param data: bytes or file object
|
|
642
|
+
:param progress_callback: user-provided callback function. like callback(bytes_read, total_bytes)
|
|
643
|
+
bytes_read is readed bytes;total_bytes is total bytes
|
|
644
|
+
:param int size : data size
|
|
645
|
+
"""
|
|
646
|
+
def __init__(self, data, progress_callback=None, size=None):
|
|
647
|
+
self.data = data
|
|
648
|
+
self.progress_callback = progress_callback
|
|
649
|
+
self.size = size
|
|
650
|
+
self.offset = 0
|
|
651
|
+
|
|
652
|
+
@property
|
|
653
|
+
def len(self):
|
|
654
|
+
return self.size
|
|
655
|
+
|
|
656
|
+
# for python 2.x
|
|
657
|
+
def __bool__(self):
|
|
658
|
+
return True
|
|
659
|
+
# for python 3.x
|
|
660
|
+
__nonzero__=__bool__
|
|
661
|
+
|
|
662
|
+
# support iterable type
|
|
663
|
+
# def __iter__(self):
|
|
664
|
+
# return self
|
|
665
|
+
|
|
666
|
+
# def __next__(self):
|
|
667
|
+
# return self.next()
|
|
668
|
+
|
|
669
|
+
# def next(self):
|
|
670
|
+
# content = self.read(_CHUNK_SIZE)
|
|
671
|
+
|
|
672
|
+
# if content:
|
|
673
|
+
# return content
|
|
674
|
+
# else:
|
|
675
|
+
# raise StopIteration
|
|
676
|
+
|
|
677
|
+
def read(self, amt=None):
|
|
678
|
+
if self.offset >= self.size:
|
|
679
|
+
return compat.convert_to_bytes('')
|
|
680
|
+
|
|
681
|
+
if amt is None or amt < 0:
|
|
682
|
+
bytes_to_read = self.size - self.offset
|
|
683
|
+
else:
|
|
684
|
+
bytes_to_read = min(amt, self.size - self.offset)
|
|
685
|
+
|
|
686
|
+
if isinstance(self.data, bytes):
|
|
687
|
+
content = self.data[self.offset:self.offset+bytes_to_read]
|
|
688
|
+
else:
|
|
689
|
+
content = self.data.read(bytes_to_read)
|
|
690
|
+
|
|
691
|
+
self.offset += bytes_to_read
|
|
692
|
+
|
|
693
|
+
_invoke_progress_callback(self.progress_callback, min(self.offset, self.size), self.size)
|
|
694
|
+
|
|
695
|
+
return content
|
|
696
|
+
|
|
697
|
+
def default_progress_callback(consumed_bytes, total_bytes):
|
|
698
|
+
"""Progress bar callback function that calculates the percentage of current completion
|
|
699
|
+
|
|
700
|
+
:param consumed_bytes: Amount of data that has been uploaded/downloaded
|
|
701
|
+
:param total_bytes: According to the total amount
|
|
702
|
+
"""
|
|
703
|
+
if total_bytes:
|
|
704
|
+
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
|
|
705
|
+
start_progress = '*' * rate
|
|
706
|
+
end_progress = '.' * (100 - rate)
|
|
707
|
+
if rate == 100:
|
|
708
|
+
print("\r{}%[{}->{}]\n".format(rate, start_progress, end_progress), end="")
|
|
709
|
+
else:
|
|
710
|
+
print("\r{}%[{}->{}]".format(rate, start_progress, end_progress), end="")
|
|
711
|
+
|
|
712
|
+
sys.stdout.flush()
|
|
713
|
+
|
|
714
|
+
def to_dict(obj, visited=None):
|
|
715
|
+
if obj is None:
|
|
716
|
+
return None
|
|
717
|
+
if visited is None:
|
|
718
|
+
visited = set()
|
|
719
|
+
# 避免循环引用
|
|
720
|
+
if id(obj) in visited:
|
|
721
|
+
return str(obj) # 或者 return None / "CyclicRef"
|
|
722
|
+
visited.add(id(obj))
|
|
723
|
+
if isinstance(obj, (str, int, float, bool)):
|
|
724
|
+
return obj
|
|
725
|
+
elif isinstance(obj, list):
|
|
726
|
+
return [to_dict(v, visited) for v in obj]
|
|
727
|
+
elif isinstance(obj, dict):
|
|
728
|
+
return {k: to_dict(v, visited) for k, v in obj.items()}
|
|
729
|
+
elif hasattr(obj, "__dict__"): # 任意 class 实例
|
|
730
|
+
result = {}
|
|
731
|
+
for key, value in obj.__dict__.items():
|
|
732
|
+
result[key] = to_dict(value, visited)
|
|
733
|
+
return result
|
|
734
|
+
else:
|
|
735
|
+
return obj
|
|
736
|
+
|
|
737
|
+
def to_api_dict(d: Dict[str, Any]) -> Dict[str, Any]:
|
|
738
|
+
"""
|
|
739
|
+
清理字典:
|
|
740
|
+
- 移除 value 为 None 的键值对
|
|
741
|
+
- 支持递归清理 dict 和 list
|
|
742
|
+
- list 内部的 None 不移除,但会递归处理其中的 dict 或对象
|
|
743
|
+
- 普通类对象会转为 __dict__
|
|
744
|
+
"""
|
|
745
|
+
if not isinstance(d, dict):
|
|
746
|
+
raise TypeError("Input must be a dict")
|
|
747
|
+
def _to_dict(obj: Any) -> Any:
|
|
748
|
+
if hasattr(obj, "__dict__"):
|
|
749
|
+
return obj.__dict__
|
|
750
|
+
return obj
|
|
751
|
+
def _clean(obj: Union[Dict[str, Any], List[Any], Any]) -> Any:
|
|
752
|
+
if isinstance(obj, dict):
|
|
753
|
+
return {k: _clean(v) for k, v in obj.items() if v is not None}
|
|
754
|
+
elif isinstance(obj, list):
|
|
755
|
+
return [
|
|
756
|
+
_clean(item) if isinstance(item, (dict, list))
|
|
757
|
+
else _clean(_to_dict(item))
|
|
758
|
+
for item in obj
|
|
759
|
+
]
|
|
760
|
+
else:
|
|
761
|
+
return _to_dict(obj)
|
|
762
|
+
return _clean(d)
|