pymammotion 0.2.62__py3-none-any.whl → 0.5.51__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 pymammotion might be problematic. Click here for more details.
- pymammotion/__init__.py +9 -6
- pymammotion/aliyun/client.py +235 -0
- pymammotion/aliyun/cloud_gateway.py +320 -69
- pymammotion/aliyun/model/aep_response.py +1 -2
- pymammotion/aliyun/model/dev_by_account_response.py +170 -23
- pymammotion/aliyun/model/login_by_oauth_response.py +2 -3
- pymammotion/aliyun/model/regions_response.py +3 -3
- pymammotion/aliyun/model/session_by_authcode_response.py +2 -2
- pymammotion/aliyun/model/thing_response.py +12 -0
- pymammotion/aliyun/regions.py +62 -0
- pymammotion/aliyun/tea/core.py +297 -0
- pymammotion/bluetooth/ble.py +11 -15
- pymammotion/bluetooth/ble_message.py +389 -106
- pymammotion/bluetooth/model/atomic_integer.py +54 -0
- pymammotion/const.py +3 -0
- pymammotion/data/model/__init__.py +1 -2
- pymammotion/data/model/device.py +92 -240
- pymammotion/data/model/device_config.py +10 -24
- pymammotion/data/model/device_info.py +35 -0
- pymammotion/data/model/device_limits.py +49 -0
- pymammotion/data/model/enums.py +12 -2
- pymammotion/data/model/errors.py +12 -0
- pymammotion/data/model/events.py +14 -0
- pymammotion/data/model/generate_geojson.py +521 -0
- pymammotion/data/model/generate_route_information.py +3 -4
- pymammotion/data/model/hash_list.py +384 -48
- pymammotion/data/model/location.py +4 -4
- pymammotion/data/model/mowing_modes.py +24 -1
- pymammotion/data/model/raw_data.py +215 -0
- pymammotion/data/model/region_data.py +10 -11
- pymammotion/data/model/report_info.py +62 -6
- pymammotion/data/model/work.py +27 -0
- pymammotion/data/mower_state_manager.py +316 -0
- pymammotion/data/mqtt/event.py +73 -28
- pymammotion/data/mqtt/mammotion_properties.py +257 -0
- pymammotion/data/mqtt/properties.py +93 -78
- pymammotion/data/mqtt/status.py +18 -17
- pymammotion/event/event.py +32 -8
- pymammotion/homeassistant/__init__.py +3 -0
- pymammotion/homeassistant/mower_api.py +484 -0
- pymammotion/homeassistant/rtk_api.py +54 -0
- pymammotion/http/__init__.py +0 -0
- pymammotion/http/encryption.py +220 -0
- pymammotion/http/http.py +652 -44
- pymammotion/http/model/__init__.py +0 -0
- pymammotion/{aliyun/model/stream_subscription_response.py → http/model/camera_stream.py} +14 -2
- pymammotion/http/model/http.py +160 -9
- pymammotion/http/model/response_factory.py +61 -0
- pymammotion/http/model/rtk.py +16 -0
- pymammotion/mammotion/commands/abstract_message.py +7 -5
- pymammotion/mammotion/commands/mammotion_command.py +32 -3
- pymammotion/mammotion/commands/messages/basestation.py +43 -0
- pymammotion/mammotion/commands/messages/driver.py +61 -29
- pymammotion/mammotion/commands/messages/media.py +68 -15
- pymammotion/mammotion/commands/messages/navigation.py +61 -25
- pymammotion/mammotion/commands/messages/network.py +93 -100
- pymammotion/mammotion/commands/messages/ota.py +18 -18
- pymammotion/mammotion/commands/messages/system.py +97 -72
- pymammotion/mammotion/commands/messages/video.py +17 -12
- pymammotion/mammotion/devices/__init__.py +27 -3
- pymammotion/mammotion/devices/base.py +50 -127
- pymammotion/mammotion/devices/mammotion.py +447 -212
- pymammotion/mammotion/devices/mammotion_bluetooth.py +105 -60
- pymammotion/mammotion/devices/mammotion_cloud.py +157 -105
- pymammotion/mammotion/devices/mammotion_mower_ble.py +49 -0
- pymammotion/mammotion/devices/mammotion_mower_cloud.py +39 -0
- pymammotion/mammotion/devices/managers/managers.py +81 -0
- pymammotion/mammotion/devices/mower_device.py +124 -0
- pymammotion/mammotion/devices/mower_manager.py +107 -0
- pymammotion/mammotion/devices/rtk_ble.py +89 -0
- pymammotion/mammotion/devices/rtk_cloud.py +113 -0
- pymammotion/mammotion/devices/rtk_device.py +50 -0
- pymammotion/mammotion/devices/rtk_manager.py +122 -0
- pymammotion/mqtt/__init__.py +2 -1
- pymammotion/mqtt/aliyun_mqtt.py +232 -0
- pymammotion/mqtt/linkkit/__init__.py +5 -0
- pymammotion/mqtt/linkkit/h2client.py +585 -0
- pymammotion/mqtt/linkkit/linkkit.py +3023 -0
- pymammotion/mqtt/mammotion_mqtt.py +176 -169
- pymammotion/mqtt/mqtt_models.py +66 -0
- pymammotion/proto/__init__.py +4839 -4
- pymammotion/proto/basestation.proto +8 -0
- pymammotion/proto/basestation_pb2.py +11 -9
- pymammotion/proto/basestation_pb2.pyi +16 -2
- pymammotion/proto/dev_net.proto +79 -55
- pymammotion/proto/dev_net_pb2.py +60 -56
- pymammotion/proto/dev_net_pb2.pyi +49 -6
- pymammotion/proto/luba_msg.proto +2 -1
- pymammotion/proto/luba_msg_pb2.py +6 -6
- pymammotion/proto/luba_msg_pb2.pyi +1 -0
- pymammotion/proto/luba_mul.proto +62 -1
- pymammotion/proto/luba_mul_pb2.py +38 -22
- pymammotion/proto/luba_mul_pb2.pyi +94 -7
- pymammotion/proto/mctrl_driver.proto +44 -4
- pymammotion/proto/mctrl_driver_pb2.py +26 -14
- pymammotion/proto/mctrl_driver_pb2.pyi +66 -11
- pymammotion/proto/mctrl_nav.proto +97 -51
- pymammotion/proto/mctrl_nav_pb2.py +75 -67
- pymammotion/proto/mctrl_nav_pb2.pyi +142 -56
- pymammotion/proto/mctrl_ota.proto +40 -2
- pymammotion/proto/mctrl_ota_pb2.py +23 -13
- pymammotion/proto/mctrl_ota_pb2.pyi +67 -4
- pymammotion/proto/mctrl_pept.proto +8 -3
- pymammotion/proto/mctrl_pept_pb2.py +8 -6
- pymammotion/proto/mctrl_pept_pb2.pyi +14 -6
- pymammotion/proto/mctrl_sys.proto +325 -86
- pymammotion/proto/mctrl_sys_pb2.py +162 -98
- pymammotion/proto/mctrl_sys_pb2.pyi +451 -25
- pymammotion/proto/message_pool.py +3 -0
- pymammotion/proto/py.typed +0 -0
- pymammotion/utility/constant/device_constant.py +65 -21
- pymammotion/utility/datatype_converter.py +13 -12
- pymammotion/utility/device_config.py +755 -0
- pymammotion/utility/device_type.py +218 -21
- pymammotion/utility/map.py +238 -51
- pymammotion/utility/mur_mur_hash.py +159 -0
- {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/METADATA +27 -31
- pymammotion-0.5.51.dist-info/RECORD +152 -0
- {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info}/WHEEL +1 -1
- pymammotion/aliyun/cloud_service.py +0 -65
- pymammotion/data/model/plan.py +0 -58
- pymammotion/data/state_manager.py +0 -130
- pymammotion/proto/basestation.py +0 -59
- pymammotion/proto/common.py +0 -12
- pymammotion/proto/dev_net.py +0 -381
- pymammotion/proto/luba_msg.py +0 -81
- pymammotion/proto/luba_mul.py +0 -76
- pymammotion/proto/mctrl_driver.py +0 -100
- pymammotion/proto/mctrl_nav.py +0 -660
- pymammotion/proto/mctrl_ota.py +0 -48
- pymammotion/proto/mctrl_pept.py +0 -41
- pymammotion/proto/mctrl_sys.py +0 -574
- pymammotion-0.2.62.dist-info/RECORD +0 -125
- /pymammotion/{http/_init_.py → bluetooth/model/__init__.py} +0 -0
- {pymammotion-0.2.62.dist-info → pymammotion-0.5.51.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from mashumaro.config import BaseConfig
|
|
5
4
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
@@ -16,7 +15,7 @@ class DeviceData(DataClassORJSONMixin):
|
|
|
16
15
|
class AepResponse(DataClassORJSONMixin):
|
|
17
16
|
code: int
|
|
18
17
|
data: DeviceData
|
|
19
|
-
id:
|
|
18
|
+
id: str | None = None
|
|
20
19
|
|
|
21
20
|
class Config(BaseConfig):
|
|
22
21
|
omit_default = True
|
|
@@ -1,36 +1,183 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Annotated, Optional
|
|
3
3
|
|
|
4
4
|
from mashumaro.config import BaseConfig
|
|
5
5
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
6
|
+
from mashumaro.types import Alias
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@dataclass
|
|
9
10
|
class Device(DataClassORJSONMixin):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
categoryName: str
|
|
18
|
-
identityAlias: str
|
|
19
|
-
productName: str
|
|
20
|
-
iotId: str
|
|
21
|
-
bindTime: int
|
|
22
|
-
owned: int
|
|
23
|
-
identityId: str
|
|
24
|
-
thingType: str
|
|
11
|
+
"""Unified device model supporting both Device and ShareNotification data"""
|
|
12
|
+
|
|
13
|
+
# Core device fields (from Device model)
|
|
14
|
+
gmt_modified: Annotated[int, Alias("gmtModified")]
|
|
15
|
+
node_type: Annotated[str, Alias("nodeType")]
|
|
16
|
+
device_name: Annotated[str, Alias("deviceName")]
|
|
17
|
+
product_name: Annotated[str, Alias("productName")]
|
|
25
18
|
status: int
|
|
26
|
-
|
|
19
|
+
identity_id: Annotated[str, Alias("identityId")]
|
|
20
|
+
|
|
21
|
+
# Required fields from original Device model
|
|
22
|
+
net_type: Annotated[str, Alias("netType")]
|
|
23
|
+
category_key: Annotated[str, Alias("categoryKey")]
|
|
24
|
+
product_key: Annotated[str, Alias("productKey")]
|
|
25
|
+
is_edge_gateway: Annotated[bool, Alias("isEdgeGateway")]
|
|
26
|
+
category_name: Annotated[str, Alias("categoryName")]
|
|
27
|
+
identity_alias: Annotated[str, Alias("identityAlias")]
|
|
28
|
+
iot_id: Annotated[str, Alias("iotId")]
|
|
29
|
+
bind_time: Annotated[int, Alias("bindTime")]
|
|
30
|
+
owned: int
|
|
31
|
+
thing_type: Annotated[str, Alias("thingType")]
|
|
32
|
+
|
|
33
|
+
# Optional fields (common to both or nullable)
|
|
34
|
+
nick_name: Annotated[Optional[str], Alias("nickName")] = None
|
|
27
35
|
description: Optional[str] = None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
product_image: Annotated[Optional[str], Alias("productImage")] = None
|
|
37
|
+
category_image: Annotated[Optional[str], Alias("categoryImage")] = None
|
|
38
|
+
product_model: Annotated[Optional[str], Alias("productModel")] = None
|
|
39
|
+
|
|
40
|
+
# Optional fields from ShareNotification only
|
|
41
|
+
target_id: Annotated[Optional[str], Alias("targetId")] = None
|
|
42
|
+
receiver_identity_id: Annotated[Optional[str], Alias("receiverIdentityId")] = None
|
|
43
|
+
target_type: Annotated[Optional[str], Alias("targetType")] = None
|
|
44
|
+
gmt_create: Annotated[Optional[int], Alias("gmtCreate")] = None
|
|
45
|
+
batch_id: Annotated[Optional[str], Alias("batchId")] = None
|
|
46
|
+
record_id: Annotated[Optional[str], Alias("recordId")] = None
|
|
47
|
+
initiator_identity_id: Annotated[Optional[str], Alias("initiatorIdentityId")] = None
|
|
48
|
+
is_receiver: Annotated[Optional[int], Alias("isReceiver")] = None
|
|
49
|
+
initiator_alias: Annotated[Optional[str], Alias("initiatorAlias")] = None
|
|
50
|
+
receiver_alias: Annotated[Optional[str], Alias("receiverAlias")] = None
|
|
31
51
|
|
|
32
52
|
class Config(BaseConfig):
|
|
33
53
|
omit_default = True
|
|
54
|
+
allow_deserialization_not_by_alias = True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# # Alternative: Keep them separate but with a common base class
|
|
58
|
+
# @dataclass
|
|
59
|
+
# class BaseDevice(DataClassORJSONMixin):
|
|
60
|
+
# """Base device model with common fields"""
|
|
61
|
+
#
|
|
62
|
+
# gmt_modified: int
|
|
63
|
+
# node_type: str
|
|
64
|
+
# device_name: str
|
|
65
|
+
# product_name: str
|
|
66
|
+
# status: int
|
|
67
|
+
# product_image: Optional[str] = None
|
|
68
|
+
# category_image: Optional[str] = None
|
|
69
|
+
# description: Optional[str] = None
|
|
70
|
+
#
|
|
71
|
+
# class Config(BaseConfig):
|
|
72
|
+
# omit_default = True
|
|
73
|
+
# serialize_by_alias = True
|
|
74
|
+
# aliases = {
|
|
75
|
+
# "gmt_modified": "gmtModified",
|
|
76
|
+
# "node_type": "nodeType",
|
|
77
|
+
# "device_name": "deviceName",
|
|
78
|
+
# "product_name": "productName",
|
|
79
|
+
# "product_image": "productImage",
|
|
80
|
+
# "category_image": "categoryImage",
|
|
81
|
+
# }
|
|
82
|
+
#
|
|
83
|
+
#
|
|
84
|
+
# @dataclass
|
|
85
|
+
# class Device(BaseDevice):
|
|
86
|
+
# """Full device model"""
|
|
87
|
+
#
|
|
88
|
+
# net_type: str
|
|
89
|
+
# category_key: str
|
|
90
|
+
# product_key: str
|
|
91
|
+
# is_edge_gateway: bool
|
|
92
|
+
# category_name: str
|
|
93
|
+
# identity_alias: str
|
|
94
|
+
# iot_id: str
|
|
95
|
+
# bind_time: int
|
|
96
|
+
# owned: int
|
|
97
|
+
# identity_id: str
|
|
98
|
+
# thing_type: str
|
|
99
|
+
# nick_name: Optional[str] = None
|
|
100
|
+
# product_model: Optional[str] = None
|
|
101
|
+
#
|
|
102
|
+
# class Config(BaseConfig):
|
|
103
|
+
# omit_default = True
|
|
104
|
+
# serialize_by_alias = True
|
|
105
|
+
# aliases = {
|
|
106
|
+
# **BaseDevice.Config.aliases,
|
|
107
|
+
# "net_type": "netType",
|
|
108
|
+
# "category_key": "categoryKey",
|
|
109
|
+
# "product_key": "productKey",
|
|
110
|
+
# "is_edge_gateway": "isEdgeGateway",
|
|
111
|
+
# "category_name": "categoryName",
|
|
112
|
+
# "identity_alias": "identityAlias",
|
|
113
|
+
# "iot_id": "iotId",
|
|
114
|
+
# "bind_time": "bindTime",
|
|
115
|
+
# "identity_id": "identityId",
|
|
116
|
+
# "thing_type": "thingType",
|
|
117
|
+
# "nick_name": "nickName",
|
|
118
|
+
# "product_model": "productModel",
|
|
119
|
+
# }
|
|
120
|
+
#
|
|
121
|
+
#
|
|
122
|
+
# @dataclass
|
|
123
|
+
# class ShareNotification(BaseDevice):
|
|
124
|
+
# """Share notification model extending base device"""
|
|
125
|
+
#
|
|
126
|
+
# target_id: str
|
|
127
|
+
# receiver_identity_id: str
|
|
128
|
+
# target_type: str
|
|
129
|
+
# gmt_create: int
|
|
130
|
+
# batch_id: str
|
|
131
|
+
# record_id: str
|
|
132
|
+
# initiator_identity_id: str
|
|
133
|
+
# is_receiver: int
|
|
134
|
+
# initiator_alias: str
|
|
135
|
+
# receiver_alias: str
|
|
136
|
+
#
|
|
137
|
+
# # Optional fields that Device has but ShareNotification might not
|
|
138
|
+
# net_type: Optional[str] = None
|
|
139
|
+
# category_key: Optional[str] = None
|
|
140
|
+
# product_key: Optional[str] = None
|
|
141
|
+
# is_edge_gateway: Optional[bool] = None
|
|
142
|
+
# category_name: Optional[str] = None
|
|
143
|
+
# identity_alias: Optional[str] = None
|
|
144
|
+
# iot_id: Optional[str] = None
|
|
145
|
+
# bind_time: Optional[int] = None
|
|
146
|
+
# owned: Optional[int] = None
|
|
147
|
+
# identity_id: Optional[str] = None
|
|
148
|
+
# thing_type: Optional[str] = None
|
|
149
|
+
# nick_name: Optional[str] = None
|
|
150
|
+
# product_model: Optional[str] = None
|
|
151
|
+
#
|
|
152
|
+
# class Config(BaseConfig):
|
|
153
|
+
# omit_default = True
|
|
154
|
+
# serialize_by_alias = True
|
|
155
|
+
# aliases = {
|
|
156
|
+
# **BaseDevice.Config.aliases,
|
|
157
|
+
# "target_id": "targetId",
|
|
158
|
+
# "receiver_identity_id": "receiverIdentityId",
|
|
159
|
+
# "target_type": "targetType",
|
|
160
|
+
# "gmt_create": "gmtCreate",
|
|
161
|
+
# "batch_id": "batchId",
|
|
162
|
+
# "record_id": "recordId",
|
|
163
|
+
# "initiator_identity_id": "initiatorIdentityId",
|
|
164
|
+
# "is_receiver": "isReceiver",
|
|
165
|
+
# "initiator_alias": "initiatorAlias",
|
|
166
|
+
# "receiver_alias": "receiverAlias",
|
|
167
|
+
# # Device fields that might be present
|
|
168
|
+
# "net_type": "netType",
|
|
169
|
+
# "category_key": "categoryKey",
|
|
170
|
+
# "product_key": "productKey",
|
|
171
|
+
# "is_edge_gateway": "isEdgeGateway",
|
|
172
|
+
# "category_name": "categoryName",
|
|
173
|
+
# "identity_alias": "identityAlias",
|
|
174
|
+
# "iot_id": "iotId",
|
|
175
|
+
# "bind_time": "bindTime",
|
|
176
|
+
# "identity_id": "identityId",
|
|
177
|
+
# "thing_type": "thingType",
|
|
178
|
+
# "nick_name": "nickName",
|
|
179
|
+
# "product_model": "productModel",
|
|
180
|
+
# }
|
|
34
181
|
|
|
35
182
|
|
|
36
183
|
@dataclass
|
|
@@ -42,7 +189,7 @@ class Data(DataClassORJSONMixin):
|
|
|
42
189
|
|
|
43
190
|
|
|
44
191
|
@dataclass
|
|
45
|
-
class
|
|
192
|
+
class ListingDevAccountResponse(DataClassORJSONMixin):
|
|
46
193
|
code: int
|
|
47
|
-
data:
|
|
48
|
-
id:
|
|
194
|
+
data: Data | None
|
|
195
|
+
id: str | None = None
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
5
4
|
|
|
@@ -18,7 +17,7 @@ class OpenAccount(DataClassORJSONMixin):
|
|
|
18
17
|
domainId: int
|
|
19
18
|
enableDevice: str
|
|
20
19
|
status: int
|
|
21
|
-
country:
|
|
20
|
+
country: str | None = None
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
@dataclass
|
|
@@ -54,7 +53,7 @@ class InnerData(DataClassORJSONMixin):
|
|
|
54
53
|
subCode: int
|
|
55
54
|
message: str
|
|
56
55
|
successful: str
|
|
57
|
-
deviceId:
|
|
56
|
+
deviceId: str | None = None
|
|
58
57
|
|
|
59
58
|
|
|
60
59
|
@dataclass
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import TypeVar
|
|
3
3
|
|
|
4
4
|
from mashumaro.config import BaseConfig
|
|
5
5
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
@@ -22,8 +22,8 @@ class RegionResponseData(DataClassORJSONMixin):
|
|
|
22
22
|
class RegionResponse(DataClassORJSONMixin):
|
|
23
23
|
data: RegionResponseData
|
|
24
24
|
code: int
|
|
25
|
-
id:
|
|
26
|
-
msg:
|
|
25
|
+
id: str | None = None
|
|
26
|
+
msg: str | None = None
|
|
27
27
|
|
|
28
28
|
class Config(BaseConfig):
|
|
29
29
|
omit_default = True
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
5
4
|
|
|
@@ -16,4 +15,5 @@ class SessionOauthToken(DataClassORJSONMixin):
|
|
|
16
15
|
@dataclass
|
|
17
16
|
class SessionByAuthCodeResponse(DataClassORJSONMixin):
|
|
18
17
|
code: int
|
|
19
|
-
data:
|
|
18
|
+
data: SessionOauthToken | None = None
|
|
19
|
+
token_issued_at: int | None = None
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
4
|
+
|
|
5
|
+
from pymammotion.data.mqtt.properties import Items
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ThingPropertiesResponse(DataClassORJSONMixin):
|
|
10
|
+
code: int
|
|
11
|
+
data: Items | None
|
|
12
|
+
id: str | None = None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
region_mappings = {
|
|
2
|
+
# Asia Pacific
|
|
3
|
+
"CN": "cn-hongkong", # China -> Hong Kong
|
|
4
|
+
"HK": "cn-hongkong", # Hong Kong
|
|
5
|
+
"TW": "cn-hongkong", # Taiwan -> Hong Kong
|
|
6
|
+
"MO": "cn-hongkong", # Macau -> Hong Kong
|
|
7
|
+
# Southeast Asia
|
|
8
|
+
"SG": "ap-southeast-1", # Singapore
|
|
9
|
+
"MY": "ap-southeast-3", # Malaysia
|
|
10
|
+
"ID": "ap-southeast-5", # Indonesia
|
|
11
|
+
"TH": "ap-southeast-1", # Thailand -> Singapore
|
|
12
|
+
"VN": "ap-southeast-1", # Vietnam -> Singapore
|
|
13
|
+
"PH": "ap-southeast-1", # Philippines -> Singapore
|
|
14
|
+
"BN": "ap-southeast-1", # Brunei -> Singapore
|
|
15
|
+
"KH": "ap-southeast-1", # Cambodia -> Singapore
|
|
16
|
+
"LA": "ap-southeast-1", # Laos -> Singapore
|
|
17
|
+
"MM": "ap-southeast-1", # Myanmar -> Singapore
|
|
18
|
+
# Australia/Oceania
|
|
19
|
+
"AU": "ap-southeast-2", # Australia
|
|
20
|
+
"NZ": "ap-southeast-2", # New Zealand -> Australia
|
|
21
|
+
"FJ": "ap-southeast-2", # Fiji -> Australia
|
|
22
|
+
"PG": "ap-southeast-2", # Papua New Guinea -> Australia
|
|
23
|
+
# South Asia
|
|
24
|
+
"IN": "ap-south-1", # India
|
|
25
|
+
"LK": "ap-south-1", # Sri Lanka -> India
|
|
26
|
+
"PK": "ap-south-1", # Pakistan -> India
|
|
27
|
+
"BD": "ap-south-1", # Bangladesh -> India
|
|
28
|
+
"NP": "ap-south-1", # Nepal -> India
|
|
29
|
+
"BT": "ap-south-1", # Bhutan -> India
|
|
30
|
+
"MV": "ap-south-1", # Maldives -> India
|
|
31
|
+
# Middle East
|
|
32
|
+
"AE": "me-east-1", # UAE
|
|
33
|
+
"SA": "me-east-1", # Saudi Arabia -> UAE
|
|
34
|
+
"QA": "me-east-1", # Qatar -> UAE
|
|
35
|
+
"KW": "me-east-1", # Kuwait -> UAE
|
|
36
|
+
"BH": "me-east-1", # Bahrain -> UAE
|
|
37
|
+
"OM": "me-east-1", # Oman -> UAE
|
|
38
|
+
"IL": "me-east-1", # Israel -> UAE
|
|
39
|
+
"TR": "me-east-1", # Turkey -> UAE
|
|
40
|
+
# Europe
|
|
41
|
+
"DE": "eu-central-1", # Germany
|
|
42
|
+
"FR": "eu-central-1", # France -> Germany
|
|
43
|
+
"IT": "eu-central-1", # Italy -> Germany
|
|
44
|
+
"ES": "eu-central-1", # Spain -> Germany
|
|
45
|
+
"GB": "eu-central-1", # UK -> Germany
|
|
46
|
+
"IE": "eu-central-1", # Ireland -> Germany
|
|
47
|
+
"NL": "eu-central-1", # Netherlands -> Germany
|
|
48
|
+
"BE": "eu-central-1", # Belgium -> Germany
|
|
49
|
+
"CH": "eu-central-1", # Switzerland -> Germany
|
|
50
|
+
"AT": "eu-central-1", # Austria -> Germany
|
|
51
|
+
"PL": "eu-central-1", # poland -> Germany
|
|
52
|
+
# North America
|
|
53
|
+
"US": "us-east-1", # USA
|
|
54
|
+
"CA": "us-east-1", # Canada -> US East
|
|
55
|
+
"MX": "us-west-1", # Mexico -> US West
|
|
56
|
+
# South America
|
|
57
|
+
"BR": "us-east-1", # Brazil -> US East
|
|
58
|
+
"AR": "us-east-1", # Argentina -> US East
|
|
59
|
+
"CL": "us-east-1", # Chile -> US East
|
|
60
|
+
"CO": "us-east-1", # Colombia -> US East
|
|
61
|
+
"PE": "us-east-1", # Peru -> US East
|
|
62
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
5
|
+
import ssl
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import urlencode, urlparse
|
|
9
|
+
|
|
10
|
+
import aiohttp
|
|
11
|
+
import certifi
|
|
12
|
+
from requests import PreparedRequest, adapters, status_codes
|
|
13
|
+
from Tea.exceptions import RequiredArgumentException, RetryError
|
|
14
|
+
from Tea.model import TeaModel
|
|
15
|
+
from Tea.request import TeaRequest
|
|
16
|
+
from Tea.response import TeaResponse
|
|
17
|
+
from Tea.stream import BaseStream
|
|
18
|
+
|
|
19
|
+
DEFAULT_CONNECT_TIMEOUT = 5000
|
|
20
|
+
DEFAULT_READ_TIMEOUT = 10000
|
|
21
|
+
DEFAULT_POOL_SIZE = 10
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("alibabacloud-tea")
|
|
24
|
+
logger.setLevel(logging.DEBUG)
|
|
25
|
+
ch = logging.StreamHandler()
|
|
26
|
+
logger.addHandler(ch)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TeaCore:
|
|
30
|
+
http_adapter = adapters.HTTPAdapter(pool_connections=DEFAULT_POOL_SIZE, pool_maxsize=DEFAULT_POOL_SIZE * 4)
|
|
31
|
+
https_adapter = adapters.HTTPAdapter(pool_connections=DEFAULT_POOL_SIZE, pool_maxsize=DEFAULT_POOL_SIZE * 4)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def get_adapter(prefix):
|
|
35
|
+
if prefix.upper() == "HTTP":
|
|
36
|
+
return TeaCore.http_adapter
|
|
37
|
+
else:
|
|
38
|
+
return TeaCore.https_adapter
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def _prepare_http_debug(request, symbol):
|
|
42
|
+
base = ""
|
|
43
|
+
for key, value in request.headers.items():
|
|
44
|
+
base += f"\n{symbol} {key} : {value}"
|
|
45
|
+
return base
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _do_http_debug(request, response) -> None:
|
|
49
|
+
# logger the request
|
|
50
|
+
url = urlparse(request.url)
|
|
51
|
+
request_base = f"\n> {request.method.upper()} {url.path + url.query} HTTP/1.1"
|
|
52
|
+
logger.debug(request_base + TeaCore._prepare_http_debug(request, ">"))
|
|
53
|
+
|
|
54
|
+
# logger the response
|
|
55
|
+
response_base = (
|
|
56
|
+
f"\n< HTTP/1.1 {response.status_code}" f" {status_codes._codes.get(response.status_code)[0].upper()}"
|
|
57
|
+
)
|
|
58
|
+
logger.debug(response_base + TeaCore._prepare_http_debug(response, "<"))
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def compose_url(request):
|
|
62
|
+
host = request.headers.get("host")
|
|
63
|
+
if not host:
|
|
64
|
+
raise RequiredArgumentException("endpoint")
|
|
65
|
+
else:
|
|
66
|
+
host = host.rstrip("/")
|
|
67
|
+
protocol = f"{request.protocol.lower()}://"
|
|
68
|
+
pathname = request.pathname
|
|
69
|
+
|
|
70
|
+
if host.startswith(("http://", "https://")):
|
|
71
|
+
protocol = ""
|
|
72
|
+
|
|
73
|
+
if request.port == 80:
|
|
74
|
+
port = ""
|
|
75
|
+
else:
|
|
76
|
+
port = f":{request.port}"
|
|
77
|
+
|
|
78
|
+
url = protocol + host + port + pathname
|
|
79
|
+
|
|
80
|
+
if request.query:
|
|
81
|
+
if "?" in url:
|
|
82
|
+
if not url.endswith("&"):
|
|
83
|
+
url += "&"
|
|
84
|
+
else:
|
|
85
|
+
url += "?"
|
|
86
|
+
|
|
87
|
+
encode_query = {}
|
|
88
|
+
for key in request.query:
|
|
89
|
+
value = request.query[key]
|
|
90
|
+
if value is not None:
|
|
91
|
+
encode_query[key] = str(value)
|
|
92
|
+
url += urlencode(encode_query)
|
|
93
|
+
return url.rstrip("?&")
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
async def async_do_action(request: TeaRequest, runtime_option=None) -> TeaResponse:
|
|
97
|
+
runtime_option = runtime_option or {}
|
|
98
|
+
|
|
99
|
+
url = TeaCore.compose_url(request)
|
|
100
|
+
verify = not runtime_option.get("ignoreSSL", False)
|
|
101
|
+
|
|
102
|
+
timeout = runtime_option.get("timeout")
|
|
103
|
+
connect_timeout = runtime_option.get("connectTimeout") or timeout or DEFAULT_CONNECT_TIMEOUT
|
|
104
|
+
read_timeout = runtime_option.get("readTimeout") or timeout or DEFAULT_READ_TIMEOUT
|
|
105
|
+
|
|
106
|
+
connect_timeout, read_timeout = (int(connect_timeout) / 1000, int(read_timeout) / 1000)
|
|
107
|
+
|
|
108
|
+
proxy = None
|
|
109
|
+
if request.protocol.upper() == "HTTP":
|
|
110
|
+
proxy = runtime_option.get("httpProxy")
|
|
111
|
+
if not proxy:
|
|
112
|
+
proxy = os.environ.get("HTTP_PROXY") or os.environ.get("http_proxy")
|
|
113
|
+
elif request.protocol.upper() == "HTTPS":
|
|
114
|
+
proxy = runtime_option.get("httpsProxy")
|
|
115
|
+
if not proxy:
|
|
116
|
+
proxy = os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy")
|
|
117
|
+
|
|
118
|
+
connector = None
|
|
119
|
+
ca_cert = certifi.where()
|
|
120
|
+
if ca_cert and request.protocol.upper() == "HTTPS":
|
|
121
|
+
loop = asyncio.get_event_loop()
|
|
122
|
+
|
|
123
|
+
ssl_context = await loop.run_in_executor(None, ssl.create_default_context, ssl.Purpose.SERVER_AUTH)
|
|
124
|
+
await loop.run_in_executor(None, ssl_context.load_verify_locations, ca_cert)
|
|
125
|
+
connector = aiohttp.TCPConnector(
|
|
126
|
+
ssl=ssl_context,
|
|
127
|
+
family=socket.AF_INET,
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
verify = False
|
|
131
|
+
|
|
132
|
+
timeout = aiohttp.ClientTimeout(sock_read=read_timeout, sock_connect=connect_timeout)
|
|
133
|
+
async with aiohttp.ClientSession(connector=connector) as s:
|
|
134
|
+
body = b""
|
|
135
|
+
if isinstance(request.body, BaseStream):
|
|
136
|
+
for content in request.body:
|
|
137
|
+
body += content
|
|
138
|
+
elif isinstance(request.body, str):
|
|
139
|
+
body = request.body.encode("utf-8")
|
|
140
|
+
else:
|
|
141
|
+
body = request.body
|
|
142
|
+
try:
|
|
143
|
+
async with s.request(
|
|
144
|
+
request.method, url, data=body, headers=request.headers, ssl=verify, proxy=proxy, timeout=timeout
|
|
145
|
+
) as response:
|
|
146
|
+
tea_resp = TeaResponse()
|
|
147
|
+
tea_resp.body = await response.read()
|
|
148
|
+
tea_resp.headers = {k.lower(): v for k, v in response.headers.items()}
|
|
149
|
+
tea_resp.status_code = response.status
|
|
150
|
+
tea_resp.status_message = response.reason
|
|
151
|
+
tea_resp.response = response
|
|
152
|
+
except OSError as e:
|
|
153
|
+
raise RetryError(str(e))
|
|
154
|
+
return tea_resp
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def do_action(request: TeaRequest, runtime_option=None) -> TeaResponse:
|
|
158
|
+
url = TeaCore.compose_url(request)
|
|
159
|
+
|
|
160
|
+
runtime_option = runtime_option or {}
|
|
161
|
+
|
|
162
|
+
verify = not runtime_option.get("ignoreSSL", False)
|
|
163
|
+
if verify:
|
|
164
|
+
verify = runtime_option.get("ca", True) if runtime_option.get("ca", True) is not None else True
|
|
165
|
+
cert = runtime_option.get("cert", None)
|
|
166
|
+
|
|
167
|
+
timeout = runtime_option.get("timeout")
|
|
168
|
+
connect_timeout = runtime_option.get("connectTimeout") or timeout or DEFAULT_CONNECT_TIMEOUT
|
|
169
|
+
read_timeout = runtime_option.get("readTimeout") or timeout or DEFAULT_READ_TIMEOUT
|
|
170
|
+
|
|
171
|
+
timeout = (int(connect_timeout) / 1000, int(read_timeout) / 1000)
|
|
172
|
+
|
|
173
|
+
if isinstance(request.body, str):
|
|
174
|
+
request.body = request.body.encode("utf-8")
|
|
175
|
+
|
|
176
|
+
p = PreparedRequest()
|
|
177
|
+
p.prepare(
|
|
178
|
+
method=request.method.upper(),
|
|
179
|
+
url=url,
|
|
180
|
+
data=request.body,
|
|
181
|
+
headers=request.headers,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
proxies = {}
|
|
185
|
+
http_proxy = runtime_option.get("httpProxy")
|
|
186
|
+
https_proxy = runtime_option.get("httpsProxy")
|
|
187
|
+
no_proxy = runtime_option.get("noProxy")
|
|
188
|
+
|
|
189
|
+
if not http_proxy:
|
|
190
|
+
http_proxy = os.environ.get("HTTP_PROXY") or os.environ.get("http_proxy")
|
|
191
|
+
if not https_proxy:
|
|
192
|
+
https_proxy = os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy")
|
|
193
|
+
|
|
194
|
+
if http_proxy:
|
|
195
|
+
proxies["http"] = http_proxy
|
|
196
|
+
if https_proxy:
|
|
197
|
+
proxies["https"] = https_proxy
|
|
198
|
+
if no_proxy:
|
|
199
|
+
proxies["no_proxy"] = no_proxy
|
|
200
|
+
|
|
201
|
+
adapter = TeaCore.get_adapter(request.protocol)
|
|
202
|
+
try:
|
|
203
|
+
resp = adapter.send(
|
|
204
|
+
p,
|
|
205
|
+
proxies=proxies,
|
|
206
|
+
timeout=timeout,
|
|
207
|
+
verify=verify,
|
|
208
|
+
cert=cert,
|
|
209
|
+
)
|
|
210
|
+
except OSError as e:
|
|
211
|
+
raise RetryError(str(e))
|
|
212
|
+
|
|
213
|
+
debug = runtime_option.get("debug") or os.getenv("DEBUG")
|
|
214
|
+
if debug and debug.lower() == "sdk":
|
|
215
|
+
TeaCore._do_http_debug(p, resp)
|
|
216
|
+
|
|
217
|
+
response = TeaResponse()
|
|
218
|
+
response.status_message = resp.reason
|
|
219
|
+
response.status_code = resp.status_code
|
|
220
|
+
response.headers = {k.lower(): v for k, v in resp.headers.items()}
|
|
221
|
+
response.body = resp.content
|
|
222
|
+
response.response = resp
|
|
223
|
+
return response
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def get_response_body(resp) -> str:
|
|
227
|
+
return resp.content.decode("utf-8")
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def allow_retry(dic, retry_times, now=None) -> bool:
|
|
231
|
+
if retry_times == 0:
|
|
232
|
+
return True
|
|
233
|
+
if dic is None or not dic.__contains__("maxAttempts") or dic.get("retryable") is not True and retry_times >= 1:
|
|
234
|
+
return False
|
|
235
|
+
else:
|
|
236
|
+
retry = 0 if dic.get("maxAttempts") is None else int(dic.get("maxAttempts"))
|
|
237
|
+
return retry >= retry_times
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def get_backoff_time(dic, retry_times) -> int:
|
|
241
|
+
default_back_off_time = 0
|
|
242
|
+
if dic is None or not dic.get("policy") or dic.get("policy") == "no":
|
|
243
|
+
return default_back_off_time
|
|
244
|
+
|
|
245
|
+
back_off_time = dic.get("period", default_back_off_time)
|
|
246
|
+
if not isinstance(back_off_time, int) and not (isinstance(back_off_time, str) and back_off_time.isdigit()):
|
|
247
|
+
return default_back_off_time
|
|
248
|
+
|
|
249
|
+
back_off_time = int(back_off_time)
|
|
250
|
+
if back_off_time < 0:
|
|
251
|
+
return retry_times
|
|
252
|
+
|
|
253
|
+
return back_off_time
|
|
254
|
+
|
|
255
|
+
@staticmethod
|
|
256
|
+
async def sleep_async(t) -> None:
|
|
257
|
+
await asyncio.sleep(t)
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def sleep(t) -> None:
|
|
261
|
+
time.sleep(t)
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def is_retryable(ex) -> bool:
|
|
265
|
+
return isinstance(ex, RetryError)
|
|
266
|
+
|
|
267
|
+
@staticmethod
|
|
268
|
+
def bytes_readable(body):
|
|
269
|
+
return body
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def merge(*dic_list) -> dict:
|
|
273
|
+
dic_result = {}
|
|
274
|
+
for item in dic_list:
|
|
275
|
+
if isinstance(item, dict):
|
|
276
|
+
dic_result.update(item)
|
|
277
|
+
elif isinstance(item, TeaModel):
|
|
278
|
+
dic_result.update(item.to_map())
|
|
279
|
+
return dic_result
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def to_map(model: TeaModel | None) -> dict[str, Any]:
|
|
283
|
+
if isinstance(model, TeaModel):
|
|
284
|
+
return model.to_map()
|
|
285
|
+
else:
|
|
286
|
+
return dict()
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def from_map(model: TeaModel, dic: dict[str, Any]) -> TeaModel:
|
|
290
|
+
if isinstance(model, TeaModel):
|
|
291
|
+
try:
|
|
292
|
+
return model.from_map(dic)
|
|
293
|
+
except Exception:
|
|
294
|
+
model._map = dic
|
|
295
|
+
return model
|
|
296
|
+
else:
|
|
297
|
+
return model
|