dashscope 1.23.6__py3-none-any.whl → 1.23.7__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.

Potentially problematic release.


This version of dashscope might be problematic. Click here for more details.

@@ -1,5 +1,4 @@
1
1
  # Copyright (c) Alibaba, Inc. and its affiliates.
2
-
3
2
  from urllib.parse import urlencode
4
3
 
5
4
  import dashscope
@@ -10,8 +9,9 @@ from dashscope.common.constants import (REQUEST_TIMEOUT_KEYWORD,
10
9
  SERVICE_API_PATH, ApiProtocol,
11
10
  HTTPMethod)
12
11
  from dashscope.common.error import InputDataRequired, UnsupportedApiProtocol
12
+ from dashscope.common.logging import logger
13
13
  from dashscope.protocol.websocket import WebsocketStreamingMode
14
-
14
+ from dashscope.api_entities.encryption import Encryption
15
15
 
16
16
  def _get_protocol_params(kwargs):
17
17
  api_protocol = kwargs.pop('api_protocol', ApiProtocol.HTTPS)
@@ -49,6 +49,9 @@ def _build_api_request(model: str,
49
49
  base_address, flattened_output,
50
50
  extra_url_parameters) = _get_protocol_params(kwargs)
51
51
  task_id = kwargs.pop('task_id', None)
52
+ enable_encryption = kwargs.pop('enable_encryption', False)
53
+ encryption = None
54
+
52
55
  if api_protocol in [ApiProtocol.HTTP, ApiProtocol.HTTPS]:
53
56
  if base_address is None:
54
57
  base_address = dashscope.base_http_api_url
@@ -69,6 +72,12 @@ def _build_api_request(model: str,
69
72
  if extra_url_parameters is not None and extra_url_parameters:
70
73
  http_url += '?' + urlencode(extra_url_parameters)
71
74
 
75
+ if enable_encryption is True:
76
+ encryption = Encryption()
77
+ encryption.initialize()
78
+ if encryption.is_valid():
79
+ logger.debug('encryption enabled')
80
+
72
81
  request = HttpRequest(url=http_url,
73
82
  api_key=api_key,
74
83
  http_method=http_method,
@@ -77,7 +86,8 @@ def _build_api_request(model: str,
77
86
  query=query,
78
87
  timeout=request_timeout,
79
88
  task_id=task_id,
80
- flattened_output=flattened_output)
89
+ flattened_output=flattened_output,
90
+ encryption=encryption)
81
91
  elif api_protocol == ApiProtocol.WEBSOCKET:
82
92
  if base_address is not None:
83
93
  websocket_url = base_address
@@ -103,6 +113,9 @@ def _build_api_request(model: str,
103
113
  if input is None and form is None:
104
114
  raise InputDataRequired('There is no input data and form data')
105
115
 
116
+ if encryption and encryption.is_valid():
117
+ input = encryption.encrypt(input)
118
+
106
119
  request_data = ApiRequestData(model,
107
120
  task_group=task_group,
108
121
  task=task,
@@ -0,0 +1,179 @@
1
+ # Copyright (c) Alibaba, Inc. and its affiliates.
2
+ import base64
3
+ import json
4
+ from dataclasses import dataclass
5
+ import os
6
+ from typing import Optional
7
+
8
+ import requests
9
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
10
+ from cryptography.hazmat.primitives import serialization, hashes
11
+ from cryptography.hazmat.primitives.asymmetric import padding
12
+ from cryptography.hazmat.backends import default_backend
13
+
14
+ import dashscope
15
+ from dashscope.common.constants import ENCRYPTION_AES_SECRET_KEY_BYTES, ENCRYPTION_AES_IV_LENGTH
16
+ from dashscope.common.logging import logger
17
+
18
+
19
+ class Encryption:
20
+ def __init__(self):
21
+ self.pub_key_id: str = ''
22
+ self.pub_key_str: str = ''
23
+ self.aes_key_bytes: bytes = b''
24
+ self.encrypted_aes_key_str: str = ''
25
+ self.iv_bytes: bytes = b''
26
+ self.base64_iv_str: str = ''
27
+ self.valid: bool = False
28
+
29
+ def initialize(self):
30
+ public_keys = self._get_public_keys()
31
+ if not public_keys:
32
+ return
33
+
34
+ public_key_str = public_keys.get('public_key')
35
+ public_key_id = public_keys.get('public_key_id')
36
+ if not public_key_str or not public_key_id:
37
+ logger.error("public keys data not valid")
38
+ return
39
+
40
+ aes_key_bytes = self._generate_aes_secret_key()
41
+ iv_bytes = self._generate_iv()
42
+
43
+ encrypted_aes_key_str = self._encrypt_aes_key_with_rsa(aes_key_bytes, public_key_str)
44
+ base64_iv_str = base64.b64encode(iv_bytes).decode('utf-8')
45
+
46
+ self.pub_key_id = public_key_id
47
+ self.pub_key_str = public_key_str
48
+ self.aes_key_bytes = aes_key_bytes
49
+ self.encrypted_aes_key_str = encrypted_aes_key_str
50
+ self.iv_bytes = iv_bytes
51
+ self.base64_iv_str = base64_iv_str
52
+
53
+ self.valid = True
54
+
55
+ def encrypt(self, dict_plaintext):
56
+ return self._encrypt_text_with_aes(json.dumps(dict_plaintext, ensure_ascii=False),
57
+ self.aes_key_bytes, self.iv_bytes)
58
+
59
+ def decrypt(self, base64_ciphertext):
60
+ return self._decrypt_text_with_aes(base64_ciphertext, self.aes_key_bytes, self.iv_bytes)
61
+
62
+ def is_valid(self):
63
+ return self.valid
64
+
65
+ def get_pub_key_id(self):
66
+ return self.pub_key_id
67
+
68
+ def get_encrypted_aes_key_str(self):
69
+ return self.encrypted_aes_key_str
70
+
71
+ def get_base64_iv_str(self):
72
+ return self.base64_iv_str
73
+
74
+ @staticmethod
75
+ def _get_public_keys():
76
+ url = dashscope.base_http_api_url + '/public-keys/latest'
77
+ headers = {
78
+ "Authorization": f"Bearer {dashscope.api_key}"
79
+ }
80
+
81
+ response = requests.get(url, headers=headers)
82
+ if response.status_code != 200:
83
+ logger.error("exceptional public key response: %s" % response)
84
+ return None
85
+
86
+ json_resp = response.json()
87
+ response_data = json_resp.get('data')
88
+
89
+ if not response_data:
90
+ logger.error("no valid data in public key response")
91
+ return None
92
+
93
+ return response_data
94
+
95
+ @staticmethod
96
+ def _generate_aes_secret_key():
97
+ return os.urandom(ENCRYPTION_AES_SECRET_KEY_BYTES)
98
+
99
+ @staticmethod
100
+ def _generate_iv():
101
+ return os.urandom(ENCRYPTION_AES_IV_LENGTH)
102
+
103
+ @staticmethod
104
+ def _encrypt_text_with_aes(plaintext, key, iv):
105
+ """使用AES-GCM加密数据"""
106
+
107
+ # 创建AES-GCM加密器
108
+ aes_gcm = Cipher(
109
+ algorithms.AES(key),
110
+ modes.GCM(iv, tag=None),
111
+ backend=default_backend()
112
+ ).encryptor()
113
+
114
+ # 关联数据设为空(根据需求可调整)
115
+ aes_gcm.authenticate_additional_data(b'')
116
+
117
+ # 加密数据
118
+ ciphertext = aes_gcm.update(plaintext.encode('utf-8')) + aes_gcm.finalize()
119
+
120
+ # 获取认证标签
121
+ tag = aes_gcm.tag
122
+
123
+ # 组合密文和标签
124
+ encrypted_data = ciphertext + tag
125
+
126
+ # 返回Base64编码结果
127
+ return base64.b64encode(encrypted_data).decode('utf-8')
128
+
129
+ @staticmethod
130
+ def _decrypt_text_with_aes(base64_ciphertext, aes_key, iv):
131
+ """使用AES-GCM解密响应"""
132
+
133
+ # 解码Base64数据
134
+ encrypted_data = base64.b64decode(base64_ciphertext)
135
+
136
+ # 分离密文和标签(标签长度16字节)
137
+ ciphertext = encrypted_data[:-16]
138
+ tag = encrypted_data[-16:]
139
+
140
+ # 创建AES-GCM解密器
141
+ aes_gcm = Cipher(
142
+ algorithms.AES(aes_key),
143
+ modes.GCM(iv, tag),
144
+ backend=default_backend()
145
+ ).decryptor()
146
+
147
+ # 验证关联数据(与加密时一致)
148
+ aes_gcm.authenticate_additional_data(b'')
149
+
150
+ # 解密数据
151
+ decrypted_bytes = aes_gcm.update(ciphertext) + aes_gcm.finalize()
152
+
153
+ # 明文
154
+ plaintext = decrypted_bytes.decode('utf-8')
155
+
156
+ return json.loads(plaintext)
157
+
158
+ @staticmethod
159
+ def _encrypt_aes_key_with_rsa(aes_key, public_key_str):
160
+ """使用RSA公钥加密AES密钥"""
161
+
162
+ # 解码Base64格式的公钥
163
+ public_key_bytes = base64.b64decode(public_key_str)
164
+
165
+ # 加载公钥
166
+ public_key = serialization.load_der_public_key(
167
+ public_key_bytes,
168
+ backend=default_backend()
169
+ )
170
+
171
+ base64_aes_key = base64.b64encode(aes_key).decode('utf-8')
172
+
173
+ # 使用RSA加密
174
+ encrypted_bytes = public_key.encrypt(
175
+ base64_aes_key.encode('utf-8'),
176
+ padding.PKCS1v15()
177
+ )
178
+
179
+ return base64.b64encode(encrypted_bytes).decode('utf-8')
@@ -2,6 +2,7 @@
2
2
 
3
3
  import json
4
4
  from http import HTTPStatus
5
+ from typing import Optional
5
6
 
6
7
  import aiohttp
7
8
  import requests
@@ -16,6 +17,7 @@ from dashscope.common.utils import (_handle_aio_stream,
16
17
  _handle_aiohttp_failed_response,
17
18
  _handle_http_failed_response,
18
19
  _handle_stream)
20
+ from dashscope.api_entities.encryption import Encryption
19
21
 
20
22
 
21
23
  class HttpRequest(AioBaseRequest):
@@ -28,7 +30,8 @@ class HttpRequest(AioBaseRequest):
28
30
  query: bool = False,
29
31
  timeout: int = DEFAULT_REQUEST_TIMEOUT_SECONDS,
30
32
  task_id: str = None,
31
- flattened_output: bool = False) -> None:
33
+ flattened_output: bool = False,
34
+ encryption: Optional[Encryption] = None) -> None:
32
35
  """HttpSSERequest, processing http server sent event stream.
33
36
 
34
37
  Args:
@@ -44,11 +47,23 @@ class HttpRequest(AioBaseRequest):
44
47
  self.url = url
45
48
  self.flattened_output = flattened_output
46
49
  self.async_request = async_request
50
+ self.encryption = encryption
47
51
  self.headers = {
48
52
  'Accept': 'application/json',
49
53
  'Authorization': 'Bearer %s' % api_key,
50
54
  **self.headers,
51
55
  }
56
+
57
+ if encryption and encryption.is_valid():
58
+ self.headers = {
59
+ "X-DashScope-EncryptionKey": json.dumps({
60
+ "public_key_id": encryption.get_pub_key_id(),
61
+ "encrypt_key": encryption.get_encrypted_aes_key_str(),
62
+ "iv": encryption.get_base64_iv_str()
63
+ }),
64
+ **self.headers,
65
+ }
66
+
52
67
  self.query = query
53
68
  if self.async_request and self.query is False:
54
69
  self.headers = {
@@ -168,6 +183,8 @@ class HttpRequest(AioBaseRequest):
168
183
  code=msg['code'],
169
184
  message=msg['message'])
170
185
  else:
186
+ if self.encryption and self.encryption.is_valid():
187
+ output = self.encryption.decrypt(output)
171
188
  yield DashScopeAPIResponse(request_id=request_id,
172
189
  status_code=HTTPStatus.OK,
173
190
  output=output,
@@ -183,6 +200,8 @@ class HttpRequest(AioBaseRequest):
183
200
  output[part.name] = await part.read()
184
201
  if 'request_id' in output:
185
202
  request_id = output['request_id']
203
+ if self.encryption and self.encryption.is_valid():
204
+ output = self.encryption.decrypt(output)
186
205
  yield DashScopeAPIResponse(request_id=request_id,
187
206
  status_code=HTTPStatus.OK,
188
207
  output=output)
@@ -196,6 +215,8 @@ class HttpRequest(AioBaseRequest):
196
215
  usage = json_content['usage']
197
216
  if 'request_id' in json_content:
198
217
  request_id = json_content['request_id']
218
+ if self.encryption and self.encryption.is_valid():
219
+ output = self.encryption.decrypt(output)
199
220
  yield DashScopeAPIResponse(request_id=request_id,
200
221
  status_code=HTTPStatus.OK,
201
222
  output=output,
@@ -243,6 +264,8 @@ class HttpRequest(AioBaseRequest):
243
264
  if self.flattened_output:
244
265
  yield msg
245
266
  else:
267
+ if self.encryption and self.encryption.is_valid():
268
+ output = self.encryption.decrypt(output)
246
269
  yield DashScopeAPIResponse(request_id=request_id,
247
270
  status_code=HTTPStatus.OK,
248
271
  output=output,
@@ -263,6 +286,8 @@ class HttpRequest(AioBaseRequest):
263
286
  if self.flattened_output:
264
287
  yield json_content
265
288
  else:
289
+ if self.encryption and self.encryption.is_valid():
290
+ output = self.encryption.decrypt(output)
266
291
  yield DashScopeAPIResponse(request_id=request_id,
267
292
  status_code=HTTPStatus.OK,
268
293
  output=output,
@@ -37,6 +37,9 @@ REQUEST_CONTENT_IMAGE = 'image'
37
37
  REQUEST_CONTENT_AUDIO = 'audio'
38
38
  FILE_PATH_SCHEMA = 'file://'
39
39
 
40
+ ENCRYPTION_AES_SECRET_KEY_BYTES = 32
41
+ ENCRYPTION_AES_IV_LENGTH = 12
42
+
40
43
  REPEATABLE_STATUS = [
41
44
  HTTPStatus.SERVICE_UNAVAILABLE, HTTPStatus.GATEWAY_TIMEOUT
42
45
  ]
dashscope/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  # Copyright (c) Alibaba, Inc. and its affiliates.
2
2
 
3
- __version__ = '1.23.6'
3
+ __version__ = '1.23.7'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dashscope
3
- Version: 1.23.6
3
+ Version: 1.23.7
4
4
  Summary: dashscope client sdk library
5
5
  Home-page: https://dashscope.aliyun.com/
6
6
  Author: Alibaba Cloud
@@ -22,6 +22,7 @@ License-File: LICENSE
22
22
  Requires-Dist: aiohttp
23
23
  Requires-Dist: requests
24
24
  Requires-Dist: websocket-client
25
+ Requires-Dist: cryptography
25
26
  Provides-Extra: tokenizer
26
27
  Requires-Dist: tiktoken; extra == "tokenizer"
27
28
  Dynamic: author
@@ -3,7 +3,7 @@ dashscope/cli.py,sha256=amegoTkGOs6TlHMdoo4JVOqBePo3lGs745rc7leEyrE,24020
3
3
  dashscope/files.py,sha256=vRDQygm3lOqBZR73o7KNHs1iTBVuvLncuwJNxIYjzAU,3981
4
4
  dashscope/model.py,sha256=B5v_BtYLPqj6raClejBgdKg6WTGwhH_f-20pvsQqmsk,1491
5
5
  dashscope/models.py,sha256=dE4mzXkl85G343qVylSGpURPRdA5pZSqXlx6PcxqC_Q,1275
6
- dashscope/version.py,sha256=VtJV154d4VNke5BkkccDtVlsLdOM6gdSce36vOpcfbU,74
6
+ dashscope/version.py,sha256=HwgMDxa-K4tdaSJnt84_qDc7qTyV4eqR_QjdjT4ulxc,74
7
7
  dashscope/aigc/__init__.py,sha256=AuRhu_vA1K0tbs_C6DgcZYhTvxMuzDgpwHJNHzEPIHg,442
8
8
  dashscope/aigc/chat_completion.py,sha256=ONlyyssIbfaKKcFo7cEKhHx5OCF2XX810HFzIExW1ho,14813
9
9
  dashscope/aigc/code_generation.py,sha256=p_mxDKJLQMW0IjFD46JRlZuEZCRESSVKEfLlAevBtqw,10936
@@ -15,11 +15,12 @@ dashscope/aigc/video_synthesis.py,sha256=XQ3-NKYFmj5cIbUbLTbI0-FyC_fQp8eds6QmD1Z
15
15
  dashscope/api_entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  dashscope/api_entities/aiohttp_request.py,sha256=1L7XdIJ9L65cQmX8x9JCR4t5hNIMDrbiWADfKKp9yfo,10280
17
17
  dashscope/api_entities/api_request_data.py,sha256=04rpYPNK1HkT3iTPJmZpquH621xcBbe8R8EGrDJSLt0,5514
18
- dashscope/api_entities/api_request_factory.py,sha256=yRzRKlFeoEyTyiXwyw_1hTKbkf6Sb5SaKZJU--Gfm3w,5112
18
+ dashscope/api_entities/api_request_factory.py,sha256=18A40aHL0t3s01VdbkIWRGNeVJyX0GXRHTZUxau7po4,5640
19
19
  dashscope/api_entities/base_request.py,sha256=W2SzrSAGFS6V8DErfSrayQtSL0T4iO7BrC8flr7nt1w,977
20
20
  dashscope/api_entities/chat_completion_types.py,sha256=1WMWPszhM3HaJBVz-ZXx-El4D8-RfVUL3ym65xsDRLk,11435
21
21
  dashscope/api_entities/dashscope_response.py,sha256=qNNB86h5Gb_4uHjBD_4lx6UckyQzSdaTgjze1prc12M,22073
22
- dashscope/api_entities/http_request.py,sha256=vgfykwSOdPom4bQVFOtXIEPYTuE_4QVNGtZ2EJ0t9lM,13310
22
+ dashscope/api_entities/encryption.py,sha256=rUCZx3wwVvS5oyKXEeWgyWPxM8Y5d4AaVdgxLhizBqA,5517
23
+ dashscope/api_entities/http_request.py,sha256=p2xfmq79evNON4ctCVXCcrJo8jnKABn0XzdTkTDgbLM,14540
23
24
  dashscope/api_entities/websocket_request.py,sha256=PS0FU854-HjTbKa68f4GHa7-noFRMzKySJGfPkrrBjw,16146
24
25
  dashscope/app/__init__.py,sha256=xvSvU8O7m5u7vgIvJXTJektJZxmjT2Rpt_YwePH88XE,113
25
26
  dashscope/app/application.py,sha256=Whf_ij4RHOaY12_xdS8uj8HVNCwkTp_MRdrFTryF1Kg,9472
@@ -47,7 +48,7 @@ dashscope/client/base_api.py,sha256=aWNy_xm02GXuLKVgWnYJht2nI4ZHSGfYIcr52SML15A,
47
48
  dashscope/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
49
  dashscope/common/api_key.py,sha256=yqFCAteq8CNQGnlLv6fxNFWsLqsQDbSzOpgAlUmDkaE,2037
49
50
  dashscope/common/base_type.py,sha256=2OQDqFlEH43wn54i-691cbarV_eKRLvRsPGfyb_GS0g,4670
50
- dashscope/common/constants.py,sha256=ULmR3ZENW1gIWRZLmUKKscRQ1vQvhw4EoTxvNbrgMmw,2378
51
+ dashscope/common/constants.py,sha256=Ry3IBz2w9amRoRmfwC5L1dXHb7Iz2slj_I_wobmo-6Q,2446
51
52
  dashscope/common/env.py,sha256=9yWWdKqfYuHlTQSvbTBaQhGbASh5Lq6SbM9pPx8hB40,920
52
53
  dashscope/common/error.py,sha256=sXQqBGWCUBPyKa5rAI6DWc0sEidH01sR8zlIBfrTTDU,2690
53
54
  dashscope/common/logging.py,sha256=lX86X9ND1MC5mA_qKAktwaVXd_BufLgmSGPggUiEJZo,1035
@@ -92,9 +93,9 @@ dashscope/tokenizers/tokenizer.py,sha256=3FQVDvMNkCW9ccYeJdjrd_PIMMD3Xv7aNZkaYOE
92
93
  dashscope/tokenizers/tokenizer_base.py,sha256=5EJIFuizMWESEmLmbd38yJnfeHmPnzZPwsO4aOGjpl4,707
93
94
  dashscope/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
95
  dashscope/utils/oss_utils.py,sha256=L5LN3lN8etVxSL_jkZydstvEKpnTG9CY0zcvPGQ5LBo,7383
95
- dashscope-1.23.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
96
- dashscope-1.23.6.dist-info/METADATA,sha256=l1m0KB4CgXdVltrY8r0NwntIgRE3-a5MKApSuhy8pks,7095
97
- dashscope-1.23.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
98
- dashscope-1.23.6.dist-info/entry_points.txt,sha256=e9C3sOf9zDYL0O5ROEGX6FT8w-QK_kaGRWmPZDHAFys,49
99
- dashscope-1.23.6.dist-info/top_level.txt,sha256=woqavFJK9zas5xTqynmALqOtlafghjsk63Xk86powTU,10
100
- dashscope-1.23.6.dist-info/RECORD,,
96
+ dashscope-1.23.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
97
+ dashscope-1.23.7.dist-info/METADATA,sha256=JxFpTi5zU9f1O1uLoTH1OoV6iV3K3aAqshhD1MTDtFY,7123
98
+ dashscope-1.23.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
99
+ dashscope-1.23.7.dist-info/entry_points.txt,sha256=e9C3sOf9zDYL0O5ROEGX6FT8w-QK_kaGRWmPZDHAFys,49
100
+ dashscope-1.23.7.dist-info/top_level.txt,sha256=woqavFJK9zas5xTqynmALqOtlafghjsk63Xk86powTU,10
101
+ dashscope-1.23.7.dist-info/RECORD,,