utils.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. # -*- coding: utf-8 -*-
  2. # This file is auto-generated, don't edit it. Thanks.
  3. from __future__ import annotations
  4. from darabonba.model import DaraModel
  5. from typing import Dict, Any, List
  6. import binascii
  7. import datetime
  8. import hashlib
  9. import hmac
  10. import base64
  11. import copy
  12. import platform
  13. import time
  14. import Tea
  15. import threading
  16. import random
  17. import hashlib
  18. from cryptography.hazmat.primitives import hashes
  19. from cryptography.hazmat.primitives.asymmetric import padding
  20. from cryptography.hazmat.primitives.serialization import load_pem_private_key
  21. from urllib.parse import quote_plus, quote
  22. from darabonba.utils.stream import STREAM_CLASS
  23. from darabonba.utils.form import Form
  24. from darabonba.core import DaraCore
  25. from datetime import datetime
  26. from typing import Any, Dict, List
  27. from .sm3 import hash_sm3, Sm3
  28. _process_start_time = int(time.time() * 1000)
  29. _seqId = 0
  30. def to_str(val):
  31. if val is None:
  32. return val
  33. if isinstance(val, bytes):
  34. return str(val, encoding='utf-8')
  35. else:
  36. return str(val)
  37. def rsa_sign(plaintext, secret):
  38. if isinstance(secret, str):
  39. secret = secret.encode('utf-8')
  40. if not secret.startswith(b'-----BEGIN'):
  41. secret = b'-----BEGIN PRIVATE KEY-----\n' + secret
  42. if not secret.endswith(b'-----END PRIVATE KEY-----'):
  43. secret = secret.rstrip() + b'\n-----END PRIVATE KEY-----'
  44. # PKCS#8 payloads are often labeled as RSA PRIVATE KEY in other SDKs; cryptography requires matching headers.
  45. secret = secret.replace(b'-----BEGIN RSA PRIVATE KEY-----', b'-----BEGIN PRIVATE KEY-----')
  46. secret = secret.replace(b'-----END RSA PRIVATE KEY-----', b'-----END PRIVATE KEY-----')
  47. key = load_pem_private_key(secret, password=None)
  48. return key.sign(plaintext, padding.PKCS1v15(), hashes.SHA256())
  49. def signature_method(secret, source, sign_type):
  50. source = source.encode('utf-8')
  51. secret = secret.encode('utf-8')
  52. if sign_type == 'ACS3-HMAC-SHA256':
  53. return hmac.new(secret, source, hashlib.sha256).digest()
  54. elif sign_type == 'ACS3-HMAC-SM3':
  55. return hmac.new(secret, source, Sm3).digest()
  56. elif sign_type == 'ACS3-RSA-SHA256':
  57. return rsa_sign(source, secret)
  58. def get_canonical_query_string(query):
  59. if query is None or len(query) <= 0:
  60. return ''
  61. canon_keys = []
  62. for k, v in query.items():
  63. if v is not None:
  64. canon_keys.append(k)
  65. canon_keys.sort()
  66. query_string = ''
  67. for key in canon_keys:
  68. value = quote(query[key], safe='~', encoding='utf-8')
  69. if value is None:
  70. s = f'{key}&'
  71. else:
  72. s = f'{key}={value}&'
  73. query_string += s
  74. return query_string[:-1]
  75. def get_canonicalized_headers(headers):
  76. canon_keys = []
  77. tmp_headers = {}
  78. for k, v in headers.items():
  79. if v is not None:
  80. if k.lower() not in canon_keys:
  81. canon_keys.append(k.lower())
  82. tmp_headers[k.lower()] = [to_str(v).strip()]
  83. else:
  84. tmp_headers[k.lower()].append(to_str(v).strip())
  85. canon_keys.sort()
  86. canonical_headers = ''
  87. for key in canon_keys:
  88. header_entry = ','.join(sorted(tmp_headers[key]))
  89. s = f'{key}:{header_entry}\n'
  90. canonical_headers += s
  91. return canonical_headers, ';'.join(canon_keys)
  92. class Utils(object):
  93. """
  94. This is for OpenApi Util
  95. """
  96. @staticmethod
  97. def convert(body, content):
  98. """
  99. Convert all params of body other than type of readable into content
  100. @param body: source Model
  101. @param content: target Model
  102. @return: void
  103. """
  104. body_map = Utils._except_stream(body.to_map())
  105. content.from_map(body_map)
  106. @staticmethod
  107. def _except_stream(val):
  108. if isinstance(val, dict):
  109. result = {}
  110. for k, v in val.items():
  111. result[k] = Utils._except_stream(v)
  112. return result
  113. elif isinstance(val, list):
  114. result = []
  115. for i in val:
  116. if i is not None:
  117. item = Utils._except_stream(i)
  118. if item is not None:
  119. result.append(item)
  120. else:
  121. result.append(Utils._except_stream(i))
  122. return result
  123. elif isinstance(val, STREAM_CLASS):
  124. return None
  125. return val
  126. @staticmethod
  127. def _get_canonicalized_headers(headers):
  128. canon_keys = []
  129. for k in headers:
  130. if k.startswith('x-acs-'):
  131. canon_keys.append(k)
  132. canon_keys = sorted(canon_keys)
  133. canon_header = ''
  134. for k in canon_keys:
  135. canon_header += '%s:%s\n' % (k, headers[k])
  136. return canon_header
  137. @staticmethod
  138. def _get_canonicalized_resource(pathname, query):
  139. if len(query) <= 0:
  140. return pathname
  141. resource = '%s?' % pathname
  142. query_list = sorted(list(query))
  143. for key in query_list:
  144. if query[key] is not None:
  145. if query[key] == '':
  146. s = '%s&' % key
  147. else:
  148. s = '%s=%s&' % (key, query[key])
  149. resource += s
  150. return resource[:-1]
  151. @staticmethod
  152. def get_string_to_sign(request):
  153. """
  154. Get the string to be signed according to request
  155. @param request: which contains signed messages
  156. @return: the signed string
  157. """
  158. method, pathname, headers, query = request.method, request.pathname, request.headers, request.query
  159. accept = '' if headers.get('accept') is None else headers.get('accept')
  160. content_md5 = '' if headers.get('content-md5') is None else headers.get('content-md5')
  161. content_type = '' if headers.get('content-type') is None else headers.get('content-type')
  162. date = '' if headers.get('date') is None else headers.get('date')
  163. header = '%s\n%s\n%s\n%s\n%s\n' % (method, accept, content_md5, content_type, date)
  164. canon_headers = Utils._get_canonicalized_headers(headers)
  165. canon_resource = Utils._get_canonicalized_resource(pathname, query)
  166. sign_str = header + canon_headers + canon_resource
  167. return sign_str
  168. @staticmethod
  169. def get_roasignature(string_to_sign, secret):
  170. """
  171. Get signature according to stringToSign, secret
  172. @type string_to_sign: str
  173. @param string_to_sign: the signed string
  174. @type secret: str
  175. @param secret: accesskey secret
  176. @return: the signature
  177. """
  178. hash_val = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1).digest()
  179. signature = base64.b64encode(hash_val).decode('utf-8')
  180. return signature
  181. @staticmethod
  182. def _object_handler(key, value, out):
  183. if value is None:
  184. return
  185. if isinstance(value, dict):
  186. for k, v in value.items():
  187. Utils._object_handler('%s.%s' % (key, k), v, out)
  188. elif isinstance(value, DaraModel):
  189. for k, v in value.to_map().items():
  190. Utils._object_handler('%s.%s' % (key, k), v, out)
  191. elif isinstance(value, (list, tuple)):
  192. for index, val in enumerate(value):
  193. Utils._object_handler('%s.%s' % (key, index + 1), val, out)
  194. else:
  195. if key.startswith('.'):
  196. key = key[1:]
  197. if isinstance(value, bytes):
  198. out[key] = str(value, encoding='utf-8')
  199. elif not isinstance(value, STREAM_CLASS):
  200. out[key] = str(value)
  201. @staticmethod
  202. def anyify_map_value(
  203. m: Dict[str, str],
  204. ) -> Dict[str, Any]:
  205. """
  206. Anyify the value of map
  207. @return: the new anyfied map
  208. """
  209. return m
  210. @staticmethod
  211. def to_form(filter):
  212. """
  213. Parse filter into a form string
  214. @type filter: dict
  215. @param filter: object
  216. @return: the string
  217. """
  218. result = {}
  219. if filter:
  220. Utils._object_handler('', filter, result)
  221. return Form.to_form_string(result)
  222. @staticmethod
  223. def get_timestamp():
  224. """
  225. Get timestamp
  226. @return: the timestamp string
  227. """
  228. return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
  229. @staticmethod
  230. def query(filter):
  231. """
  232. Parse filter into a object which's type is map[string]string
  233. @type filter: dict
  234. @param filter: query param
  235. @return: the object
  236. """
  237. out_dict = {}
  238. if filter:
  239. Utils._object_handler('', filter, out_dict)
  240. return out_dict
  241. @staticmethod
  242. def get_rpcsignature(signed_params, method, secret):
  243. """
  244. Get signature according to signedParams, method and secret
  245. @type signed_params: dict
  246. @param signed_params: params which need to be signed
  247. @type method: str
  248. @param method: http method e.g. GET
  249. @type secret: str
  250. @param secret: AccessKeySecret
  251. @return: the signature
  252. """
  253. queries = signed_params.copy()
  254. keys = list(queries.keys())
  255. keys.sort()
  256. canonicalized_query_string = ""
  257. for k in keys:
  258. if queries[k] is not None:
  259. canonicalized_query_string += f'&{quote(k, safe="~", encoding="utf-8")}=' \
  260. f'{quote(queries[k], safe="~", encoding="utf-8")}'
  261. string_to_sign = f'{method}&%2F&{quote_plus(canonicalized_query_string[1:], safe="~", encoding="utf-8")}'
  262. digest_maker = hmac.new(bytes(secret + '&', encoding="utf-8"),
  263. bytes(string_to_sign, encoding="utf-8"),
  264. digestmod=hashlib.sha1)
  265. hash_bytes = digest_maker.digest()
  266. signed_str = str(base64.b64encode(hash_bytes), encoding="utf-8")
  267. return signed_str
  268. @staticmethod
  269. def array_to_string_with_specified_style(array, prefix, style):
  270. """
  271. Parse array into a string with specified style
  272. @type array: any
  273. @param array: the array
  274. @type prefix: str
  275. @param prefix: the prefix string
  276. @param style: specified style e.g. repeatList
  277. @return: the string
  278. """
  279. if array is None:
  280. return ''
  281. if style == 'repeatList':
  282. return Utils._flat_repeat_list({prefix: array})
  283. elif style == 'simple':
  284. return ','.join(map(str, array))
  285. elif style == 'spaceDelimited':
  286. return ' '.join(map(str, array))
  287. elif style == 'pipeDelimited':
  288. return '|'.join(map(str, array))
  289. elif style == 'json':
  290. return DaraCore.to_json_string(Utils._parse_to_dict(array))
  291. else:
  292. return ''
  293. @staticmethod
  294. def _flat_repeat_list(dic):
  295. query = {}
  296. if dic:
  297. Utils._object_handler('', dic, query)
  298. l = []
  299. q = sorted(query)
  300. for i in q:
  301. k = quote_plus(i, encoding='utf-8')
  302. v = quote_plus(query[i], encoding='utf-8')
  303. l.append(k + '=' + v)
  304. return '&&'.join(l)
  305. @staticmethod
  306. def parse_to_map(inp):
  307. """
  308. Transform input as map.
  309. """
  310. try:
  311. result = Utils._parse_to_dict(inp)
  312. return copy.deepcopy(result)
  313. except TypeError:
  314. return
  315. @staticmethod
  316. def _parse_to_dict(val):
  317. if isinstance(val, dict):
  318. result = {}
  319. for k, v in val.items():
  320. if isinstance(v, (list, dict, DaraModel)):
  321. result[k] = Utils._parse_to_dict(v)
  322. else:
  323. result[k] = v
  324. return result
  325. elif isinstance(val, list):
  326. result = []
  327. for i in val:
  328. if isinstance(i, (list, dict, DaraModel)):
  329. result.append(Utils._parse_to_dict(i))
  330. else:
  331. result.append(i)
  332. return result
  333. elif isinstance(val, DaraModel):
  334. return val.to_map()
  335. @staticmethod
  336. def get_endpoint(endpoint, server_use, endpoint_type):
  337. """
  338. If endpointType is internal, use internal endpoint
  339. If serverUse is true and endpointType is accelerate, use accelerate endpoint
  340. Default return endpoint
  341. @param server_use whether use accelerate endpoint
  342. @param endpoint_type value must be internal or accelerate
  343. @return the final endpoint
  344. """
  345. if endpoint_type == "internal":
  346. str_split = endpoint.split('.')
  347. str_split[0] += "-internal"
  348. endpoint = ".".join(str_split)
  349. if server_use and endpoint_type == "accelerate":
  350. return "oss-accelerate.aliyuncs.com"
  351. return endpoint
  352. @staticmethod
  353. def hash(raw, sign_type):
  354. if sign_type == 'ACS3-HMAC-SHA256' or sign_type == 'ACS3-RSA-SHA256':
  355. return hashlib.sha256(raw).digest()
  356. elif sign_type == 'ACS3-HMAC-SM3':
  357. return hash_sm3(raw)
  358. @staticmethod
  359. def hex_encode(raw):
  360. if raw:
  361. return binascii.b2a_hex(raw).decode('utf-8')
  362. @staticmethod
  363. def get_authorization(request, sign_type, payload, ak, secret):
  364. canonical_uri = request.pathname if request.pathname else '/'
  365. canonicalized_query = get_canonical_query_string(request.query)
  366. canonicalized_headers, signed_headers = get_canonicalized_headers(request.headers)
  367. canonical_request = f'{request.method}\n' \
  368. f'{canonical_uri}\n' \
  369. f'{canonicalized_query}\n' \
  370. f'{canonicalized_headers}\n' \
  371. f'{signed_headers}\n' \
  372. f'{payload}'
  373. str_to_sign = f'{sign_type}\n{Utils.hex_encode(Utils.hash(canonical_request.encode("utf-8"), sign_type))}'
  374. signature = Utils.hex_encode(signature_method(secret, str_to_sign, sign_type))
  375. auth = f'{sign_type} Credential={ak},SignedHeaders={signed_headers},Signature={signature}'
  376. return auth
  377. @staticmethod
  378. def get_encode_path(path):
  379. return quote(path, safe='/~', encoding="utf-8")
  380. @staticmethod
  381. def get_encode_param(param):
  382. return quote(param, safe='~', encoding="utf-8")
  383. @staticmethod
  384. def get_nonce() -> str:
  385. """
  386. Generate a nonce string
  387. @return: the nonce string
  388. """
  389. global _seqId
  390. thread_id = threading.get_ident()
  391. current_time = int(time.time() * 1000)
  392. seq = _seqId
  393. _seqId += 1
  394. randNum = random.getrandbits(64)
  395. _process_start_time = int(time.time() * 1000)
  396. msg = f'{_process_start_time}-{thread_id}-{current_time}-{seq}-{randNum}'
  397. md5 = hashlib.md5()
  398. md5.update(msg.encode('utf-8'))
  399. return md5.hexdigest()
  400. @staticmethod
  401. def apply_retry_headers(headers, retries_attempted, backoff_delay):
  402. if retries_attempted > 0:
  403. headers['x-acs-retry-attempts'] = str(retries_attempted)
  404. headers['x-acs-retry-delay'] = str(backoff_delay)
  405. @staticmethod
  406. def get_date_utcstring() -> str:
  407. """
  408. Get an UTC format string by current date, e.g. 'Thu, 06 Feb 2020 07:32:54 GMT'
  409. @return: the UTC format string
  410. """
  411. return datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
  412. @staticmethod
  413. def stringify_map_value(
  414. m: Dict[str, Any],
  415. ) -> Dict[str, str]:
  416. """
  417. Stringify the value of map
  418. @return: the new stringified map
  419. """
  420. if m is None:
  421. return {}
  422. dic_result = {}
  423. for k, v in m.items():
  424. if v is not None:
  425. if isinstance(v, bytes):
  426. v = v.decode('utf-8')
  427. else:
  428. v = str(v)
  429. dic_result[k] = v
  430. return dic_result
  431. @staticmethod
  432. def to_array(
  433. input: Any,
  434. ) -> List[Dict[str, Any]]:
  435. """
  436. Transform input as array.
  437. """
  438. if input is None:
  439. return []
  440. out = []
  441. for i in input:
  442. if isinstance(i, DaraModel):
  443. out.append(i.to_map())
  444. else:
  445. out.append(i)
  446. return out
  447. @staticmethod
  448. def __get_default_agent():
  449. return f'AlibabaCloud ({platform.system()}; {platform.machine()}) ' \
  450. f'Python/{platform.python_version()} Core/{Tea.__version__} TeaDSL/2'
  451. @staticmethod
  452. def get_user_agent(
  453. user_agent: str,
  454. ) -> str:
  455. """
  456. Get user agent, if it userAgent is not null, splice it with defaultUserAgent and return, otherwise return defaultUserAgent
  457. @return: the string value
  458. """
  459. if user_agent:
  460. return f'{Utils.__get_default_agent()} {user_agent}'
  461. return Utils.__get_default_agent()
  462. @staticmethod
  463. def get_endpoint_rules(product, region_id, endpoint_type, network, suffix=None):
  464. product = product or ""
  465. network = network or ""
  466. if endpoint_type == "regional":
  467. if region_id is None or region_id == "":
  468. raise RuntimeError(
  469. "RegionId is empty, please set a valid RegionId")
  470. result = "<product><network>.<region_id>.aliyuncs.com".replace(
  471. "<region_id>", region_id)
  472. else:
  473. result = "<product><network>.aliyuncs.com"
  474. result = result.replace("<product>", product.lower())
  475. if network == "" or network == "public":
  476. result = result.replace("<network>", "")
  477. else:
  478. result = result.replace("<network>", "-"+network)
  479. return result
  480. @staticmethod
  481. def get_throttling_time_left(headers: Dict[str, str]) -> int:
  482. retry_after = headers.get('x-acs-retry-after')
  483. if retry_after is None or retry_after == '':
  484. return None
  485. try:
  486. time_left = int(retry_after)
  487. except (TypeError, ValueError):
  488. return None
  489. # Only a positive wait time is valid; missing/empty/invalid/<=0 → None
  490. # so callers' is_null check won't treat 0 as a usable backoff.
  491. if time_left <= 0:
  492. return None
  493. return time_left
  494. @staticmethod
  495. def _get_time_left(rate_limit: str) -> int:
  496. """
  497. Extract time left from rate limit string
  498. @param rate_limit: Rate limit string from headers
  499. @return: Time left in milliseconds
  500. """
  501. if rate_limit:
  502. pairs = rate_limit.split(',')
  503. for pair in pairs:
  504. key, value = pair.split(':')
  505. if key.strip() == 'TimeLeft':
  506. return int(value.strip())
  507. return 0
  508. @staticmethod
  509. def flat_map(params: Dict[str, Any], prefix: str = '') -> Dict[str, str]:
  510. """
  511. Flatten the dictionary with a given prefix
  512. @param params: Dictionary to flatten
  513. @param prefix: Prefix for keys in the flattened dictionary
  514. @return: A flattened dictionary
  515. """
  516. flat_result = {}
  517. def _flatten(current_params, current_prefix):
  518. if isinstance(current_params, dict):
  519. for k, v in current_params.items():
  520. new_key = f"{current_prefix}.{k}" if current_prefix else k
  521. _flatten(v, new_key)
  522. elif isinstance(current_params, list):
  523. for index, item in enumerate(current_params):
  524. new_key = f"{current_prefix}.{index + 1}"
  525. _flatten(item, new_key)
  526. else:
  527. flat_result[current_prefix] = str(current_params)
  528. _flatten(params, prefix)
  529. return flat_result
  530. @staticmethod
  531. def map_to_flat_style(input: Any) -> Any:
  532. """
  533. Transform a map to a flat style map where keys are prefixed with length info.
  534. Map keys are transformed from "key" to "#length#key" format.
  535. @param input: Input to convert
  536. @return: A flat representation of the input
  537. """
  538. if input is None:
  539. return input
  540. # Handle list
  541. if isinstance(input, list):
  542. result = []
  543. for item in input:
  544. result.append(Utils.map_to_flat_style(item))
  545. return result
  546. # Handle DaraModel - modify original object's fields
  547. if isinstance(input, DaraModel):
  548. # Use reflection to modify fields directly
  549. for attr_name in dir(input):
  550. # Skip private and special attributes
  551. if attr_name.startswith('_'):
  552. continue
  553. try:
  554. attr_value = getattr(input, attr_name)
  555. # Skip methods and non-data attributes
  556. if callable(attr_value):
  557. continue
  558. # If attribute is a dict, apply flat style to its keys
  559. if isinstance(attr_value, dict):
  560. flat_map = {}
  561. for key, value in attr_value.items():
  562. flat_key = f"#{len(key)}#{key}"
  563. flat_map[flat_key] = Utils.map_to_flat_style(value)
  564. setattr(input, attr_name, flat_map)
  565. else:
  566. # Recursively process other fields
  567. setattr(input, attr_name, Utils.map_to_flat_style(attr_value))
  568. except (AttributeError, TypeError):
  569. # Skip attributes that can't be accessed or set
  570. continue
  571. return input # Return the modified original object
  572. # Handle plain dict - transform ALL keys
  573. if isinstance(input, dict):
  574. result = {}
  575. for key, value in input.items():
  576. flat_key = f"#{len(key)}#{key}"
  577. result[flat_key] = Utils.map_to_flat_style(value)
  578. return result
  579. # For primitive types, return as-is
  580. return input