utils.py 21 KB

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