tencentcloud-sdk-python-common 3.0.1457__tar.gz → 3.1.21__tar.gz
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 tencentcloud-sdk-python-common might be problematic. Click here for more details.
- tencentcloud_sdk_python_common-3.1.21/PKG-INFO +48 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/setup.py +1 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/__init__.py +1 -1
- tencentcloud_sdk_python_common-3.1.21/tencentcloud/common/abstract_client_async.py +654 -0
- tencentcloud_sdk_python_common-3.1.21/tencentcloud/common/common_client_async.py +45 -0
- tencentcloud_sdk_python_common-3.1.21/tencentcloud/common/http/request_async.py +62 -0
- tencentcloud_sdk_python_common-3.1.21/tencentcloud/common/retry_async.py +87 -0
- tencentcloud_sdk_python_common-3.1.21/tencentcloud_sdk_python_common.egg-info/PKG-INFO +48 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud_sdk_python_common.egg-info/SOURCES.txt +4 -0
- tencentcloud_sdk_python_common-3.1.21/tencentcloud_sdk_python_common.egg-info/requires.txt +4 -0
- tencentcloud-sdk-python-common-3.0.1457/PKG-INFO +0 -45
- tencentcloud-sdk-python-common-3.0.1457/tencentcloud_sdk_python_common.egg-info/PKG-INFO +0 -45
- tencentcloud-sdk-python-common-3.0.1457/tencentcloud_sdk_python_common.egg-info/requires.txt +0 -1
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/README.rst +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/setup.cfg +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/__init__.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/abstract_client.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/abstract_model.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/circuit_breaker.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/common_client.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/credential.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/exception/__init__.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/exception/tencent_cloud_sdk_exception.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/http/__init__.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/http/pre_conn.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/http/request.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/profile/__init__.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/profile/client_profile.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/profile/http_profile.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/retry.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud/common/sign.py +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud_sdk_python_common.egg-info/dependency_links.txt +0 -0
- {tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/tencentcloud_sdk_python_common.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: tencentcloud-sdk-python-common
|
|
3
|
+
Version: 3.1.21
|
|
4
|
+
Summary: Tencent Cloud Common SDK for Python
|
|
5
|
+
Home-page: https://github.com/TencentCloud/tencentcloud-sdk-python
|
|
6
|
+
Author: Tencent Cloud
|
|
7
|
+
Maintainer-email: tencentcloudapi@tencent.com
|
|
8
|
+
License: Apache License 2.0
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
+
Requires-Dist: requests>=2.16.0
|
|
19
|
+
Provides-Extra: async
|
|
20
|
+
Requires-Dist: httpx>=0.22.0; extra == "async"
|
|
21
|
+
|
|
22
|
+
============================
|
|
23
|
+
Tencent Cloud SDK for Python
|
|
24
|
+
============================
|
|
25
|
+
|
|
26
|
+
Tencent Cloud Python Common SDK is the official software development kit, which allows Python developers to write software that makes use of Tencent Cloud services like CVM and CBS.
|
|
27
|
+
The SDK works on Python versions:
|
|
28
|
+
|
|
29
|
+
* 2.7 and greater, including 3.x
|
|
30
|
+
|
|
31
|
+
Quick Start
|
|
32
|
+
-----------
|
|
33
|
+
|
|
34
|
+
First, install the library:
|
|
35
|
+
|
|
36
|
+
.. code-block:: sh
|
|
37
|
+
|
|
38
|
+
$ pip install tencentcloud-sdk-python-common
|
|
39
|
+
$ pip install tencentcloud-sdk-python-common
|
|
40
|
+
|
|
41
|
+
or download source code from github and install:
|
|
42
|
+
|
|
43
|
+
.. code-block:: sh
|
|
44
|
+
|
|
45
|
+
$ git clone https://github.com/tencentcloud/tencentcloud-sdk-python.git
|
|
46
|
+
$ cd tencentcloud-sdk-python
|
|
47
|
+
$ python package.py --components common common
|
|
48
|
+
|
|
@@ -9,6 +9,7 @@ ROOT = os.path.dirname(__file__)
|
|
|
9
9
|
setup(
|
|
10
10
|
name='tencentcloud-sdk-python-common',
|
|
11
11
|
install_requires=["requests>=2.16.0"],
|
|
12
|
+
extras_require={"async": ["httpx>=0.22.0"]},
|
|
12
13
|
version=tencentcloud.__version__,
|
|
13
14
|
description='Tencent Cloud Common SDK for Python',
|
|
14
15
|
long_description=open('README.rst').read(),
|
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2017-2021 Tencent Ltd.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
import copy
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
import logging.handlers
|
|
21
|
+
import random
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
import uuid
|
|
25
|
+
import warnings
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
from typing import Dict, Type, Union, List, Callable, Awaitable, Optional
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
import tencentcloud
|
|
32
|
+
from tencentcloud.common.abstract_client import logger, urlparse, urlencode
|
|
33
|
+
from tencentcloud.common.abstract_model import AbstractModel
|
|
34
|
+
from tencentcloud.common.circuit_breaker import CircuitBreaker
|
|
35
|
+
from tencentcloud.common.credential import Credential
|
|
36
|
+
from tencentcloud.common.exception import TencentCloudSDKException
|
|
37
|
+
from tencentcloud.common.http.request_async import ApiRequest, ApiResponse, ResponsePrettyFormatter, \
|
|
38
|
+
RequestPrettyFormatter
|
|
39
|
+
from tencentcloud.common.profile.client_profile import ClientProfile, RegionBreakerProfile
|
|
40
|
+
from tencentcloud.common.retry_async import NoopRetryer
|
|
41
|
+
from tencentcloud.common.sign import Sign
|
|
42
|
+
|
|
43
|
+
warnings.filterwarnings("ignore", module="tencentcloud", category=UserWarning)
|
|
44
|
+
|
|
45
|
+
_json_content = 'application/json'
|
|
46
|
+
_multipart_content = 'multipart/form-data'
|
|
47
|
+
_form_urlencoded_content = 'application/x-www-form-urlencoded'
|
|
48
|
+
_octet_stream = "application/octet-stream"
|
|
49
|
+
|
|
50
|
+
LOGGER_NAME = "tencentcloud_sdk_common"
|
|
51
|
+
|
|
52
|
+
InterceptorType = Callable[["RequestChain"], Awaitable]
|
|
53
|
+
ParamsType = Union[Dict, str, bytes]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RequestChain(object):
|
|
57
|
+
def __init__(self):
|
|
58
|
+
self.request: Optional[ApiRequest] = None
|
|
59
|
+
|
|
60
|
+
self._inters: List[InterceptorType] = []
|
|
61
|
+
self._idx = 0
|
|
62
|
+
|
|
63
|
+
async def proceed(self):
|
|
64
|
+
interceptor = self._inters[self._idx]
|
|
65
|
+
snapshot = self._new_snapshot()
|
|
66
|
+
snapshot._idx += 1
|
|
67
|
+
return await interceptor(snapshot)
|
|
68
|
+
|
|
69
|
+
def add_interceptor(self, inter: InterceptorType, idx=sys.maxsize):
|
|
70
|
+
self._inters.insert(idx, inter)
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def _new_snapshot(self) -> 'RequestChain':
|
|
74
|
+
new_chain = RequestChain()
|
|
75
|
+
new_chain.request = self.request
|
|
76
|
+
new_chain._inters = self._inters
|
|
77
|
+
new_chain._idx = self._idx
|
|
78
|
+
return new_chain
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AbstractClient(object):
|
|
82
|
+
_requestPath = '/'
|
|
83
|
+
_params = {}
|
|
84
|
+
_apiVersion = ''
|
|
85
|
+
_endpoint = ''
|
|
86
|
+
_service = ''
|
|
87
|
+
_sdkVersion = 'SDK_PYTHON_%s' % tencentcloud.__version__
|
|
88
|
+
_default_content_type = _form_urlencoded_content
|
|
89
|
+
FMT = '%(asctime)s %(process)d %(filename)s L%(lineno)s %(levelname)s %(message)s'
|
|
90
|
+
|
|
91
|
+
def __init__(self, credential: Credential, region: str, profile: ClientProfile = None):
|
|
92
|
+
self.credential = credential
|
|
93
|
+
self.region = region
|
|
94
|
+
self.profile = profile or ClientProfile()
|
|
95
|
+
self.circuit_breaker = None
|
|
96
|
+
|
|
97
|
+
kwargs: Dict = {"timeout": self.profile.httpProfile.reqTimeout}
|
|
98
|
+
|
|
99
|
+
if not self.profile.httpProfile.keepAlive:
|
|
100
|
+
kwargs["limits"] = httpx.Limits(max_keepalive_connections=0)
|
|
101
|
+
|
|
102
|
+
if self.profile.httpProfile.proxy:
|
|
103
|
+
kwargs["proxies"] = self.profile.httpProfile.proxy
|
|
104
|
+
|
|
105
|
+
if self.profile.httpProfile.certification is False:
|
|
106
|
+
kwargs["verify"] = False
|
|
107
|
+
elif isinstance(self.profile.httpProfile.certification, str) and self.profile.httpProfile.certification != "":
|
|
108
|
+
kwargs["cert"] = self.profile.httpProfile.certification
|
|
109
|
+
|
|
110
|
+
self.http_client = httpx.AsyncClient(**kwargs)
|
|
111
|
+
|
|
112
|
+
if not self.profile.disable_region_breaker:
|
|
113
|
+
if self.profile.region_breaker_profile is None:
|
|
114
|
+
self.profile.region_breaker_profile = RegionBreakerProfile()
|
|
115
|
+
self.circuit_breaker = CircuitBreaker(self.profile.region_breaker_profile)
|
|
116
|
+
if self.profile.request_client:
|
|
117
|
+
self.request_client = self._sdkVersion + "; " + self.profile.request_client
|
|
118
|
+
else:
|
|
119
|
+
self.request_client = self._sdkVersion
|
|
120
|
+
|
|
121
|
+
async def __aenter__(self):
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
125
|
+
await self.close()
|
|
126
|
+
|
|
127
|
+
async def close(self):
|
|
128
|
+
await self.http_client.aclose()
|
|
129
|
+
|
|
130
|
+
async def call_and_deserialize(
|
|
131
|
+
self,
|
|
132
|
+
action: str,
|
|
133
|
+
params: ParamsType,
|
|
134
|
+
resp_cls: Type = dict,
|
|
135
|
+
headers: Dict[str, str] = None,
|
|
136
|
+
opts: Dict = None,
|
|
137
|
+
):
|
|
138
|
+
opts = opts or {}
|
|
139
|
+
headers = headers or {}
|
|
140
|
+
chain = self._create_default_chain(action, params, resp_cls, headers, opts)
|
|
141
|
+
return await chain.proceed()
|
|
142
|
+
|
|
143
|
+
def _create_default_chain(
|
|
144
|
+
self,
|
|
145
|
+
action: str,
|
|
146
|
+
params: ParamsType,
|
|
147
|
+
resp_cls: Type,
|
|
148
|
+
headers: Dict[str, str],
|
|
149
|
+
opts: Dict,
|
|
150
|
+
):
|
|
151
|
+
chain = RequestChain()
|
|
152
|
+
chain.add_interceptor(self._inter_retry)
|
|
153
|
+
chain.add_interceptor(self._inter_deserialize_resp(resp_cls))
|
|
154
|
+
if self.circuit_breaker:
|
|
155
|
+
chain.add_interceptor(self._inter_breaker(opts))
|
|
156
|
+
chain.add_interceptor(self._inter_build_request(action, params, headers, opts))
|
|
157
|
+
chain.add_interceptor(self._inter_send_request)
|
|
158
|
+
return chain
|
|
159
|
+
|
|
160
|
+
async def _inter_retry(self, chain: RequestChain):
|
|
161
|
+
retryer = self.profile.retryer or NoopRetryer()
|
|
162
|
+
return await retryer.send_request(chain.proceed)
|
|
163
|
+
|
|
164
|
+
def _inter_build_request(
|
|
165
|
+
self,
|
|
166
|
+
action: str,
|
|
167
|
+
params: ParamsType,
|
|
168
|
+
headers: Dict[str, str],
|
|
169
|
+
opts: Dict,
|
|
170
|
+
):
|
|
171
|
+
async def inter(chain: RequestChain):
|
|
172
|
+
nonlocal action, params, opts, headers
|
|
173
|
+
|
|
174
|
+
if headers is None:
|
|
175
|
+
headers = {}
|
|
176
|
+
|
|
177
|
+
if not isinstance(headers, dict):
|
|
178
|
+
raise TencentCloudSDKException("ClientError", "headers must be a dict.")
|
|
179
|
+
|
|
180
|
+
if "x-tc-traceid" not in (k.lower() for k in headers.keys()):
|
|
181
|
+
headers["X-TC-TraceId"] = str(uuid.uuid4())
|
|
182
|
+
|
|
183
|
+
req = self._build_req(action, params, headers, opts)
|
|
184
|
+
|
|
185
|
+
if self.profile.httpProfile.apigw_endpoint:
|
|
186
|
+
req.host = self.profile.httpProfile.apigw_endpoint
|
|
187
|
+
req.headers["Host"] = req.host
|
|
188
|
+
|
|
189
|
+
# 低版本的 httpx 不会使用 Client.timeout, 需要 per request setup
|
|
190
|
+
req.extensions["timeout"] = self.http_client.timeout.as_dict()
|
|
191
|
+
chain.request = req
|
|
192
|
+
|
|
193
|
+
logger.debug("SendRequest:\n%s", RequestPrettyFormatter(req))
|
|
194
|
+
|
|
195
|
+
return await chain.proceed()
|
|
196
|
+
|
|
197
|
+
return inter
|
|
198
|
+
|
|
199
|
+
def _inter_breaker(self, opts: Dict):
|
|
200
|
+
async def inter(chain: RequestChain):
|
|
201
|
+
generation, need_break = self.circuit_breaker.before_requests()
|
|
202
|
+
|
|
203
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
204
|
+
if need_break:
|
|
205
|
+
endpoint = self._service + "." + self.profile.region_breaker_profile.backup_endpoint
|
|
206
|
+
|
|
207
|
+
url = self.profile.httpProfile.scheme + endpoint + self._requestPath
|
|
208
|
+
chain.request.url = url
|
|
209
|
+
|
|
210
|
+
resp = None
|
|
211
|
+
try:
|
|
212
|
+
resp = await chain.proceed()
|
|
213
|
+
self.circuit_breaker.after_requests(generation, True)
|
|
214
|
+
return resp
|
|
215
|
+
except httpx.TransportError:
|
|
216
|
+
self.circuit_breaker.after_requests(generation, False)
|
|
217
|
+
raise
|
|
218
|
+
except TencentCloudSDKException as e:
|
|
219
|
+
success = resp and "RequestId" in (await resp.aread()) and e.code != "InternalError"
|
|
220
|
+
self.circuit_breaker.after_requests(generation, success)
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
return inter
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _inter_deserialize_resp(resp_cls: Type):
|
|
227
|
+
async def inter(chain: RequestChain):
|
|
228
|
+
resp = await chain.proceed()
|
|
229
|
+
|
|
230
|
+
await check_err(resp)
|
|
231
|
+
|
|
232
|
+
content_type = resp.headers["Content-Type"]
|
|
233
|
+
if content_type == "text/event-stream":
|
|
234
|
+
return deserialize_sse(resp)
|
|
235
|
+
|
|
236
|
+
return await deserialize_json(resp)
|
|
237
|
+
|
|
238
|
+
async def check_err(resp: ApiResponse):
|
|
239
|
+
if resp.status_code != 200:
|
|
240
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
241
|
+
logger.debug("GetResponse: %s", await ResponsePrettyFormatter(resp, format_body=True).astr())
|
|
242
|
+
raise TencentCloudSDKException("ServerNetworkError", await resp.aread())
|
|
243
|
+
|
|
244
|
+
ct = resp.headers.get('Content-Type')
|
|
245
|
+
if ct not in ('text/plain', _json_content):
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
content = await resp.aread()
|
|
249
|
+
data = json.loads(content)
|
|
250
|
+
if "Error" in data["Response"]:
|
|
251
|
+
code = data["Response"]["Error"]["Code"]
|
|
252
|
+
message = data["Response"]["Error"]["Message"]
|
|
253
|
+
reqid = data["Response"]["RequestId"]
|
|
254
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
255
|
+
logger.debug("GetResponse: %s", await ResponsePrettyFormatter(resp, format_body=True).astr())
|
|
256
|
+
raise TencentCloudSDKException(code, message, reqid)
|
|
257
|
+
if "DeprecatedWarning" in data["Response"]:
|
|
258
|
+
import warnings
|
|
259
|
+
warnings.filterwarnings("default")
|
|
260
|
+
warnings.warn("This action is deprecated, detail: %s" % data["Response"]["DeprecatedWarning"],
|
|
261
|
+
DeprecationWarning)
|
|
262
|
+
|
|
263
|
+
async def deserialize_json(resp: ApiResponse):
|
|
264
|
+
try:
|
|
265
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
266
|
+
logger.debug("GetResponse: %s", await ResponsePrettyFormatter(resp, format_body=True).astr())
|
|
267
|
+
|
|
268
|
+
content = await resp.aread()
|
|
269
|
+
if resp_cls == dict:
|
|
270
|
+
return json.loads(content)
|
|
271
|
+
|
|
272
|
+
if issubclass(resp_cls, AbstractModel):
|
|
273
|
+
resp_model = resp_cls()
|
|
274
|
+
resp_model._deserialize(json.loads(content)["Response"])
|
|
275
|
+
return resp_model
|
|
276
|
+
|
|
277
|
+
raise TencentCloudSDKException("ClientParamsError", "invalid resp_cls %s" % resp_cls)
|
|
278
|
+
finally:
|
|
279
|
+
await resp.aclose()
|
|
280
|
+
|
|
281
|
+
async def deserialize_sse(resp: ApiResponse):
|
|
282
|
+
logger.debug("GetResponse:\n%s", ResponsePrettyFormatter(resp, format_body=False))
|
|
283
|
+
e = {}
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
async for line in resp.aiter_lines():
|
|
287
|
+
line = line.strip()
|
|
288
|
+
if not line:
|
|
289
|
+
yield e
|
|
290
|
+
e = {}
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
logger.debug("GetResponse.Readline: %s", line)
|
|
294
|
+
|
|
295
|
+
# comment
|
|
296
|
+
if line[0] == ':':
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
colon_idx = line.find(':')
|
|
300
|
+
key = line[:colon_idx]
|
|
301
|
+
val = line[colon_idx + 1:]
|
|
302
|
+
# If value starts with a U+0020 SPACE character, remove it from value.
|
|
303
|
+
if val and val[0] == " ":
|
|
304
|
+
val = val[1:]
|
|
305
|
+
if key == 'data':
|
|
306
|
+
# The spec allows for multiple data fields per event, concatenated them with "\n".
|
|
307
|
+
if 'data' not in e:
|
|
308
|
+
e['data'] = val
|
|
309
|
+
else:
|
|
310
|
+
e['data'] += '\n' + val
|
|
311
|
+
elif key in ('event', 'id'):
|
|
312
|
+
e[key] = val
|
|
313
|
+
elif key == 'retry':
|
|
314
|
+
e[key] = int(val)
|
|
315
|
+
finally:
|
|
316
|
+
await resp.aclose()
|
|
317
|
+
|
|
318
|
+
return inter
|
|
319
|
+
|
|
320
|
+
async def _inter_send_request(self, chain: RequestChain):
|
|
321
|
+
return await self.http_client.send(chain.request, stream=True)
|
|
322
|
+
|
|
323
|
+
def _get_service_domain(self):
|
|
324
|
+
root_domain = self.profile.httpProfile.rootDomain
|
|
325
|
+
return self._service + "." + root_domain
|
|
326
|
+
|
|
327
|
+
def _get_endpoint(self, opts=None):
|
|
328
|
+
endpoint = self.profile.httpProfile.endpoint
|
|
329
|
+
if not endpoint and opts:
|
|
330
|
+
endpoint = urlparse(opts.get("Endpoint", "")).hostname
|
|
331
|
+
if endpoint is None:
|
|
332
|
+
endpoint = self._get_service_domain()
|
|
333
|
+
return endpoint
|
|
334
|
+
|
|
335
|
+
def _build_req(self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
336
|
+
if opts.get('SkipSign'):
|
|
337
|
+
return self._build_req_without_signature(action, params, headers, opts)
|
|
338
|
+
elif self.profile.signMethod == "TC3-HMAC-SHA256" or opts.get("IsMultipart") is True:
|
|
339
|
+
return self._build_req_with_tc3_signature(action, params, headers, opts)
|
|
340
|
+
elif self.profile.signMethod in ("HmacSHA1", "HmacSHA256"):
|
|
341
|
+
return self._build_req_with_old_signature(action, params, headers, opts)
|
|
342
|
+
else:
|
|
343
|
+
raise TencentCloudSDKException("ClientError", "Invalid signature method.")
|
|
344
|
+
|
|
345
|
+
def _build_req_without_signature(
|
|
346
|
+
self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
347
|
+
method = self.profile.httpProfile.reqMethod
|
|
348
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
349
|
+
url = "%s://%s%s" % (self.profile.httpProfile.scheme, endpoint, self._requestPath)
|
|
350
|
+
query = {}
|
|
351
|
+
body = ""
|
|
352
|
+
|
|
353
|
+
content_type = self._default_content_type
|
|
354
|
+
if method == 'GET':
|
|
355
|
+
content_type = _form_urlencoded_content
|
|
356
|
+
elif method == 'POST':
|
|
357
|
+
content_type = _json_content
|
|
358
|
+
if opts.get("IsMultipart"):
|
|
359
|
+
content_type = _multipart_content
|
|
360
|
+
if opts.get("IsOctetStream"):
|
|
361
|
+
content_type = _octet_stream
|
|
362
|
+
headers["Content-Type"] = content_type
|
|
363
|
+
|
|
364
|
+
if method == "GET" and content_type == _multipart_content:
|
|
365
|
+
raise TencentCloudSDKException("ClientError", "Invalid request method GET for multipart.")
|
|
366
|
+
|
|
367
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
368
|
+
timestamp = int(time.time())
|
|
369
|
+
headers["Host"] = endpoint
|
|
370
|
+
headers["X-TC-Action"] = action[0].upper() + action[1:]
|
|
371
|
+
headers["X-TC-RequestClient"] = self.request_client
|
|
372
|
+
headers["X-TC-Timestamp"] = str(timestamp)
|
|
373
|
+
headers["X-TC-Version"] = self._apiVersion
|
|
374
|
+
if self.profile.unsignedPayload is True:
|
|
375
|
+
headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
|
|
376
|
+
if self.region:
|
|
377
|
+
headers['X-TC-Region'] = self.region
|
|
378
|
+
if self.profile.language:
|
|
379
|
+
headers['X-TC-Language'] = self.profile.language
|
|
380
|
+
|
|
381
|
+
if method == 'GET':
|
|
382
|
+
params = copy.deepcopy(self._fix_params(params))
|
|
383
|
+
url += "?" + urlencode(params)
|
|
384
|
+
elif content_type == _json_content:
|
|
385
|
+
body = json.dumps(params)
|
|
386
|
+
elif content_type == _multipart_content:
|
|
387
|
+
boundary = uuid.uuid4().hex
|
|
388
|
+
headers["Content-Type"] = content_type + "; boundary=" + boundary
|
|
389
|
+
body = self._get_multipart_body(params, boundary, opts)
|
|
390
|
+
|
|
391
|
+
headers["Authorization"] = "SKIP"
|
|
392
|
+
return ApiRequest(method, url, params=query, content=body, headers=headers)
|
|
393
|
+
|
|
394
|
+
def _build_req_with_tc3_signature(
|
|
395
|
+
self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
396
|
+
method = self.profile.httpProfile.reqMethod
|
|
397
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
398
|
+
host = headers.get("Host", endpoint)
|
|
399
|
+
url = "%s://%s%s" % (self.profile.httpProfile.scheme, endpoint, self._requestPath)
|
|
400
|
+
query = ""
|
|
401
|
+
body = ""
|
|
402
|
+
|
|
403
|
+
content_type = self._default_content_type
|
|
404
|
+
if method == 'GET':
|
|
405
|
+
content_type = _form_urlencoded_content
|
|
406
|
+
elif method == 'POST':
|
|
407
|
+
content_type = _json_content
|
|
408
|
+
if opts.get("IsMultipart"):
|
|
409
|
+
content_type = _multipart_content
|
|
410
|
+
elif opts.get("IsOctetStream"):
|
|
411
|
+
if method != "POST":
|
|
412
|
+
raise TencentCloudSDKException("ClientError", "Invalid request method.")
|
|
413
|
+
content_type = _octet_stream
|
|
414
|
+
headers["Content-Type"] = content_type
|
|
415
|
+
|
|
416
|
+
if method == "GET" and content_type == _multipart_content:
|
|
417
|
+
raise TencentCloudSDKException("ClientError", "Invalid request method GET for multipart.")
|
|
418
|
+
|
|
419
|
+
timestamp = int(time.time())
|
|
420
|
+
cred_secret_id, cred_secret_key, cred_token = self.credential.get_credential_info()
|
|
421
|
+
headers["Host"] = host
|
|
422
|
+
headers["X-TC-Action"] = action[0].upper() + action[1:]
|
|
423
|
+
headers["X-TC-RequestClient"] = self.request_client
|
|
424
|
+
headers["X-TC-Timestamp"] = str(timestamp)
|
|
425
|
+
headers["X-TC-Version"] = self._apiVersion
|
|
426
|
+
if self.profile.unsignedPayload is True:
|
|
427
|
+
headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
|
|
428
|
+
if self.region:
|
|
429
|
+
headers['X-TC-Region'] = self.region
|
|
430
|
+
if cred_token:
|
|
431
|
+
headers['X-TC-Token'] = cred_token
|
|
432
|
+
if self.profile.language:
|
|
433
|
+
headers['X-TC-Language'] = self.profile.language
|
|
434
|
+
|
|
435
|
+
if method == 'GET':
|
|
436
|
+
query = urlencode(copy.deepcopy(self._fix_params(params)))
|
|
437
|
+
elif content_type == _json_content:
|
|
438
|
+
body = json.dumps(params)
|
|
439
|
+
elif content_type == _multipart_content:
|
|
440
|
+
boundary = uuid.uuid4().hex
|
|
441
|
+
headers["Content-Type"] = content_type + "; boundary=" + boundary
|
|
442
|
+
body = self._get_multipart_body(params, boundary, opts)
|
|
443
|
+
elif content_type == _octet_stream:
|
|
444
|
+
body = params
|
|
445
|
+
|
|
446
|
+
if isinstance(body, str):
|
|
447
|
+
body = body.encode("utf-8")
|
|
448
|
+
|
|
449
|
+
service = self._service
|
|
450
|
+
date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')
|
|
451
|
+
signature = self._get_tc3_signature(
|
|
452
|
+
method, self._requestPath, query, body, headers, date, service, cred_secret_key, opts)
|
|
453
|
+
|
|
454
|
+
auth = "TC3-HMAC-SHA256 Credential=%s/%s/%s/tc3_request, SignedHeaders=content-type;host, Signature=%s" % (
|
|
455
|
+
cred_secret_id, date, service, signature)
|
|
456
|
+
headers["Authorization"] = auth
|
|
457
|
+
# httpx 0.22.0 版本会过滤掉空 value 的 params 如 "a=&b=2"
|
|
458
|
+
# 不能使用 params 参数, 需要用 url 绕过, 后续版本已经修复, 但是 py36 最高只能安装 0.22.0
|
|
459
|
+
url += "?" + query
|
|
460
|
+
return ApiRequest(method, url, content=body, headers=headers)
|
|
461
|
+
|
|
462
|
+
def _build_req_with_old_signature(
|
|
463
|
+
self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
464
|
+
|
|
465
|
+
if opts.get("IsOctetStream"):
|
|
466
|
+
raise TencentCloudSDKException("ClientError", "Invalid signature method.")
|
|
467
|
+
|
|
468
|
+
method = self.profile.httpProfile.reqMethod
|
|
469
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
470
|
+
url = "%s://%s%s" % (self.profile.httpProfile.scheme, endpoint, self._requestPath)
|
|
471
|
+
query = {}
|
|
472
|
+
body = ""
|
|
473
|
+
|
|
474
|
+
params = copy.deepcopy(self._fix_params(params))
|
|
475
|
+
params['Action'] = action[0].upper() + action[1:]
|
|
476
|
+
params['RequestClient'] = self.request_client
|
|
477
|
+
params['Nonce'] = random.randint(1, sys.maxsize)
|
|
478
|
+
params['Timestamp'] = int(time.time())
|
|
479
|
+
params['Version'] = self._apiVersion
|
|
480
|
+
|
|
481
|
+
cred_secret_id, cred_secret_key, cred_token = self.credential.get_credential_info()
|
|
482
|
+
|
|
483
|
+
if self.region:
|
|
484
|
+
params['Region'] = self.region
|
|
485
|
+
|
|
486
|
+
if cred_token:
|
|
487
|
+
params['Token'] = cred_token
|
|
488
|
+
|
|
489
|
+
if cred_secret_id:
|
|
490
|
+
params['SecretId'] = cred_secret_id
|
|
491
|
+
|
|
492
|
+
if self.profile.signMethod:
|
|
493
|
+
params['SignatureMethod'] = self.profile.signMethod
|
|
494
|
+
|
|
495
|
+
if self.profile.language:
|
|
496
|
+
params['Language'] = self.profile.language
|
|
497
|
+
|
|
498
|
+
signInParam = self._format_sign_string(params, opts)
|
|
499
|
+
params['Signature'] = Sign.sign(str(cred_secret_key),
|
|
500
|
+
str(signInParam),
|
|
501
|
+
str(self.profile.signMethod))
|
|
502
|
+
if method == "GET":
|
|
503
|
+
query = params
|
|
504
|
+
else:
|
|
505
|
+
body = urlencode(params)
|
|
506
|
+
|
|
507
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
508
|
+
return ApiRequest(method, url, params=query, content=body, headers=headers)
|
|
509
|
+
|
|
510
|
+
def _format_sign_string(self, params, opts=None):
|
|
511
|
+
formatParam = {}
|
|
512
|
+
for k in params:
|
|
513
|
+
formatParam[k.replace('_', '.')] = params[k]
|
|
514
|
+
strParam = '&'.join('%s=%s' % (k, formatParam[k]) for k in sorted(formatParam))
|
|
515
|
+
msg = '%s%s%s?%s' % (
|
|
516
|
+
self.profile.httpProfile.reqMethod, self._get_endpoint(opts=opts), self._requestPath, strParam)
|
|
517
|
+
return msg
|
|
518
|
+
|
|
519
|
+
def _fix_params(self, params):
|
|
520
|
+
if not isinstance(params, (dict,)):
|
|
521
|
+
return params
|
|
522
|
+
return self._format_params(None, params)
|
|
523
|
+
|
|
524
|
+
def _format_params(self, prefix, params):
|
|
525
|
+
d = {}
|
|
526
|
+
if params is None:
|
|
527
|
+
return d
|
|
528
|
+
|
|
529
|
+
if not isinstance(params, (tuple, list, dict)):
|
|
530
|
+
d[prefix] = params
|
|
531
|
+
return d
|
|
532
|
+
|
|
533
|
+
if isinstance(params, (list, tuple)):
|
|
534
|
+
for idx, item in enumerate(params):
|
|
535
|
+
if prefix:
|
|
536
|
+
key = "{0}.{1}".format(prefix, idx)
|
|
537
|
+
else:
|
|
538
|
+
key = "{0}".format(idx)
|
|
539
|
+
d.update(self._format_params(key, item))
|
|
540
|
+
return d
|
|
541
|
+
|
|
542
|
+
if isinstance(params, dict):
|
|
543
|
+
for k, v in params.items():
|
|
544
|
+
if prefix:
|
|
545
|
+
key = '{0}.{1}'.format(prefix, k)
|
|
546
|
+
else:
|
|
547
|
+
key = '{0}'.format(k)
|
|
548
|
+
d.update(self._format_params(key, v))
|
|
549
|
+
return d
|
|
550
|
+
|
|
551
|
+
raise TencentCloudSDKException("ClientParamsError", "some params type error")
|
|
552
|
+
|
|
553
|
+
def _get_multipart_body(self, params, boundary, options=None):
|
|
554
|
+
if options is None:
|
|
555
|
+
options = {}
|
|
556
|
+
# boundary and params key will never contain unicode characters
|
|
557
|
+
boundary = boundary.encode()
|
|
558
|
+
binparas = options.get("BinaryParams", [])
|
|
559
|
+
body = b''
|
|
560
|
+
for k, v in params.items():
|
|
561
|
+
kbytes = k.encode()
|
|
562
|
+
body += b'--%s\r\n' % boundary
|
|
563
|
+
body += b'Content-Disposition: form-data; name="%s"' % kbytes
|
|
564
|
+
if k in binparas:
|
|
565
|
+
body += b'; filename="%s"\r\n' % kbytes
|
|
566
|
+
else:
|
|
567
|
+
body += b"\r\n"
|
|
568
|
+
if isinstance(v, list) or isinstance(v, dict):
|
|
569
|
+
v = json.dumps(v)
|
|
570
|
+
body += b'Content-Type: application/json\r\n'
|
|
571
|
+
if sys.version_info[0] == 3 and isinstance(v, type("")):
|
|
572
|
+
v = v.encode()
|
|
573
|
+
body += b'\r\n%s\r\n' % v
|
|
574
|
+
if body != b'':
|
|
575
|
+
body += b'--%s--\r\n' % boundary
|
|
576
|
+
return body
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
def _get_tc3_signature(method: str, path: str, query: str, body: bytes, headers: Dict, date: str, service: str,
|
|
580
|
+
secret_key: str, opts: Dict):
|
|
581
|
+
canonical_uri = path
|
|
582
|
+
canonical_querystring = query
|
|
583
|
+
payload = body
|
|
584
|
+
|
|
585
|
+
if headers.get("X-TC-Content-SHA256") == "UNSIGNED-PAYLOAD":
|
|
586
|
+
payload = b"UNSIGNED-PAYLOAD"
|
|
587
|
+
|
|
588
|
+
payload_hash = hashlib.sha256(payload).hexdigest()
|
|
589
|
+
|
|
590
|
+
canonical_headers = 'content-type:%s\nhost:%s\n' % (
|
|
591
|
+
headers["Content-Type"], headers["Host"])
|
|
592
|
+
signed_headers = 'content-type;host'
|
|
593
|
+
canonical_request = '%s\n%s\n%s\n%s\n%s\n%s' % (method,
|
|
594
|
+
canonical_uri,
|
|
595
|
+
canonical_querystring,
|
|
596
|
+
canonical_headers,
|
|
597
|
+
signed_headers,
|
|
598
|
+
payload_hash)
|
|
599
|
+
|
|
600
|
+
algorithm = 'TC3-HMAC-SHA256'
|
|
601
|
+
credential_scope = date + '/' + service + '/tc3_request'
|
|
602
|
+
if sys.version_info[0] == 3:
|
|
603
|
+
canonical_request = canonical_request.encode("utf8")
|
|
604
|
+
digest = hashlib.sha256(canonical_request).hexdigest()
|
|
605
|
+
string2sign = '%s\n%s\n%s\n%s' % (algorithm,
|
|
606
|
+
headers["X-TC-Timestamp"],
|
|
607
|
+
credential_scope,
|
|
608
|
+
digest)
|
|
609
|
+
return Sign.sign_tc3(secret_key, date, service, string2sign)
|
|
610
|
+
|
|
611
|
+
def set_stream_logger(self, stream=None, level=logging.DEBUG, log_format=None):
|
|
612
|
+
"""Add a stream handler
|
|
613
|
+
|
|
614
|
+
:param stream: e.g. ``sys.stdout`` ``sys.stdin`` ``sys.stderr``
|
|
615
|
+
:type stream: IO[str]
|
|
616
|
+
:param level: Logging level, e.g. ``logging.INFO``
|
|
617
|
+
:type level: int
|
|
618
|
+
:param log_format: Log message format
|
|
619
|
+
:type log_format: str
|
|
620
|
+
"""
|
|
621
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
622
|
+
log.setLevel(level)
|
|
623
|
+
sh = logging.StreamHandler(stream)
|
|
624
|
+
sh.setLevel(level)
|
|
625
|
+
if log_format is None:
|
|
626
|
+
log_format = self.FMT
|
|
627
|
+
formatter = logging.Formatter(log_format)
|
|
628
|
+
sh.setFormatter(formatter)
|
|
629
|
+
log.addHandler(sh)
|
|
630
|
+
|
|
631
|
+
def set_file_logger(self, file_path, level=logging.DEBUG, log_format=None):
|
|
632
|
+
"""Add a file handler
|
|
633
|
+
|
|
634
|
+
:param file_path: path of log file
|
|
635
|
+
:type file_path: str
|
|
636
|
+
:param level: Logging level, e.g. ``logging.INFO``
|
|
637
|
+
:type level: int
|
|
638
|
+
:param log_format: Log message format
|
|
639
|
+
:type log_format: str
|
|
640
|
+
"""
|
|
641
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
642
|
+
log.setLevel(level)
|
|
643
|
+
mb = 1024 * 1024
|
|
644
|
+
fh = logging.handlers.RotatingFileHandler(file_path, maxBytes=512 * mb, backupCount=10)
|
|
645
|
+
fh.setLevel(level)
|
|
646
|
+
if log_format is None:
|
|
647
|
+
log_format = self.FMT
|
|
648
|
+
formatter = logging.Formatter(log_format)
|
|
649
|
+
fh.setFormatter(formatter)
|
|
650
|
+
log.addHandler(fh)
|
|
651
|
+
|
|
652
|
+
def set_default_logger(self):
|
|
653
|
+
"""Set default log handler"""
|
|
654
|
+
pass
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 1999-2017 Tencent Ltd.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
from tencentcloud.common.abstract_client_async import AbstractClient
|
|
18
|
+
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CommonClient(AbstractClient):
|
|
22
|
+
"""General client for all products.
|
|
23
|
+
|
|
24
|
+
With CommonClient, you only need to install the tencentcloud-sdk-python-common package to access APIs of all products.
|
|
25
|
+
See GitHub examples for usage details: https://github.com/TencentCloud/tencentcloud-sdk-python/tree/master/examples/common_client
|
|
26
|
+
|
|
27
|
+
:param service: Product name
|
|
28
|
+
:type service: str
|
|
29
|
+
:param version: Version of API
|
|
30
|
+
:type version: str
|
|
31
|
+
:param credential: Request credential
|
|
32
|
+
:type credential: tencentcloud.common.credential.Credential or tencentcloud.common.credential.STSAssumeRoleCredential or None
|
|
33
|
+
:param region: Request region
|
|
34
|
+
:type region: str
|
|
35
|
+
:param profile: Request SDK profile
|
|
36
|
+
:type profile: tencentcloud.common.profile.client_profile.ClientProfile
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, service, version, credential, region, profile=None):
|
|
40
|
+
if region is None or version is None or service is None:
|
|
41
|
+
raise TencentCloudSDKException("CommonClient Parameter Error, "
|
|
42
|
+
"credential region version service all required.")
|
|
43
|
+
self._apiVersion = version
|
|
44
|
+
self._service = service
|
|
45
|
+
super(CommonClient, self).__init__(credential, region, profile)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
__all__ = ["ApiRequest", "ApiResponse", "RequestPrettyFormatter", "ResponsePrettyFormatter"]
|
|
6
|
+
|
|
7
|
+
ApiRequest = httpx.Request
|
|
8
|
+
ApiResponse = httpx.Response
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RequestPrettyFormatter(object):
|
|
12
|
+
def __init__(self, req: ApiRequest, format_body=True, delimiter="\n"):
|
|
13
|
+
self._req = req
|
|
14
|
+
self._format_body = format_body
|
|
15
|
+
self._delimiter = delimiter
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
lines = ["%s %s" % (self._req.method, self._req.url)]
|
|
19
|
+
for k, v in self._req.headers.items():
|
|
20
|
+
lines.append("%s: %s" % (k, v))
|
|
21
|
+
lines.append("")
|
|
22
|
+
if self._format_body:
|
|
23
|
+
try:
|
|
24
|
+
lines.append(self._req.content.decode("utf-8"))
|
|
25
|
+
except UnicodeDecodeError:
|
|
26
|
+
# binary body
|
|
27
|
+
import base64
|
|
28
|
+
lines.append("base64_body:" + base64.standard_b64encode(self._req.content).decode())
|
|
29
|
+
return self._delimiter.join(lines)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ResponsePrettyFormatter(object):
|
|
33
|
+
def __init__(self, resp: ApiResponse, format_body=True, delimiter="\n"):
|
|
34
|
+
self._resp = resp
|
|
35
|
+
self._format_body = format_body
|
|
36
|
+
self._delimiter = delimiter
|
|
37
|
+
|
|
38
|
+
def __str__(self):
|
|
39
|
+
lines = ['%s %d %s' % (self.str_ver(self._resp.http_version), self._resp.status_code, self._resp.reason_phrase)]
|
|
40
|
+
for k, v in self._resp.headers.items():
|
|
41
|
+
lines.append('%s: %s' % (k, v))
|
|
42
|
+
return self._delimiter.join(lines)
|
|
43
|
+
|
|
44
|
+
async def astr(self):
|
|
45
|
+
lines = ['%s %d %s' % (self.str_ver(self._resp.http_version), self._resp.status_code, self._resp.reason_phrase)]
|
|
46
|
+
for k, v in self._resp.headers.items():
|
|
47
|
+
lines.append('%s: %s' % (k, v))
|
|
48
|
+
if self._format_body:
|
|
49
|
+
lines.append('')
|
|
50
|
+
lines.append((await self._resp.aread()).decode("utf-8"))
|
|
51
|
+
return self._delimiter.join(lines)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def str_ver(ver):
|
|
55
|
+
if ver == 10:
|
|
56
|
+
return "HTTP/1.0"
|
|
57
|
+
elif ver == 11:
|
|
58
|
+
return "HTTP/1.1"
|
|
59
|
+
elif ver == 20:
|
|
60
|
+
return "HTTP/2.0"
|
|
61
|
+
else:
|
|
62
|
+
return str(ver)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from tencentcloud.common.exception import TencentCloudSDKException
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NoopRetryer(object):
|
|
10
|
+
"""configuration without retry
|
|
11
|
+
|
|
12
|
+
NoopRetryer is a retry policy that does nothing.
|
|
13
|
+
It is useful when you don't want to retry.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
async def send_request(self, fn):
|
|
17
|
+
return await fn()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StandardRetryer(object):
|
|
21
|
+
"""Retry configuration
|
|
22
|
+
|
|
23
|
+
StandardRetryer is a retry policy that retries on network errors or frequency limitation.
|
|
24
|
+
:param max_attempts: Maximum number of attempts.
|
|
25
|
+
:type max_attempts: int
|
|
26
|
+
:param backoff_fn: A function that takes the number of attempts and returns the number of seconds to sleep before the next retry.
|
|
27
|
+
Default sleep time is 2^n seconds, n is the number of attempts.
|
|
28
|
+
:type backoff_fn: function
|
|
29
|
+
:param logger: A logger to log retry attempts. If not provided, no logging will be performed.
|
|
30
|
+
:type logger: logging.Logger
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, max_attempts=3, backoff_fn=None, logger=None):
|
|
34
|
+
self._max_attempts = max_attempts
|
|
35
|
+
self._backoff_fn = backoff_fn or self.backoff
|
|
36
|
+
self._logger = logger
|
|
37
|
+
|
|
38
|
+
async def send_request(self, fn):
|
|
39
|
+
resp = None
|
|
40
|
+
err = None
|
|
41
|
+
|
|
42
|
+
for n in range(self._max_attempts):
|
|
43
|
+
try:
|
|
44
|
+
resp = await fn()
|
|
45
|
+
except TencentCloudSDKException as e:
|
|
46
|
+
err = e
|
|
47
|
+
except httpx.TransportError as e:
|
|
48
|
+
err = e
|
|
49
|
+
|
|
50
|
+
if not await self.should_retry(resp, err):
|
|
51
|
+
if err:
|
|
52
|
+
raise err
|
|
53
|
+
return resp
|
|
54
|
+
|
|
55
|
+
sleep = await self._backoff_fn(n)
|
|
56
|
+
await self.on_retry(n, sleep, resp, err)
|
|
57
|
+
await asyncio.sleep(sleep)
|
|
58
|
+
|
|
59
|
+
raise err
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
async def should_retry(resp, err):
|
|
63
|
+
if not err:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
if isinstance(err, httpx.TransportError):
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
if not isinstance(err, TencentCloudSDKException):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
ec = err.get_code()
|
|
73
|
+
if ec in (
|
|
74
|
+
"ClientNetworkError", "ServerNetworkError", "RequestLimitExceeded",
|
|
75
|
+
"RequestLimitExceeded.UinLimitExceeded", "RequestLimitExceeded.GlobalRegionUinLimitExceeded"
|
|
76
|
+
):
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
async def backoff(n):
|
|
83
|
+
return 2 ** n
|
|
84
|
+
|
|
85
|
+
async def on_retry(self, n, sleep, resp, err):
|
|
86
|
+
if self._logger:
|
|
87
|
+
self._logger.debug("retry: n=%d sleep=%ss err=%s", n, sleep, err)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: tencentcloud-sdk-python-common
|
|
3
|
+
Version: 3.1.21
|
|
4
|
+
Summary: Tencent Cloud Common SDK for Python
|
|
5
|
+
Home-page: https://github.com/TencentCloud/tencentcloud-sdk-python
|
|
6
|
+
Author: Tencent Cloud
|
|
7
|
+
Maintainer-email: tencentcloudapi@tencent.com
|
|
8
|
+
License: Apache License 2.0
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
+
Requires-Dist: requests>=2.16.0
|
|
19
|
+
Provides-Extra: async
|
|
20
|
+
Requires-Dist: httpx>=0.22.0; extra == "async"
|
|
21
|
+
|
|
22
|
+
============================
|
|
23
|
+
Tencent Cloud SDK for Python
|
|
24
|
+
============================
|
|
25
|
+
|
|
26
|
+
Tencent Cloud Python Common SDK is the official software development kit, which allows Python developers to write software that makes use of Tencent Cloud services like CVM and CBS.
|
|
27
|
+
The SDK works on Python versions:
|
|
28
|
+
|
|
29
|
+
* 2.7 and greater, including 3.x
|
|
30
|
+
|
|
31
|
+
Quick Start
|
|
32
|
+
-----------
|
|
33
|
+
|
|
34
|
+
First, install the library:
|
|
35
|
+
|
|
36
|
+
.. code-block:: sh
|
|
37
|
+
|
|
38
|
+
$ pip install tencentcloud-sdk-python-common
|
|
39
|
+
$ pip install tencentcloud-sdk-python-common
|
|
40
|
+
|
|
41
|
+
or download source code from github and install:
|
|
42
|
+
|
|
43
|
+
.. code-block:: sh
|
|
44
|
+
|
|
45
|
+
$ git clone https://github.com/tencentcloud/tencentcloud-sdk-python.git
|
|
46
|
+
$ cd tencentcloud-sdk-python
|
|
47
|
+
$ python package.py --components common common
|
|
48
|
+
|
|
@@ -4,17 +4,21 @@ setup.py
|
|
|
4
4
|
tencentcloud/__init__.py
|
|
5
5
|
tencentcloud/common/__init__.py
|
|
6
6
|
tencentcloud/common/abstract_client.py
|
|
7
|
+
tencentcloud/common/abstract_client_async.py
|
|
7
8
|
tencentcloud/common/abstract_model.py
|
|
8
9
|
tencentcloud/common/circuit_breaker.py
|
|
9
10
|
tencentcloud/common/common_client.py
|
|
11
|
+
tencentcloud/common/common_client_async.py
|
|
10
12
|
tencentcloud/common/credential.py
|
|
11
13
|
tencentcloud/common/retry.py
|
|
14
|
+
tencentcloud/common/retry_async.py
|
|
12
15
|
tencentcloud/common/sign.py
|
|
13
16
|
tencentcloud/common/exception/__init__.py
|
|
14
17
|
tencentcloud/common/exception/tencent_cloud_sdk_exception.py
|
|
15
18
|
tencentcloud/common/http/__init__.py
|
|
16
19
|
tencentcloud/common/http/pre_conn.py
|
|
17
20
|
tencentcloud/common/http/request.py
|
|
21
|
+
tencentcloud/common/http/request_async.py
|
|
18
22
|
tencentcloud/common/profile/__init__.py
|
|
19
23
|
tencentcloud/common/profile/client_profile.py
|
|
20
24
|
tencentcloud/common/profile/http_profile.py
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.2
|
|
2
|
-
Name: tencentcloud-sdk-python-common
|
|
3
|
-
Version: 3.0.1457
|
|
4
|
-
Summary: Tencent Cloud Common SDK for Python
|
|
5
|
-
Home-page: https://github.com/TencentCloud/tencentcloud-sdk-python
|
|
6
|
-
Author: Tencent Cloud
|
|
7
|
-
Maintainer-email: tencentcloudapi@tencent.com
|
|
8
|
-
License: Apache License 2.0
|
|
9
|
-
Description: ============================
|
|
10
|
-
Tencent Cloud SDK for Python
|
|
11
|
-
============================
|
|
12
|
-
|
|
13
|
-
Tencent Cloud Python Common SDK is the official software development kit, which allows Python developers to write software that makes use of Tencent Cloud services like CVM and CBS.
|
|
14
|
-
The SDK works on Python versions:
|
|
15
|
-
|
|
16
|
-
* 2.7 and greater, including 3.x
|
|
17
|
-
|
|
18
|
-
Quick Start
|
|
19
|
-
-----------
|
|
20
|
-
|
|
21
|
-
First, install the library:
|
|
22
|
-
|
|
23
|
-
.. code-block:: sh
|
|
24
|
-
|
|
25
|
-
$ pip install tencentcloud-sdk-python-common
|
|
26
|
-
$ pip install tencentcloud-sdk-python-common
|
|
27
|
-
|
|
28
|
-
or download source code from github and install:
|
|
29
|
-
|
|
30
|
-
.. code-block:: sh
|
|
31
|
-
|
|
32
|
-
$ git clone https://github.com/tencentcloud/tencentcloud-sdk-python.git
|
|
33
|
-
$ cd tencentcloud-sdk-python
|
|
34
|
-
$ python package.py --components common common
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Platform: any
|
|
38
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
39
|
-
Classifier: Intended Audience :: Developers
|
|
40
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
41
|
-
Classifier: Programming Language :: Python
|
|
42
|
-
Classifier: Programming Language :: Python :: 2.7
|
|
43
|
-
Classifier: Programming Language :: Python :: 3
|
|
44
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
45
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.2
|
|
2
|
-
Name: tencentcloud-sdk-python-common
|
|
3
|
-
Version: 3.0.1457
|
|
4
|
-
Summary: Tencent Cloud Common SDK for Python
|
|
5
|
-
Home-page: https://github.com/TencentCloud/tencentcloud-sdk-python
|
|
6
|
-
Author: Tencent Cloud
|
|
7
|
-
Maintainer-email: tencentcloudapi@tencent.com
|
|
8
|
-
License: Apache License 2.0
|
|
9
|
-
Description: ============================
|
|
10
|
-
Tencent Cloud SDK for Python
|
|
11
|
-
============================
|
|
12
|
-
|
|
13
|
-
Tencent Cloud Python Common SDK is the official software development kit, which allows Python developers to write software that makes use of Tencent Cloud services like CVM and CBS.
|
|
14
|
-
The SDK works on Python versions:
|
|
15
|
-
|
|
16
|
-
* 2.7 and greater, including 3.x
|
|
17
|
-
|
|
18
|
-
Quick Start
|
|
19
|
-
-----------
|
|
20
|
-
|
|
21
|
-
First, install the library:
|
|
22
|
-
|
|
23
|
-
.. code-block:: sh
|
|
24
|
-
|
|
25
|
-
$ pip install tencentcloud-sdk-python-common
|
|
26
|
-
$ pip install tencentcloud-sdk-python-common
|
|
27
|
-
|
|
28
|
-
or download source code from github and install:
|
|
29
|
-
|
|
30
|
-
.. code-block:: sh
|
|
31
|
-
|
|
32
|
-
$ git clone https://github.com/tencentcloud/tencentcloud-sdk-python.git
|
|
33
|
-
$ cd tencentcloud-sdk-python
|
|
34
|
-
$ python package.py --components common common
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Platform: any
|
|
38
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
39
|
-
Classifier: Intended Audience :: Developers
|
|
40
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
41
|
-
Classifier: Programming Language :: Python
|
|
42
|
-
Classifier: Programming Language :: Python :: 2.7
|
|
43
|
-
Classifier: Programming Language :: Python :: 3
|
|
44
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
45
|
-
Classifier: Programming Language :: Python :: 3.7
|
tencentcloud-sdk-python-common-3.0.1457/tencentcloud_sdk_python_common.egg-info/requires.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
requests>=2.16.0
|
{tencentcloud-sdk-python-common-3.0.1457 → tencentcloud_sdk_python_common-3.1.21}/README.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|