ucloud-sdk-python3 0.11.81__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.
- ucloud/__init__.py +0 -0
- ucloud/client.py +469 -0
- ucloud/core/__init__.py +0 -0
- ucloud/core/auth/__init__.py +3 -0
- ucloud/core/auth/_cfg.py +72 -0
- ucloud/core/client/__init__.py +8 -0
- ucloud/core/client/_cfg.py +96 -0
- ucloud/core/client/_client.py +176 -0
- ucloud/core/exc/__init__.py +9 -0
- ucloud/core/exc/_exc.py +94 -0
- ucloud/core/transport/__init__.py +4 -0
- ucloud/core/transport/_requests.py +135 -0
- ucloud/core/transport/http.py +120 -0
- ucloud/core/transport/utils.py +40 -0
- ucloud/core/typesystem/__init__.py +0 -0
- ucloud/core/typesystem/abstract.py +60 -0
- ucloud/core/typesystem/encoder.py +33 -0
- ucloud/core/typesystem/fields.py +149 -0
- ucloud/core/typesystem/schema.py +97 -0
- ucloud/core/utils/__init__.py +0 -0
- ucloud/core/utils/compat.py +15 -0
- ucloud/core/utils/deco.py +31 -0
- ucloud/core/utils/log.py +29 -0
- ucloud/core/utils/middleware.py +63 -0
- ucloud/helpers/__init__.py +0 -0
- ucloud/helpers/utils.py +90 -0
- ucloud/helpers/wait.py +108 -0
- ucloud/services/__init__.py +0 -0
- ucloud/services/cube/__init__.py +0 -0
- ucloud/services/cube/client.py +618 -0
- ucloud/services/cube/schemas/__init__.py +0 -0
- ucloud/services/cube/schemas/apis.py +548 -0
- ucloud/services/cube/schemas/models.py +58 -0
- ucloud/services/iam/__init__.py +0 -0
- ucloud/services/iam/client.py +1078 -0
- ucloud/services/iam/schemas/__init__.py +0 -0
- ucloud/services/iam/schemas/apis.py +973 -0
- ucloud/services/iam/schemas/models.py +127 -0
- ucloud/services/ipsecvpn/__init__.py +0 -0
- ucloud/services/ipsecvpn/client.py +522 -0
- ucloud/services/ipsecvpn/schemas/__init__.py +0 -0
- ucloud/services/ipsecvpn/schemas/apis.py +455 -0
- ucloud/services/ipsecvpn/schemas/models.py +134 -0
- ucloud/services/ipv6gw/__init__.py +0 -0
- ucloud/services/ipv6gw/client.py +44 -0
- ucloud/services/ipv6gw/schemas/__init__.py +0 -0
- ucloud/services/ipv6gw/schemas/apis.py +34 -0
- ucloud/services/ipv6gw/schemas/models.py +3 -0
- ucloud/services/isms/__init__.py +0 -0
- ucloud/services/isms/client.py +330 -0
- ucloud/services/isms/schemas/__init__.py +0 -0
- ucloud/services/isms/schemas/apis.py +272 -0
- ucloud/services/isms/schemas/models.py +50 -0
- ucloud/services/pathx/__init__.py +0 -0
- ucloud/services/pathx/client.py +1656 -0
- ucloud/services/pathx/schemas/__init__.py +0 -0
- ucloud/services/pathx/schemas/apis.py +1289 -0
- ucloud/services/pathx/schemas/models.py +420 -0
- ucloud/services/stepflow/__init__.py +0 -0
- ucloud/services/stepflow/client.py +98 -0
- ucloud/services/stepflow/schemas/__init__.py +0 -0
- ucloud/services/stepflow/schemas/apis.py +67 -0
- ucloud/services/stepflow/schemas/models.py +37 -0
- ucloud/services/sts/__init__.py +0 -0
- ucloud/services/sts/client.py +46 -0
- ucloud/services/sts/schemas/__init__.py +0 -0
- ucloud/services/sts/schemas/apis.py +35 -0
- ucloud/services/sts/schemas/models.py +16 -0
- ucloud/services/tidb/__init__.py +0 -0
- ucloud/services/tidb/client.py +120 -0
- ucloud/services/tidb/schemas/__init__.py +0 -0
- ucloud/services/tidb/schemas/apis.py +103 -0
- ucloud/services/tidb/schemas/models.py +11 -0
- ucloud/services/uaaa/__init__.py +0 -0
- ucloud/services/uaaa/client.py +311 -0
- ucloud/services/uaaa/schemas/__init__.py +0 -0
- ucloud/services/uaaa/schemas/apis.py +252 -0
- ucloud/services/uaaa/schemas/models.py +47 -0
- ucloud/services/uaccount/__init__.py +0 -0
- ucloud/services/uaccount/client.py +547 -0
- ucloud/services/uaccount/schemas/__init__.py +0 -0
- ucloud/services/uaccount/schemas/apis.py +442 -0
- ucloud/services/uaccount/schemas/models.py +128 -0
- ucloud/services/uads/__init__.py +0 -0
- ucloud/services/uads/client.py +1148 -0
- ucloud/services/uads/schemas/__init__.py +0 -0
- ucloud/services/uads/schemas/apis.py +983 -0
- ucloud/services/uads/schemas/models.py +199 -0
- ucloud/services/ubill/__init__.py +0 -0
- ucloud/services/ubill/client.py +248 -0
- ucloud/services/ubill/schemas/__init__.py +0 -0
- ucloud/services/ubill/schemas/apis.py +183 -0
- ucloud/services/ubill/schemas/models.py +107 -0
- ucloud/services/ucdn/__init__.py +0 -0
- ucloud/services/ucdn/client.py +1964 -0
- ucloud/services/ucdn/schemas/__init__.py +0 -0
- ucloud/services/ucdn/schemas/apis.py +1395 -0
- ucloud/services/ucdn/schemas/models.py +576 -0
- ucloud/services/ucloudstack/__init__.py +0 -0
- ucloud/services/ucloudstack/client.py +3352 -0
- ucloud/services/ucloudstack/schemas/__init__.py +0 -0
- ucloud/services/ucloudstack/schemas/apis.py +2887 -0
- ucloud/services/ucloudstack/schemas/models.py +560 -0
- ucloud/services/ucompshare/__init__.py +0 -0
- ucloud/services/ucompshare/client.py +820 -0
- ucloud/services/ucompshare/schemas/__init__.py +0 -0
- ucloud/services/ucompshare/schemas/apis.py +623 -0
- ucloud/services/ucompshare/schemas/models.py +241 -0
- ucloud/services/udb/__init__.py +0 -0
- ucloud/services/udb/client.py +2463 -0
- ucloud/services/udb/schemas/__init__.py +0 -0
- ucloud/services/udb/schemas/apis.py +2053 -0
- ucloud/services/udb/schemas/models.py +319 -0
- ucloud/services/udbproxy/__init__.py +0 -0
- ucloud/services/udbproxy/client.py +67 -0
- ucloud/services/udbproxy/schemas/__init__.py +0 -0
- ucloud/services/udbproxy/schemas/apis.py +38 -0
- ucloud/services/udbproxy/schemas/models.py +31 -0
- ucloud/services/uddb/__init__.py +0 -0
- ucloud/services/uddb/client.py +456 -0
- ucloud/services/uddb/schemas/__init__.py +0 -0
- ucloud/services/uddb/schemas/apis.py +520 -0
- ucloud/services/uddb/schemas/models.py +96 -0
- ucloud/services/udi/__init__.py +0 -0
- ucloud/services/udi/client.py +250 -0
- ucloud/services/udi/schemas/__init__.py +0 -0
- ucloud/services/udi/schemas/apis.py +205 -0
- ucloud/services/udi/schemas/models.py +58 -0
- ucloud/services/udisk/__init__.py +0 -0
- ucloud/services/udisk/client.py +832 -0
- ucloud/services/udisk/schemas/__init__.py +0 -0
- ucloud/services/udisk/schemas/apis.py +741 -0
- ucloud/services/udisk/schemas/models.py +100 -0
- ucloud/services/udns/__init__.py +0 -0
- ucloud/services/udns/client.py +380 -0
- ucloud/services/udns/schemas/__init__.py +0 -0
- ucloud/services/udns/schemas/apis.py +293 -0
- ucloud/services/udns/schemas/models.py +58 -0
- ucloud/services/udpn/__init__.py +0 -0
- ucloud/services/udpn/client.py +240 -0
- ucloud/services/udpn/schemas/__init__.py +0 -0
- ucloud/services/udpn/schemas/apis.py +203 -0
- ucloud/services/udpn/schemas/models.py +29 -0
- ucloud/services/udts/__init__.py +0 -0
- ucloud/services/udts/client.py +410 -0
- ucloud/services/udts/schemas/__init__.py +0 -0
- ucloud/services/udts/schemas/apis.py +403 -0
- ucloud/services/udts/schemas/models.py +93 -0
- ucloud/services/uec/__init__.py +0 -0
- ucloud/services/uec/client.py +1510 -0
- ucloud/services/uec/schemas/__init__.py +0 -0
- ucloud/services/uec/schemas/apis.py +1195 -0
- ucloud/services/uec/schemas/models.py +316 -0
- ucloud/services/ufile/__init__.py +0 -0
- ucloud/services/ufile/client.py +698 -0
- ucloud/services/ufile/schemas/__init__.py +0 -0
- ucloud/services/ufile/schemas/apis.py +542 -0
- ucloud/services/ufile/schemas/models.py +139 -0
- ucloud/services/ufs/__init__.py +0 -0
- ucloud/services/ufs/client.py +328 -0
- ucloud/services/ufs/schemas/__init__.py +0 -0
- ucloud/services/ufs/schemas/apis.py +265 -0
- ucloud/services/ufs/schemas/models.py +52 -0
- ucloud/services/ugn/__init__.py +0 -0
- ucloud/services/ugn/client.py +857 -0
- ucloud/services/ugn/schemas/__init__.py +0 -0
- ucloud/services/ugn/schemas/apis.py +678 -0
- ucloud/services/ugn/schemas/models.py +191 -0
- ucloud/services/uhost/__init__.py +0 -0
- ucloud/services/uhost/client.py +1647 -0
- ucloud/services/uhost/schemas/__init__.py +0 -0
- ucloud/services/uhost/schemas/apis.py +1483 -0
- ucloud/services/uhost/schemas/models.py +427 -0
- ucloud/services/uhub/__init__.py +0 -0
- ucloud/services/uhub/client.py +229 -0
- ucloud/services/uhub/schemas/__init__.py +0 -0
- ucloud/services/uhub/schemas/apis.py +194 -0
- ucloud/services/uhub/schemas/models.py +39 -0
- ucloud/services/uk8s/__init__.py +0 -0
- ucloud/services/uk8s/client.py +729 -0
- ucloud/services/uk8s/schemas/__init__.py +0 -0
- ucloud/services/uk8s/schemas/apis.py +639 -0
- ucloud/services/uk8s/schemas/models.py +179 -0
- ucloud/services/ulb/__init__.py +0 -0
- ucloud/services/ulb/client.py +2285 -0
- ucloud/services/ulb/schemas/__init__.py +0 -0
- ucloud/services/ulb/schemas/apis.py +1678 -0
- ucloud/services/ulb/schemas/models.py +591 -0
- ucloud/services/ulighthost/__init__.py +0 -0
- ucloud/services/ulighthost/client.py +576 -0
- ucloud/services/ulighthost/schemas/__init__.py +0 -0
- ucloud/services/ulighthost/schemas/apis.py +445 -0
- ucloud/services/ulighthost/schemas/models.py +133 -0
- ucloud/services/umem/__init__.py +0 -0
- ucloud/services/umem/client.py +1829 -0
- ucloud/services/umem/schemas/__init__.py +0 -0
- ucloud/services/umem/schemas/apis.py +1477 -0
- ucloud/services/umem/schemas/models.py +327 -0
- ucloud/services/umongodb/__init__.py +0 -0
- ucloud/services/umongodb/client.py +752 -0
- ucloud/services/umongodb/schemas/__init__.py +0 -0
- ucloud/services/umongodb/schemas/apis.py +567 -0
- ucloud/services/umongodb/schemas/models.py +220 -0
- ucloud/services/unet/__init__.py +0 -0
- ucloud/services/unet/client.py +1278 -0
- ucloud/services/unet/schemas/__init__.py +0 -0
- ucloud/services/unet/schemas/apis.py +1006 -0
- ucloud/services/unet/schemas/models.py +275 -0
- ucloud/services/unvs/__init__.py +0 -0
- ucloud/services/unvs/client.py +87 -0
- ucloud/services/unvs/schemas/__init__.py +0 -0
- ucloud/services/unvs/schemas/apis.py +66 -0
- ucloud/services/unvs/schemas/models.py +19 -0
- ucloud/services/upfs/__init__.py +0 -0
- ucloud/services/upfs/client.py +252 -0
- ucloud/services/upfs/schemas/__init__.py +0 -0
- ucloud/services/upfs/schemas/apis.py +204 -0
- ucloud/services/upfs/schemas/models.py +36 -0
- ucloud/services/upgsql/__init__.py +0 -0
- ucloud/services/upgsql/client.py +1007 -0
- ucloud/services/upgsql/schemas/__init__.py +0 -0
- ucloud/services/upgsql/schemas/apis.py +827 -0
- ucloud/services/upgsql/schemas/models.py +158 -0
- ucloud/services/uphone/__init__.py +0 -0
- ucloud/services/uphone/client.py +2122 -0
- ucloud/services/uphone/schemas/__init__.py +0 -0
- ucloud/services/uphone/schemas/apis.py +1799 -0
- ucloud/services/uphone/schemas/models.py +357 -0
- ucloud/services/uphost/__init__.py +0 -0
- ucloud/services/uphost/client.py +847 -0
- ucloud/services/uphost/schemas/__init__.py +0 -0
- ucloud/services/uphost/schemas/apis.py +689 -0
- ucloud/services/uphost/schemas/models.py +175 -0
- ucloud/services/urocketmq/__init__.py +0 -0
- ucloud/services/urocketmq/client.py +117 -0
- ucloud/services/urocketmq/schemas/__init__.py +0 -0
- ucloud/services/urocketmq/schemas/apis.py +92 -0
- ucloud/services/urocketmq/schemas/models.py +14 -0
- ucloud/services/uslk/__init__.py +0 -0
- ucloud/services/uslk/client.py +249 -0
- ucloud/services/uslk/schemas/__init__.py +0 -0
- ucloud/services/uslk/schemas/apis.py +191 -0
- ucloud/services/uslk/schemas/models.py +74 -0
- ucloud/services/usms/__init__.py +0 -0
- ucloud/services/usms/client.py +759 -0
- ucloud/services/usms/schemas/__init__.py +0 -0
- ucloud/services/usms/schemas/apis.py +653 -0
- ucloud/services/usms/schemas/models.py +215 -0
- ucloud/services/utsdb/__init__.py +0 -0
- ucloud/services/utsdb/client.py +604 -0
- ucloud/services/utsdb/schemas/__init__.py +0 -0
- ucloud/services/utsdb/schemas/apis.py +515 -0
- ucloud/services/utsdb/schemas/models.py +61 -0
- ucloud/services/uvms/__init__.py +0 -0
- ucloud/services/uvms/client.py +119 -0
- ucloud/services/uvms/schemas/__init__.py +0 -0
- ucloud/services/uvms/schemas/apis.py +88 -0
- ucloud/services/uvms/schemas/models.py +40 -0
- ucloud/services/vpc/__init__.py +0 -0
- ucloud/services/vpc/client.py +3233 -0
- ucloud/services/vpc/schemas/__init__.py +0 -0
- ucloud/services/vpc/schemas/apis.py +2529 -0
- ucloud/services/vpc/schemas/models.py +651 -0
- ucloud/testing/__init__.py +0 -0
- ucloud/testing/driver/__init__.py +5 -0
- ucloud/testing/driver/_scenario.py +93 -0
- ucloud/testing/driver/_specification.py +57 -0
- ucloud/testing/driver/_step.py +166 -0
- ucloud/testing/env.py +28 -0
- ucloud/testing/exc.py +18 -0
- ucloud/testing/funcs.py +68 -0
- ucloud/testing/mock.py +28 -0
- ucloud/testing/op.py +177 -0
- ucloud/testing/utest.py +195 -0
- ucloud/version.py +1 -0
- ucloud_sdk_python3-0.11.81.dist-info/LICENSE +202 -0
- ucloud_sdk_python3-0.11.81.dist-info/METADATA +71 -0
- ucloud_sdk_python3-0.11.81.dist-info/RECORD +280 -0
- ucloud_sdk_python3-0.11.81.dist-info/WHEEL +5 -0
- ucloud_sdk_python3-0.11.81.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from ucloud import version
|
|
7
|
+
from ucloud.core.client._cfg import Config
|
|
8
|
+
from ucloud.core.transport import (
|
|
9
|
+
Transport,
|
|
10
|
+
RequestsTransport,
|
|
11
|
+
Request,
|
|
12
|
+
SSLOption,
|
|
13
|
+
http,
|
|
14
|
+
)
|
|
15
|
+
from ucloud.core.typesystem import encoder
|
|
16
|
+
from ucloud.core.utils import log
|
|
17
|
+
from ucloud.core.utils.middleware import Middleware
|
|
18
|
+
from ucloud.core import auth, exc
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Client:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
config: dict,
|
|
25
|
+
transport: typing.Optional[Transport] = None,
|
|
26
|
+
middleware: typing.Optional[Middleware] = None,
|
|
27
|
+
logger: typing.Optional[logging.Logger] = None,
|
|
28
|
+
):
|
|
29
|
+
cfg, cred = self._parse_dict_config(config)
|
|
30
|
+
self.config = cfg
|
|
31
|
+
self.credential = cred
|
|
32
|
+
self.transport = transport or RequestsTransport()
|
|
33
|
+
self.logger = logger or log.default_logger
|
|
34
|
+
if middleware is None:
|
|
35
|
+
middleware = Middleware()
|
|
36
|
+
middleware.response(self.logged_response_handler)
|
|
37
|
+
middleware.request(self.logged_request_handler)
|
|
38
|
+
middleware.exception(self.logged_exception_handler)
|
|
39
|
+
self._middleware = middleware
|
|
40
|
+
|
|
41
|
+
def invoke(self, action: str, args: dict = None, **options) -> dict:
|
|
42
|
+
"""invoke will invoke the action with arguments data and options
|
|
43
|
+
|
|
44
|
+
:param str action: the api action, like `CreateUHostInstance`
|
|
45
|
+
:param dict args: arguments of api(action), see doc: `UCloud API Documentation <https://docs.ucloud.cn/api>`__
|
|
46
|
+
:return:
|
|
47
|
+
"""
|
|
48
|
+
retries = 0
|
|
49
|
+
max_retries = options.get("max_retries") or self.config.max_retries
|
|
50
|
+
timeout = options.get("timeout") or self.config.timeout
|
|
51
|
+
|
|
52
|
+
while retries <= max_retries:
|
|
53
|
+
try:
|
|
54
|
+
return self._send(
|
|
55
|
+
action, args or {}, max_retries=max_retries, timeout=timeout
|
|
56
|
+
)
|
|
57
|
+
except exc.UCloudException as e:
|
|
58
|
+
if e.retryable and retries != max_retries:
|
|
59
|
+
logging.info(
|
|
60
|
+
"Retrying {action}: {args}".format(
|
|
61
|
+
action=action, args=args
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
retries += 1
|
|
65
|
+
continue
|
|
66
|
+
raise e
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def middleware(self) -> Middleware:
|
|
70
|
+
return self._middleware
|
|
71
|
+
|
|
72
|
+
def logged_request_handler(self, req):
|
|
73
|
+
action = req.get("Action", "")
|
|
74
|
+
self.logger.info("[request] {} {}".format(action, req))
|
|
75
|
+
return req
|
|
76
|
+
|
|
77
|
+
def logged_response_handler(self, resp, http_resp: http.Response = None):
|
|
78
|
+
action = resp.get("Action", "")
|
|
79
|
+
request_uuid = http_resp and http_resp.request_uuid
|
|
80
|
+
self.logger.info(
|
|
81
|
+
"[response] [{}] {} {}".format(request_uuid or "*", action, resp)
|
|
82
|
+
)
|
|
83
|
+
return resp
|
|
84
|
+
|
|
85
|
+
def logged_exception_handler(self, e: Exception):
|
|
86
|
+
if isinstance(e, exc.RetCodeException):
|
|
87
|
+
self.logger.warning(e)
|
|
88
|
+
else:
|
|
89
|
+
self.logger.exception(e)
|
|
90
|
+
return e
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _parse_dict_config(
|
|
94
|
+
config: dict,
|
|
95
|
+
) -> typing.Tuple[Config, auth.Credential]:
|
|
96
|
+
return Config.from_dict(config), auth.Credential.from_dict(config)
|
|
97
|
+
|
|
98
|
+
def _send(self, action: str, args: dict, max_retries, timeout) -> dict:
|
|
99
|
+
args["Action"] = action
|
|
100
|
+
|
|
101
|
+
# inject request middleware
|
|
102
|
+
for handler in self.middleware.request_handlers:
|
|
103
|
+
args = handler(args)
|
|
104
|
+
|
|
105
|
+
# send http request
|
|
106
|
+
try:
|
|
107
|
+
req = self._build_http_request(args)
|
|
108
|
+
|
|
109
|
+
resp = self.transport.send(
|
|
110
|
+
req,
|
|
111
|
+
ssl_option=SSLOption(
|
|
112
|
+
self.config.ssl_verify,
|
|
113
|
+
self.config.ssl_cacert,
|
|
114
|
+
self.config.ssl_cert,
|
|
115
|
+
self.config.ssl_key,
|
|
116
|
+
),
|
|
117
|
+
timeout=timeout,
|
|
118
|
+
max_retries=max_retries,
|
|
119
|
+
)
|
|
120
|
+
data = resp.json()
|
|
121
|
+
except Exception as e:
|
|
122
|
+
for handler in self.middleware.exception_handlers:
|
|
123
|
+
handler(e)
|
|
124
|
+
raise e
|
|
125
|
+
|
|
126
|
+
# inject response middleware
|
|
127
|
+
for handler in self.middleware.response_handlers:
|
|
128
|
+
data = handler(data, resp)
|
|
129
|
+
|
|
130
|
+
# return when successful
|
|
131
|
+
if int(data.get("RetCode", -1)) == 0:
|
|
132
|
+
return data
|
|
133
|
+
|
|
134
|
+
# inject exception middleware
|
|
135
|
+
ret_code_exc = exc.RetCodeException(
|
|
136
|
+
action=req.data.get("Action", ""),
|
|
137
|
+
code=int(data.get("RetCode", 0)),
|
|
138
|
+
message=data.get("Message", ""),
|
|
139
|
+
request_uuid=resp.request_uuid,
|
|
140
|
+
)
|
|
141
|
+
for handler in self.middleware.exception_handlers:
|
|
142
|
+
handler(ret_code_exc)
|
|
143
|
+
raise ret_code_exc
|
|
144
|
+
|
|
145
|
+
def _build_http_request(self, args: dict) -> Request:
|
|
146
|
+
config = {
|
|
147
|
+
"Region": self.config.region,
|
|
148
|
+
"ProjectId": self.config.project_id,
|
|
149
|
+
}
|
|
150
|
+
payload = {k: v for k, v in config.items() if v is not None}
|
|
151
|
+
payload.update({k: v for k, v in args.items() if v is not None})
|
|
152
|
+
payload = encoder.encode(payload)
|
|
153
|
+
payload["Signature"] = self.credential.verify_ac(payload)
|
|
154
|
+
|
|
155
|
+
return Request(
|
|
156
|
+
url=self.config.base_url,
|
|
157
|
+
method="post",
|
|
158
|
+
data=payload,
|
|
159
|
+
headers={
|
|
160
|
+
"User-Agent": self._build_user_agent(),
|
|
161
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
162
|
+
"U-Timestamp-Ms": str(int(round(time.time() * 1000))),
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def _build_user_agent(self) -> str:
|
|
167
|
+
python_version = "{v[0]}.{v[1]}.{v[2]}".format(v=sys.version_info)
|
|
168
|
+
user_agent = "Python/{python_version} Python-SDK/{sdk_version}".format(
|
|
169
|
+
python_version=python_version, sdk_version=version.version
|
|
170
|
+
) + (self.config.user_agent or "")
|
|
171
|
+
return user_agent
|
|
172
|
+
|
|
173
|
+
def __repr__(self):
|
|
174
|
+
return '<{}(region="{}")>'.format(
|
|
175
|
+
self.__class__.__name__, self.config.region
|
|
176
|
+
)
|
ucloud/core/exc/_exc.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
|
|
3
|
+
from ucloud.core.utils import compat
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UCloudException(Exception):
|
|
7
|
+
@property
|
|
8
|
+
def retryable(self):
|
|
9
|
+
return False
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
MAX_COMMON_RET_CODE = 2000
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TransportException(UCloudException):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HTTPStatusException(TransportException):
|
|
20
|
+
def __init__(self, status_code: int, request_uuid: str = None):
|
|
21
|
+
self.status_code = status_code
|
|
22
|
+
self.request_uuid = request_uuid
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def retryable(self):
|
|
26
|
+
return self.status_code in [429, 502, 503, 504]
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
return "[{uuid}] {self.status_code} http status error".format(
|
|
30
|
+
self=self, uuid=self.request_uuid or "*"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InvalidResponseException(TransportException):
|
|
35
|
+
def __init__(self, content: bytes, message: str, request_uuid: str = None):
|
|
36
|
+
self.content = content
|
|
37
|
+
self.message = message
|
|
38
|
+
self.request_uuid = request_uuid
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def retryable(self):
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def __str__(self):
|
|
45
|
+
return "[{uuid}] {self.message}: {self.content}".format(
|
|
46
|
+
self=self, uuid=self.request_uuid or "*"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RetCodeException(UCloudException):
|
|
51
|
+
def __init__(
|
|
52
|
+
self, action: str, code: int, message: str, request_uuid: str = None
|
|
53
|
+
):
|
|
54
|
+
self.action = action
|
|
55
|
+
self.code = code
|
|
56
|
+
self.message = message
|
|
57
|
+
self.request_uuid = request_uuid
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def retryable(self):
|
|
61
|
+
return self.code > MAX_COMMON_RET_CODE
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return "[{uuid}] {self.action} - {self.code}: {self.message}".format(
|
|
65
|
+
self=self, uuid=self.request_uuid or "*"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def json(self):
|
|
69
|
+
return {
|
|
70
|
+
"RetCode": self.code,
|
|
71
|
+
"Message": self.message or "",
|
|
72
|
+
"Action": self.action or "",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RetryTimeoutException(UCloudException):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ValidationException(UCloudException):
|
|
81
|
+
def __init__(self, e=None):
|
|
82
|
+
if isinstance(e, compat.string_types):
|
|
83
|
+
self.errors = [e]
|
|
84
|
+
elif isinstance(e, Iterable):
|
|
85
|
+
self.errors = e or []
|
|
86
|
+
else:
|
|
87
|
+
self.errors = [e]
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def retryable(self):
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
def __str__(self):
|
|
94
|
+
return str([str(e) for e in self.errors])
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import typing
|
|
3
|
+
import requests
|
|
4
|
+
from urllib3.util.retry import Retry
|
|
5
|
+
from requests.adapters import HTTPAdapter
|
|
6
|
+
from ucloud.core.transport import http
|
|
7
|
+
from ucloud.core.transport.http import Request, Response, SSLOption
|
|
8
|
+
from ucloud.core.utils.middleware import Middleware
|
|
9
|
+
from ucloud.core import exc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RequestsTransport(http.Transport):
|
|
13
|
+
"""transport is the implementation of http client, use for send a request and return a http response
|
|
14
|
+
|
|
15
|
+
:type max_retries: int
|
|
16
|
+
:param max_retries: max retries is the max number of transport request when occur http error
|
|
17
|
+
:type backoff_factor: float
|
|
18
|
+
:param backoff_factor: backoff factor will calculate the backoff delay during retrying,
|
|
19
|
+
the backoff delay = {backoff factor} * (2 ^ ({number of total retries} - 1))
|
|
20
|
+
:type status_forcelist: tuple
|
|
21
|
+
:param status_forcelist: the status code list that could be retried
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
max_retries: int = 3,
|
|
27
|
+
backoff_factor: float = 0.3,
|
|
28
|
+
status_forcelist: typing.Tuple[int] = (500, 502, 504),
|
|
29
|
+
):
|
|
30
|
+
self.max_retries = max_retries
|
|
31
|
+
self.backoff_factor = backoff_factor
|
|
32
|
+
self.status_forcelist = status_forcelist
|
|
33
|
+
|
|
34
|
+
self._adapter = self._load_adapter(max_retries)
|
|
35
|
+
self._middleware = Middleware()
|
|
36
|
+
|
|
37
|
+
def send(self, req: Request, **options: typing.Any) -> http.Response:
|
|
38
|
+
"""send request and return the response
|
|
39
|
+
|
|
40
|
+
:param req: the full http request descriptor
|
|
41
|
+
:return: the response of http request
|
|
42
|
+
"""
|
|
43
|
+
for handler in self.middleware.request_handlers:
|
|
44
|
+
req = handler(req)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
resp = self._send(req, **options)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
for handler in self.middleware.exception_handlers:
|
|
50
|
+
handler(e)
|
|
51
|
+
raise e
|
|
52
|
+
|
|
53
|
+
for handler in self.middleware.response_handlers:
|
|
54
|
+
resp = handler(resp)
|
|
55
|
+
|
|
56
|
+
return resp
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def middleware(self) -> Middleware:
|
|
60
|
+
"""the middleware object, see :mod:
|
|
61
|
+
|
|
62
|
+
:return: the transport middleware
|
|
63
|
+
"""
|
|
64
|
+
return self._middleware
|
|
65
|
+
|
|
66
|
+
def _send(self, req: Request, **options: typing.Any) -> requests.Response:
|
|
67
|
+
with requests.Session() as session:
|
|
68
|
+
adapter = self._load_adapter(options.get("max_retries"))
|
|
69
|
+
session.mount("http://", adapter=adapter)
|
|
70
|
+
session.mount("https://", adapter=adapter)
|
|
71
|
+
|
|
72
|
+
ssl_option = options.get("ssl_option")
|
|
73
|
+
kwargs = self._build_ssl_option(ssl_option) if ssl_option else {}
|
|
74
|
+
|
|
75
|
+
req.request_time = time.time()
|
|
76
|
+
session_resp = session.request(
|
|
77
|
+
method=req.method.upper(),
|
|
78
|
+
url=req.url,
|
|
79
|
+
json=req.json,
|
|
80
|
+
data=req.data,
|
|
81
|
+
params=req.params,
|
|
82
|
+
headers=req.headers,
|
|
83
|
+
timeout=options.get("timeout"),
|
|
84
|
+
**kwargs
|
|
85
|
+
)
|
|
86
|
+
resp = self.convert_response(session_resp)
|
|
87
|
+
resp.request = req
|
|
88
|
+
resp.response_time = time.time()
|
|
89
|
+
|
|
90
|
+
if resp.status_code >= 400:
|
|
91
|
+
raise exc.HTTPStatusException(
|
|
92
|
+
resp.status_code, resp.request_uuid
|
|
93
|
+
)
|
|
94
|
+
return resp
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _build_ssl_option(ssl_option):
|
|
98
|
+
kwargs = {"verify": ssl_option.ssl_verify and ssl_option.ssl_cacert}
|
|
99
|
+
if not ssl_option.ssl_cert:
|
|
100
|
+
return kwargs
|
|
101
|
+
|
|
102
|
+
if ssl_option.ssl_key:
|
|
103
|
+
kwargs["cert"] = (ssl_option.ssl_cert, ssl_option.ssl_key)
|
|
104
|
+
else:
|
|
105
|
+
kwargs["cert"] = ssl_option.ssl_cert
|
|
106
|
+
return kwargs
|
|
107
|
+
|
|
108
|
+
def _load_adapter(
|
|
109
|
+
self, max_retries: typing.Optional[int] = None
|
|
110
|
+
) -> HTTPAdapter:
|
|
111
|
+
if max_retries is None and self._adapter is not None:
|
|
112
|
+
return self._adapter
|
|
113
|
+
|
|
114
|
+
max_retries = max_retries or 0
|
|
115
|
+
adapter = HTTPAdapter()
|
|
116
|
+
adapter.max_retries = Retry(
|
|
117
|
+
total=max_retries,
|
|
118
|
+
read=max_retries,
|
|
119
|
+
connect=max_retries,
|
|
120
|
+
backoff_factor=self.backoff_factor,
|
|
121
|
+
status_forcelist=self.status_forcelist,
|
|
122
|
+
)
|
|
123
|
+
return adapter
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def convert_response(r: requests.Response) -> Response:
|
|
127
|
+
return Response(
|
|
128
|
+
url=r.url,
|
|
129
|
+
method=r.request.method,
|
|
130
|
+
status_code=r.status_code,
|
|
131
|
+
reason=r.reason,
|
|
132
|
+
headers=r.headers,
|
|
133
|
+
content=r.content,
|
|
134
|
+
encoding=r.encoding or r.apparent_encoding,
|
|
135
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
import logging
|
|
3
|
+
import json as json_mod
|
|
4
|
+
|
|
5
|
+
from ucloud.core import exc
|
|
6
|
+
from ucloud.core.transport import utils
|
|
7
|
+
from ucloud.core.utils.compat import str
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Request:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
url: str,
|
|
16
|
+
method: str = "GET",
|
|
17
|
+
params: dict = None,
|
|
18
|
+
data: dict = None,
|
|
19
|
+
json: dict = None,
|
|
20
|
+
headers: dict = None,
|
|
21
|
+
**kwargs
|
|
22
|
+
):
|
|
23
|
+
self.url = url
|
|
24
|
+
self.method = method
|
|
25
|
+
self.params = params
|
|
26
|
+
self.data = data
|
|
27
|
+
self.json = json
|
|
28
|
+
self.headers = headers
|
|
29
|
+
self.request_time = 0
|
|
30
|
+
|
|
31
|
+
def payload(self):
|
|
32
|
+
payload = (self.params or {}).copy()
|
|
33
|
+
payload.update(self.data or {})
|
|
34
|
+
payload.update(self.json or {})
|
|
35
|
+
return payload
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
REQUEST_UUID_HEADER_KEY = "X-UCLOUD-REQUEST-UUID"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Response:
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
url: str,
|
|
45
|
+
method: str,
|
|
46
|
+
request: Request = None,
|
|
47
|
+
status_code: int = None,
|
|
48
|
+
reason: str = None,
|
|
49
|
+
headers: dict = None,
|
|
50
|
+
content: bytes = None,
|
|
51
|
+
encoding: str = None,
|
|
52
|
+
**kwargs
|
|
53
|
+
):
|
|
54
|
+
self.url = url
|
|
55
|
+
self.method = method
|
|
56
|
+
self.request = request
|
|
57
|
+
self.status_code = status_code
|
|
58
|
+
self.reason = reason
|
|
59
|
+
self.content = content
|
|
60
|
+
self.encoding = encoding
|
|
61
|
+
self.response_time = 0
|
|
62
|
+
self.headers = headers or {}
|
|
63
|
+
self.request_uuid = self.headers.get(REQUEST_UUID_HEADER_KEY)
|
|
64
|
+
|
|
65
|
+
def json(self, **kwargs) -> typing.Optional[dict]:
|
|
66
|
+
"""json will return the bytes of content"""
|
|
67
|
+
if not self.content:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
return self._decode_json(**kwargs)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise exc.InvalidResponseException(
|
|
74
|
+
self.content, str(e), request_uuid=self.request_uuid
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def text(self):
|
|
79
|
+
"""text will return the unicode string of content,
|
|
80
|
+
see `requests.Response.text`
|
|
81
|
+
"""
|
|
82
|
+
if not self.content:
|
|
83
|
+
return str("")
|
|
84
|
+
|
|
85
|
+
# Decode unicode from given encoding.
|
|
86
|
+
try:
|
|
87
|
+
content = str(self.content, self.encoding, errors="replace")
|
|
88
|
+
except (LookupError, TypeError):
|
|
89
|
+
content = str(self.content, errors="replace")
|
|
90
|
+
return content
|
|
91
|
+
|
|
92
|
+
def _decode_json(self, **kwargs):
|
|
93
|
+
encoding = utils.guess_json_utf(self.content)
|
|
94
|
+
if encoding is not None:
|
|
95
|
+
try:
|
|
96
|
+
return json_mod.loads(self.content.decode(encoding), **kwargs)
|
|
97
|
+
except UnicodeDecodeError:
|
|
98
|
+
pass
|
|
99
|
+
return json_mod.loads(self.text, **kwargs)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class SSLOption:
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
ssl_verify: bool = True,
|
|
106
|
+
ssl_cacert: str = None,
|
|
107
|
+
ssl_cert: str = None,
|
|
108
|
+
ssl_key: str = None,
|
|
109
|
+
):
|
|
110
|
+
self.ssl_verify = ssl_verify
|
|
111
|
+
self.ssl_cacert = ssl_cacert
|
|
112
|
+
self.ssl_cert = ssl_cert
|
|
113
|
+
self.ssl_key = ssl_key
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Transport:
|
|
117
|
+
"""the abstract class of transport implementation"""
|
|
118
|
+
|
|
119
|
+
def send(self, req: Request, **options: typing.Any) -> Response:
|
|
120
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
|
|
3
|
+
# Null bytes; no need to recreate these on each call to guess_json_utf
|
|
4
|
+
_null = "\x00".encode("ascii") # encoding to ASCII for Python 3
|
|
5
|
+
_null2 = _null * 2
|
|
6
|
+
_null3 = _null * 3
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def guess_json_utf(data):
|
|
10
|
+
"""guess_json_utf will detect the encoding of bytes,
|
|
11
|
+
see `requests.utils.guess_json_utf`
|
|
12
|
+
|
|
13
|
+
:rtype: str
|
|
14
|
+
"""
|
|
15
|
+
# JSON always starts with two ASCII characters, so detection is as
|
|
16
|
+
# easy as counting the nulls and from their location and count
|
|
17
|
+
# determine the encoding. Also detect a BOM, if present.
|
|
18
|
+
sample = data[:4]
|
|
19
|
+
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
|
|
20
|
+
return "utf-32" # BOM included
|
|
21
|
+
if sample[:3] == codecs.BOM_UTF8:
|
|
22
|
+
return "utf-8-sig" # BOM included, MS style (discouraged)
|
|
23
|
+
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
|
|
24
|
+
return "utf-16" # BOM included
|
|
25
|
+
nullcount = sample.count(_null)
|
|
26
|
+
if nullcount == 0:
|
|
27
|
+
return "utf-8"
|
|
28
|
+
if nullcount == 2:
|
|
29
|
+
if sample[::2] == _null2: # 1st and 3rd are null
|
|
30
|
+
return "utf-16-be"
|
|
31
|
+
if sample[1::2] == _null2: # 2nd and 4th are null
|
|
32
|
+
return "utf-16-le"
|
|
33
|
+
# Did not detect 2 valid UTF-16 ascii-range characters
|
|
34
|
+
if nullcount == 3:
|
|
35
|
+
if sample[:3] == _null3:
|
|
36
|
+
return "utf-32-be"
|
|
37
|
+
if sample[1:] == _null3:
|
|
38
|
+
return "utf-32-le"
|
|
39
|
+
# Did not detect a valid UTF-32 ascii-range character
|
|
40
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from ucloud.core import exc
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Field(object):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
required: bool = False,
|
|
10
|
+
default: typing.Any = None,
|
|
11
|
+
dump_to: str = None,
|
|
12
|
+
load_from: str = None,
|
|
13
|
+
strict: bool = None,
|
|
14
|
+
**kwargs
|
|
15
|
+
):
|
|
16
|
+
self.required = required
|
|
17
|
+
self.default = default
|
|
18
|
+
self.dump_to = dump_to
|
|
19
|
+
self.load_from = load_from
|
|
20
|
+
self.options = kwargs
|
|
21
|
+
self.strict = bool(strict) # None as False
|
|
22
|
+
|
|
23
|
+
def dumps(self, value, **kwargs):
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
def loads(self, value, **kwargs):
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def fail(name, expected, got):
|
|
31
|
+
msg = "invalid field {}, expect {}, got {}".format(name, expected, got)
|
|
32
|
+
raise exc.ValidationException(msg)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Schema(object):
|
|
36
|
+
fields = {}
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
required: bool = False,
|
|
41
|
+
default: typing.Union[typing.Callable, typing.Any] = dict,
|
|
42
|
+
dump_to: str = None,
|
|
43
|
+
load_from: str = None,
|
|
44
|
+
strict: bool = False,
|
|
45
|
+
case_sensitive: bool = False,
|
|
46
|
+
**kwargs
|
|
47
|
+
):
|
|
48
|
+
self.required = required
|
|
49
|
+
self.default = default
|
|
50
|
+
self.dump_to = dump_to
|
|
51
|
+
self.load_from = load_from
|
|
52
|
+
self.options = kwargs
|
|
53
|
+
self.strict = strict
|
|
54
|
+
self.case_sensitive = case_sensitive
|
|
55
|
+
|
|
56
|
+
def dumps(self, d: dict) -> dict:
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
|
|
59
|
+
def loads(self, d: dict) -> dict:
|
|
60
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from ucloud.core.utils.compat import str
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def encode(d: dict) -> dict:
|
|
5
|
+
result = {}
|
|
6
|
+
|
|
7
|
+
for k, v in d.items():
|
|
8
|
+
if isinstance(v, dict):
|
|
9
|
+
for ek, ev in encode(v).items():
|
|
10
|
+
result["{}.{}".format(k, ek)] = encode_value(ev)
|
|
11
|
+
elif isinstance(v, list):
|
|
12
|
+
for i, item in enumerate(v):
|
|
13
|
+
if isinstance(item, dict):
|
|
14
|
+
for ek, ev in encode(item).items():
|
|
15
|
+
result["{}.{}.{}".format(k, i, ek)] = encode_value(ev)
|
|
16
|
+
else:
|
|
17
|
+
result["{}.{}".format(k, i)] = encode_value(item)
|
|
18
|
+
else:
|
|
19
|
+
result[k] = encode_value(v)
|
|
20
|
+
|
|
21
|
+
return result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def encode_value(v):
|
|
25
|
+
# bool only accept lower case
|
|
26
|
+
if isinstance(v, bool):
|
|
27
|
+
return "true" if v else "false"
|
|
28
|
+
|
|
29
|
+
# api gateway will try to decode float as int in lua syntax
|
|
30
|
+
if isinstance(v, float):
|
|
31
|
+
return str(int(v)) if v % 1 == 0 else str(v)
|
|
32
|
+
|
|
33
|
+
return str(v)
|