appmesh 1.6.11__py3-none-any.whl → 1.6.13__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.
- appmesh/__init__.py +29 -15
- appmesh/app.py +108 -92
- appmesh/app_output.py +16 -15
- appmesh/app_run.py +15 -19
- appmesh/tcp_messages.py +50 -53
- appmesh/tcp_transport.py +111 -85
- {appmesh-1.6.11.dist-info → appmesh-1.6.13.dist-info}/METADATA +3 -2
- appmesh-1.6.13.dist-info/RECORD +16 -0
- appmesh-1.6.11.dist-info/RECORD +0 -16
- {appmesh-1.6.11.dist-info → appmesh-1.6.13.dist-info}/WHEEL +0 -0
- {appmesh-1.6.11.dist-info → appmesh-1.6.13.dist-info}/top_level.txt +0 -0
appmesh/__init__.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# __init__.py
|
2
2
|
"""
|
3
|
-
App Mesh SDK package initializer.
|
4
|
-
|
5
|
-
This module exports the main client classes used to interact with the App Mesh API.
|
3
|
+
App Mesh SDK package initializer with lazy loading support.
|
6
4
|
|
7
5
|
Example:
|
8
6
|
from appmesh import AppMeshClient
|
@@ -12,9 +10,18 @@ Example:
|
|
12
10
|
import sys
|
13
11
|
from types import ModuleType
|
14
12
|
from typing import TYPE_CHECKING
|
13
|
+
from importlib import import_module
|
15
14
|
|
16
|
-
__all__ = [
|
15
|
+
__all__ = [
|
16
|
+
"App",
|
17
|
+
"AppMeshClient",
|
18
|
+
"AppMeshClientTCP",
|
19
|
+
"AppMeshClientOAuth",
|
20
|
+
"AppMeshServer",
|
21
|
+
"AppMeshServerTCP",
|
22
|
+
]
|
17
23
|
|
24
|
+
# Lazy import configuration
|
18
25
|
_LAZY_IMPORTS = {
|
19
26
|
"App": ("app", "App"), # from .app import App
|
20
27
|
"AppMeshClient": ("client_http", "AppMeshClient"), # from .client_http import AppMeshClient
|
@@ -24,10 +31,8 @@ _LAZY_IMPORTS = {
|
|
24
31
|
"AppMeshServerTCP": ("server_tcp", "AppMeshServerTCP"), # from .server_tcp import AppMeshServerTCP
|
25
32
|
}
|
26
33
|
|
27
|
-
|
28
34
|
if TYPE_CHECKING:
|
29
|
-
#
|
30
|
-
# These imports are only executed during type checking and won't affect runtime.
|
35
|
+
# Type checking imports (not executed at runtime)
|
31
36
|
from .app import App # noqa: F401
|
32
37
|
from .client_http import AppMeshClient # noqa: F401
|
33
38
|
from .client_tcp import AppMeshClientTCP # noqa: F401
|
@@ -36,25 +41,34 @@ if TYPE_CHECKING:
|
|
36
41
|
from .server_tcp import AppMeshServerTCP # noqa: F401
|
37
42
|
|
38
43
|
|
39
|
-
def _lazy_import(name):
|
40
|
-
"""
|
44
|
+
def _lazy_import(name: str):
|
45
|
+
"""
|
46
|
+
Internal helper for lazy import resolution using PEP 562.
|
47
|
+
Only imports modules when accessed, improving startup time.
|
48
|
+
"""
|
41
49
|
if name in _LAZY_IMPORTS:
|
42
50
|
module_name, attr_name = _LAZY_IMPORTS[name]
|
43
|
-
module =
|
44
|
-
|
51
|
+
module = import_module(f".{module_name}", __name__)
|
52
|
+
globals()[name] = getattr(module, attr_name)
|
53
|
+
return globals()[name]
|
45
54
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
46
55
|
|
47
56
|
|
48
|
-
|
57
|
+
def __dir__():
|
58
|
+
"""Provide tab-completion support for lazy-loaded attributes."""
|
59
|
+
return sorted(__all__ + list(globals().keys()))
|
49
60
|
|
50
|
-
def __getattr__(name):
|
51
|
-
return _lazy_import(name)
|
52
61
|
|
62
|
+
if sys.version_info >= (3, 7):
|
63
|
+
__getattr__ = _lazy_import
|
53
64
|
else:
|
54
|
-
# Python 3.6 compatibility
|
65
|
+
# Python 3.6 compatibility via module replacement
|
55
66
|
class _LazyModule(ModuleType):
|
56
67
|
def __getattr__(self, name):
|
57
68
|
return _lazy_import(name)
|
58
69
|
|
70
|
+
def __dir__(self):
|
71
|
+
return sorted(__all__ + list(globals().keys()))
|
72
|
+
|
59
73
|
sys.modules[__name__] = _LazyModule(__name__)
|
60
74
|
sys.modules[__name__].__dict__.update(globals())
|
appmesh/app.py
CHANGED
@@ -3,49 +3,55 @@
|
|
3
3
|
|
4
4
|
import json
|
5
5
|
import copy
|
6
|
-
|
7
6
|
from datetime import datetime
|
8
|
-
from typing import Optional, Any
|
7
|
+
from typing import Optional, Any, Dict
|
9
8
|
from enum import Enum, unique
|
10
9
|
|
11
|
-
# pylint: disable=line-too-long
|
12
10
|
|
11
|
+
def _get_str(data: Optional[dict], key: str) -> Optional[str]:
|
12
|
+
"""Retrieve a string value from a dictionary by key, if it exists and is a valid string."""
|
13
|
+
if not data or key not in data:
|
14
|
+
return None
|
15
|
+
value = data[key]
|
16
|
+
return value if value and isinstance(value, str) else None
|
13
17
|
|
14
|
-
class App:
|
15
|
-
"""
|
16
|
-
An application in App Mesh, include all the process attributes, resource limitations, behaviors, and permissions.
|
17
|
-
"""
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
return
|
19
|
+
def _get_int(data: Optional[dict], key: str) -> Optional[int]:
|
20
|
+
"""Retrieve an integer value from a dictionary by key, if it exists and is a valid integer."""
|
21
|
+
if not data or key not in data or data[key] is None:
|
22
|
+
return None
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
value = data[key]
|
25
|
+
if isinstance(value, int):
|
26
|
+
return value
|
27
|
+
if isinstance(value, str) and value.isdigit():
|
28
|
+
return int(value)
|
29
|
+
return None
|
30
|
+
|
31
|
+
|
32
|
+
def _get_bool(data: Optional[dict], key: str) -> Optional[bool]:
|
33
|
+
"""Retrieve a boolean value from a dictionary by key, if it exists and is boolean-like."""
|
34
|
+
if not data or key not in data or data[key] is None:
|
32
35
|
return None
|
36
|
+
return bool(data[key])
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
return bool(data[key])
|
38
|
+
|
39
|
+
def _get_item(data: Optional[dict], key: str) -> Optional[Any]:
|
40
|
+
"""Retrieve a deep copy of a value from a dictionary by key, if it exists."""
|
41
|
+
if not data or key not in data or data[key] is None:
|
39
42
|
return None
|
43
|
+
return copy.deepcopy(data[key])
|
40
44
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
|
46
|
+
class App:
|
47
|
+
"""
|
48
|
+
An application in App Mesh, include all the process attributes,
|
49
|
+
resource limitations, behaviors, and permissions.
|
50
|
+
"""
|
45
51
|
|
46
52
|
@unique
|
47
53
|
class Permission(Enum):
|
48
|
-
"""
|
54
|
+
"""Application permission levels."""
|
49
55
|
|
50
56
|
DENY = "1"
|
51
57
|
READ = "2"
|
@@ -53,26 +59,26 @@ class App:
|
|
53
59
|
|
54
60
|
class Behavior:
|
55
61
|
"""
|
56
|
-
|
62
|
+
Application error handling behavior, including exit and control behaviors.
|
57
63
|
"""
|
58
64
|
|
59
65
|
@unique
|
60
66
|
class Action(Enum):
|
61
|
-
"""
|
67
|
+
"""Actions for application exit behaviors."""
|
62
68
|
|
63
69
|
RESTART = "restart"
|
64
70
|
STANDBY = "standby"
|
65
71
|
KEEPALIVE = "keepalive"
|
66
72
|
REMOVE = "remove"
|
67
73
|
|
68
|
-
def __init__(self, data=None) -> None:
|
74
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
69
75
|
if isinstance(data, (str, bytes, bytearray)):
|
70
76
|
data = json.loads(data)
|
71
77
|
|
72
|
-
self.exit =
|
78
|
+
self.exit = _get_str(data, "exit")
|
73
79
|
"""Default exit behavior, options: 'restart', 'standby', 'keepalive', 'remove'."""
|
74
80
|
|
75
|
-
self.control =
|
81
|
+
self.control = _get_item(data, "control") or {}
|
76
82
|
"""Exit code specific behavior (e.g, --control 0:restart --control 1:standby), higher priority than default exit behavior"""
|
77
83
|
|
78
84
|
def set_exit_behavior(self, action: "App.Behavior.Action") -> None:
|
@@ -85,17 +91,17 @@ class App:
|
|
85
91
|
|
86
92
|
class DailyLimitation:
|
87
93
|
"""
|
88
|
-
|
94
|
+
Application availability within a daily time range.
|
89
95
|
"""
|
90
96
|
|
91
|
-
def __init__(self, data=None) -> None:
|
97
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
92
98
|
if isinstance(data, (str, bytes, bytearray)):
|
93
99
|
data = json.loads(data)
|
94
100
|
|
95
|
-
self.daily_start =
|
101
|
+
self.daily_start = _get_int(data, "daily_start")
|
96
102
|
"""Start time for application availability (e.g., 09:00:00+08)."""
|
97
103
|
|
98
|
-
self.daily_end =
|
104
|
+
self.daily_end = _get_int(data, "daily_end")
|
99
105
|
"""End time for application availability (e.g., 09:00:00+08)."""
|
100
106
|
|
101
107
|
def set_daily_range(self, start: datetime, end: datetime) -> None:
|
@@ -105,106 +111,112 @@ class App:
|
|
105
111
|
|
106
112
|
class ResourceLimitation:
|
107
113
|
"""
|
108
|
-
|
114
|
+
Application resource limits, such as CPU and memory usage.
|
109
115
|
"""
|
110
116
|
|
111
|
-
def __init__(self, data=None) -> None:
|
117
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
112
118
|
if isinstance(data, (str, bytes, bytearray)):
|
113
119
|
data = json.loads(data)
|
114
120
|
|
115
|
-
self.cpu_shares =
|
121
|
+
self.cpu_shares = _get_int(data, "cpu_shares")
|
116
122
|
"""CPU shares, relative weight of CPU usage."""
|
117
123
|
|
118
|
-
self.memory_mb =
|
124
|
+
self.memory_mb = _get_int(data, "memory_mb")
|
119
125
|
"""Physical memory limit in MB."""
|
120
126
|
|
121
|
-
self.memory_virt_mb =
|
127
|
+
self.memory_virt_mb = _get_int(data, "memory_virt_mb")
|
122
128
|
"""Virtual memory limit in MB."""
|
123
129
|
|
124
|
-
def __init__(self, data=None) -> None:
|
130
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
125
131
|
"""Initialize an App instance with optional configuration data."""
|
126
132
|
if isinstance(data, (str, bytes, bytearray)):
|
127
133
|
data = json.loads(data)
|
128
134
|
|
129
|
-
|
135
|
+
# Application configuration
|
136
|
+
self.name = _get_str(data, "name")
|
130
137
|
"""application name (unique)"""
|
131
|
-
self.command =
|
138
|
+
self.command = _get_str(data, "command")
|
132
139
|
"""full command line with arguments"""
|
133
|
-
self.shell =
|
140
|
+
self.shell = _get_bool(data, "shell")
|
134
141
|
"""use shell mode, cmd can be more shell commands with string format"""
|
135
|
-
self.session_login =
|
142
|
+
self.session_login = _get_bool(data, "session_login")
|
136
143
|
"""app run in session login mode"""
|
137
|
-
self.description =
|
144
|
+
self.description = _get_str(data, "description")
|
138
145
|
"""application description string"""
|
139
|
-
self.metadata =
|
146
|
+
self.metadata = _get_item(data, "metadata")
|
140
147
|
"""metadata string/JSON (input for application, pass to process stdin)"""
|
141
|
-
self.working_dir =
|
148
|
+
self.working_dir = _get_str(data, "working_dir")
|
142
149
|
"""working directory"""
|
143
|
-
self.status =
|
150
|
+
self.status = _get_int(data, "status")
|
144
151
|
"""initial application status (true is enable, false is disabled)"""
|
145
|
-
self.docker_image =
|
152
|
+
self.docker_image = _get_str(data, "docker_image")
|
146
153
|
"""docker image which used to run command line (for docker container application)"""
|
147
|
-
self.stdout_cache_num =
|
154
|
+
self.stdout_cache_num = _get_int(data, "stdout_cache_num")
|
148
155
|
"""stdout file cache number"""
|
149
|
-
self.start_time =
|
156
|
+
self.start_time = _get_int(data, "start_time")
|
150
157
|
"""start date time for app (ISO8601 time format, e.g., '2020-10-11T09:22:05')"""
|
151
|
-
self.end_time =
|
158
|
+
self.end_time = _get_int(data, "end_time")
|
152
159
|
"""end date time for app (ISO8601 time format, e.g., '2020-10-11T10:22:05')"""
|
153
|
-
self.interval =
|
160
|
+
self.interval = _get_int(data, "interval")
|
154
161
|
"""start interval seconds for short running app, support ISO 8601 durations and cron expression (e.g., 'P1Y2M3DT4H5M6S' 'P5W' '* */5 * * * *')"""
|
155
|
-
self.cron =
|
162
|
+
self.cron = _get_bool(data, "cron")
|
156
163
|
"""indicate interval parameter use cron expression or not"""
|
157
|
-
self.daily_limitation = App.DailyLimitation(
|
158
|
-
self.retention =
|
164
|
+
self.daily_limitation = App.DailyLimitation(_get_item(data, "daily_limitation"))
|
165
|
+
self.retention = _get_str(data, "retention")
|
159
166
|
"""extra timeout seconds for stopping current process, support ISO 8601 durations (e.g., 'P1Y2M3DT4H5M6S' 'P5W')."""
|
160
|
-
self.health_check_cmd =
|
167
|
+
self.health_check_cmd = _get_str(data, "health_check_cmd")
|
161
168
|
"""health check script command (e.g., sh -x 'curl host:port/health', return 0 is health)"""
|
162
|
-
self.permission =
|
169
|
+
self.permission = _get_int(data, "permission")
|
163
170
|
"""application user permission, value is 2 bit integer: [group & other], each bit can be deny:1, read:2, write: 3."""
|
164
|
-
self.behavior = App.Behavior(
|
171
|
+
self.behavior = App.Behavior(_get_item(data, "behavior"))
|
165
172
|
|
166
173
|
self.env = data.get("env", {}) if data else {}
|
167
174
|
"""environment variables (e.g., -e env1=value1 -e env2=value2, APP_DOCKER_OPTS is used to input docker run parameters)"""
|
168
175
|
self.sec_env = data.get("sec_env", {}) if data else {}
|
169
176
|
"""security environment variables, encrypt in server side with application owner's cipher"""
|
170
|
-
self.pid =
|
177
|
+
self.pid = _get_int(data, "pid")
|
171
178
|
"""process id used to attach to the running process"""
|
172
|
-
self.resource_limit = App.ResourceLimitation(
|
179
|
+
self.resource_limit = App.ResourceLimitation(_get_item(data, "resource_limit"))
|
173
180
|
|
174
181
|
# Read-only attributes
|
175
|
-
self.owner =
|
182
|
+
self.owner = _get_str(data, "owner")
|
176
183
|
"""owner name"""
|
177
|
-
self.user =
|
184
|
+
self.user = _get_str(data, "pid_user")
|
178
185
|
"""process user name"""
|
179
|
-
self.pstree =
|
186
|
+
self.pstree = _get_str(data, "pstree")
|
180
187
|
"""process tree"""
|
181
|
-
self.container_id =
|
188
|
+
self.container_id = _get_str(data, "container_id")
|
182
189
|
"""container id"""
|
183
|
-
self.memory =
|
190
|
+
self.memory = _get_int(data, "memory")
|
184
191
|
"""memory usage"""
|
185
|
-
self.cpu =
|
192
|
+
self.cpu = _get_int(data, "cpu")
|
186
193
|
"""cpu usage"""
|
187
|
-
self.fd =
|
194
|
+
self.fd = _get_int(data, "fd")
|
188
195
|
"""file descriptor usage"""
|
189
|
-
self.last_start_time =
|
196
|
+
self.last_start_time = _get_int(data, "last_start_time")
|
190
197
|
"""last start time"""
|
191
|
-
self.last_exit_time =
|
198
|
+
self.last_exit_time = _get_int(data, "last_exit_time")
|
192
199
|
"""last exit time"""
|
193
|
-
self.health =
|
200
|
+
self.health = _get_int(data, "health")
|
194
201
|
"""health status"""
|
195
|
-
self.version =
|
202
|
+
self.version = _get_int(data, "version")
|
196
203
|
"""version number"""
|
197
|
-
self.return_code =
|
204
|
+
self.return_code = _get_int(data, "return_code")
|
198
205
|
"""last exit code"""
|
206
|
+
self.task_id = _get_int(data, "task_id")
|
207
|
+
"""current task id"""
|
208
|
+
self.task_status = _get_str(data, "task_status")
|
209
|
+
"""task status"""
|
199
210
|
|
200
|
-
def set_valid_time(self, start: datetime, end: datetime) -> None:
|
211
|
+
def set_valid_time(self, start: Optional[datetime], end: Optional[datetime]) -> None:
|
201
212
|
"""Define the valid time window for the application."""
|
202
213
|
self.start_time = int(start.timestamp()) if start else None
|
203
214
|
self.end_time = int(end.timestamp()) if end else None
|
204
215
|
|
205
216
|
def set_env(self, key: str, value: str, secure: bool = False) -> None:
|
206
217
|
"""Set an environment variable, marking it secure if specified."""
|
207
|
-
|
218
|
+
target = self.sec_env if secure else self.env
|
219
|
+
target[key] = value
|
208
220
|
|
209
221
|
def set_permission(self, group_user: Permission, others_user: Permission) -> None:
|
210
222
|
"""Define application permissions based on user roles."""
|
@@ -214,23 +226,27 @@ class App:
|
|
214
226
|
"""Return a JSON string representation of the application."""
|
215
227
|
return json.dumps(self.json())
|
216
228
|
|
217
|
-
def json(self) ->
|
229
|
+
def json(self) -> Dict[str, Any]:
|
218
230
|
"""Convert the application data into a JSON-compatible dictionary, removing empty items."""
|
219
231
|
output = copy.deepcopy(self.__dict__)
|
220
232
|
output["behavior"] = self.behavior.__dict__
|
221
233
|
output["daily_limitation"] = self.daily_limitation.__dict__
|
222
234
|
output["resource_limit"] = self.resource_limit.__dict__
|
223
235
|
|
224
|
-
|
225
|
-
keys_to_delete = []
|
226
|
-
for key, value in data.items():
|
227
|
-
if isinstance(value, dict) and key != "metadata":
|
228
|
-
clean_empty(value) # Recursive call (without checking user metadata)
|
229
|
-
if data[key] in [None, "", {}]:
|
230
|
-
keys_to_delete.append(key) # Mark keys for deletion
|
231
|
-
|
232
|
-
for key in keys_to_delete: # Delete keys after the loop to avoid modifying dict during iteration
|
233
|
-
del data[key]
|
234
|
-
|
235
|
-
clean_empty(output)
|
236
|
+
self._clean_empty(output)
|
236
237
|
return output
|
238
|
+
|
239
|
+
@staticmethod
|
240
|
+
def _clean_empty(data: dict) -> None:
|
241
|
+
"""Recursively remove None, empty string, and empty dict values from nested dictionaries (except 'metadata')."""
|
242
|
+
keys_to_delete = []
|
243
|
+
for key, value in data.items():
|
244
|
+
if isinstance(value, dict) and key != "metadata":
|
245
|
+
App._clean_empty(value)
|
246
|
+
if not value:
|
247
|
+
keys_to_delete.append(key)
|
248
|
+
elif value in (None, "", {}):
|
249
|
+
keys_to_delete.append(key)
|
250
|
+
|
251
|
+
for key in keys_to_delete:
|
252
|
+
del data[key]
|
appmesh/app_output.py
CHANGED
@@ -1,27 +1,28 @@
|
|
1
1
|
# app_output.py
|
2
|
-
|
3
|
-
"""Application output information"""
|
2
|
+
"""Application output information."""
|
4
3
|
|
5
|
-
|
4
|
+
from dataclasses import dataclass
|
6
5
|
from http import HTTPStatus
|
7
6
|
from typing import Optional
|
8
7
|
|
9
8
|
|
10
|
-
|
9
|
+
@dataclass(frozen=True)
|
10
|
+
class AppOutput:
|
11
11
|
"""
|
12
|
-
|
13
|
-
|
12
|
+
Output information returned by the `app_output()` API.
|
13
|
+
|
14
|
+
Includes the application's stdout, current read position,
|
15
|
+
HTTP status code, and process exit code.
|
14
16
|
"""
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
"""HTTP status code from the `app_output()` API request, indicating the http status."""
|
18
|
+
status_code: HTTPStatus
|
19
|
+
"""HTTP status code from the `app_output()` API request."""
|
19
20
|
|
20
|
-
|
21
|
-
|
21
|
+
output: str
|
22
|
+
"""Captured stdout content of the application."""
|
22
23
|
|
23
|
-
|
24
|
-
|
24
|
+
out_position: Optional[int]
|
25
|
+
"""Current read position in stdout stream, or None if not applicable."""
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
exit_code: Optional[int]
|
28
|
+
"""Exit code of the application, or None if still running."""
|
appmesh/app_run.py
CHANGED
@@ -2,18 +2,19 @@
|
|
2
2
|
"""Application run object for remote application execution."""
|
3
3
|
|
4
4
|
from contextlib import contextmanager
|
5
|
-
from typing import Optional
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
6
6
|
|
7
|
-
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from .client_http import AppMeshClient
|
8
9
|
|
9
10
|
|
10
11
|
class AppRun:
|
11
12
|
"""
|
12
|
-
|
13
|
-
|
13
|
+
Application run object for monitoring and retrieving results
|
14
|
+
of a remote application run initiated by `run_async()`.
|
14
15
|
"""
|
15
16
|
|
16
|
-
def __init__(self, client, app_name: str, process_id: str):
|
17
|
+
def __init__(self, client: "AppMeshClient", app_name: str, process_id: str):
|
17
18
|
self.app_name = app_name
|
18
19
|
"""Name of the application associated with this run."""
|
19
20
|
|
@@ -21,20 +22,15 @@ class AppRun:
|
|
21
22
|
"""Unique process ID from `run_async()`."""
|
22
23
|
|
23
24
|
self._client = client
|
24
|
-
"""Instance of `AppMeshClient` used to manage this application run."""
|
25
|
-
|
26
25
|
self._forward_to = client.forward_to
|
27
|
-
"""Target server for the application run, used for forwarding."""
|
28
26
|
|
29
27
|
@contextmanager
|
30
28
|
def forward_to(self):
|
31
|
-
"""
|
32
|
-
|
33
|
-
This ensures that operations during this run use the correct target server,
|
34
|
-
then restores the original setting when done.
|
29
|
+
"""
|
30
|
+
Context manager to temporarily override the client's `forward_to` setting.
|
35
31
|
|
36
|
-
|
37
|
-
|
32
|
+
Ensures operations during this run use the correct target server,
|
33
|
+
then restores the original setting.
|
38
34
|
"""
|
39
35
|
original_value = self._client.forward_to
|
40
36
|
self._client.forward_to = self._forward_to
|
@@ -44,15 +40,15 @@ class AppRun:
|
|
44
40
|
self._client.forward_to = original_value
|
45
41
|
|
46
42
|
def wait(self, stdout_print: bool = True, timeout: int = 0) -> Optional[int]:
|
47
|
-
"""
|
43
|
+
"""
|
44
|
+
Wait for the asynchronous run to complete.
|
48
45
|
|
49
46
|
Args:
|
50
|
-
stdout_print: If
|
51
|
-
timeout: Maximum time to wait in seconds.
|
52
|
-
Defaults to `0`.
|
47
|
+
stdout_print: If True, prints remote stdout to local console.
|
48
|
+
timeout: Maximum time to wait in seconds. 0 means wait indefinitely.
|
53
49
|
|
54
50
|
Returns:
|
55
|
-
Exit code if the process finishes successfully, or
|
51
|
+
Exit code if the process finishes successfully, or None on timeout.
|
56
52
|
"""
|
57
53
|
with self.forward_to():
|
58
54
|
return self._client.wait_for_async_run(self, stdout_print, timeout)
|
appmesh/tcp_messages.py
CHANGED
@@ -1,72 +1,69 @@
|
|
1
|
-
|
1
|
+
"""TCP message classes for HTTP-like communication."""
|
2
2
|
|
3
3
|
# Standard library imports
|
4
|
-
from
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from typing import Any, Dict, Type, get_type_hints
|
5
6
|
|
6
7
|
# Third-party imports
|
7
8
|
import msgpack
|
8
9
|
|
9
10
|
|
11
|
+
@dataclass
|
10
12
|
class RequestMessage:
|
11
|
-
"""TCP request message for HTTP-like communication"""
|
13
|
+
"""TCP request message for HTTP-like communication."""
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
self.query: dict = {}
|
15
|
+
uuid: str = ""
|
16
|
+
request_uri: str = ""
|
17
|
+
http_method: str = ""
|
18
|
+
client_addr: str = ""
|
19
|
+
body: bytes = b""
|
20
|
+
headers: Dict[str, str] = field(default_factory=dict)
|
21
|
+
query: Dict[str, str] = field(default_factory=dict)
|
21
22
|
|
22
23
|
def serialize(self) -> bytes:
|
23
|
-
"""Serialize request message to bytes"""
|
24
|
-
return msgpack.
|
24
|
+
"""Serialize request message to bytes."""
|
25
|
+
return msgpack.packb(self.__dict__, use_bin_type=True)
|
25
26
|
|
26
27
|
|
28
|
+
@dataclass
|
27
29
|
class ResponseMessage:
|
28
|
-
"""TCP response message for HTTP-like communication"""
|
29
|
-
|
30
|
-
uuid: str
|
31
|
-
request_uri: str
|
32
|
-
http_status: int
|
33
|
-
body_msg_type: str
|
34
|
-
body: bytes
|
35
|
-
headers: dict
|
36
|
-
|
37
|
-
def
|
38
|
-
self.uuid = ""
|
39
|
-
self.request_uri = ""
|
40
|
-
self.http_status = 0
|
41
|
-
self.body_msg_type = ""
|
42
|
-
self.body = b""
|
43
|
-
self.headers = {}
|
44
|
-
|
45
|
-
def deserialize(self, buf: bytes):
|
30
|
+
"""TCP response message for HTTP-like communication."""
|
31
|
+
|
32
|
+
uuid: str = ""
|
33
|
+
request_uri: str = ""
|
34
|
+
http_status: int = 0
|
35
|
+
body_msg_type: str = ""
|
36
|
+
body: bytes = b""
|
37
|
+
headers: Dict[str, str] = field(default_factory=dict)
|
38
|
+
|
39
|
+
def deserialize(self, buf: bytes) -> "ResponseMessage":
|
46
40
|
"""Deserialize TCP msgpack buffer with proper type conversion."""
|
47
|
-
|
41
|
+
data = msgpack.unpackb(buf, raw=False)
|
48
42
|
hints = get_type_hints(self.__class__)
|
49
43
|
|
50
|
-
for
|
51
|
-
if
|
52
|
-
|
53
|
-
|
54
|
-
# handle all types (int, bytes, dict, str)
|
55
|
-
t = hints[k]
|
56
|
-
if t is str:
|
57
|
-
if isinstance(v, bytes):
|
58
|
-
v = v.decode("utf-8", errors="replace")
|
59
|
-
elif v is None:
|
60
|
-
v = ""
|
61
|
-
else:
|
62
|
-
v = str(v)
|
63
|
-
elif t is bytes:
|
64
|
-
if isinstance(v, str):
|
65
|
-
v = v.encode("utf-8") # handle accidental str
|
66
|
-
elif v is None:
|
67
|
-
v = b""
|
68
|
-
elif t is int:
|
69
|
-
v = int(v or 0)
|
70
|
-
setattr(self, k, v)
|
44
|
+
for key, value in data.items():
|
45
|
+
if key in hints:
|
46
|
+
setattr(self, key, self._convert_type(value, hints[key]))
|
71
47
|
|
72
48
|
return self
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
def _convert_type(value: Any, expected_type: Type) -> Any:
|
52
|
+
"""Convert value to expected type."""
|
53
|
+
if value is None:
|
54
|
+
return {
|
55
|
+
str: "",
|
56
|
+
bytes: b"",
|
57
|
+
int: 0,
|
58
|
+
}.get(expected_type, None)
|
59
|
+
|
60
|
+
if expected_type is str:
|
61
|
+
return value.decode("utf-8", errors="replace") if isinstance(value, bytes) else str(value)
|
62
|
+
|
63
|
+
if expected_type is bytes:
|
64
|
+
return value.encode("utf-8") if isinstance(value, str) else value
|
65
|
+
|
66
|
+
if expected_type is int:
|
67
|
+
return int(value or 0)
|
68
|
+
|
69
|
+
return value
|
appmesh/tcp_transport.py
CHANGED
@@ -1,71 +1,100 @@
|
|
1
1
|
# tcp_transport.py
|
2
|
+
"""TCP Transport layer handling socket connections."""
|
2
3
|
|
3
|
-
# Standard library imports
|
4
|
-
import os
|
5
4
|
import socket
|
6
5
|
import ssl
|
7
6
|
import struct
|
8
|
-
from
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional, Union, Tuple
|
9
9
|
|
10
10
|
|
11
11
|
class TCPTransport:
|
12
|
-
"""TCP Transport layer
|
12
|
+
"""TCP Transport layer with TLS support."""
|
13
13
|
|
14
14
|
# Number of bytes used for the message length header
|
15
15
|
# Must match the C++ service implementation which uses uint32_t (4 bytes)
|
16
16
|
# Format: Big-endian unsigned 32-bit integer
|
17
17
|
TCP_MESSAGE_HEADER_LENGTH = 8
|
18
18
|
TCP_MESSAGE_MAGIC = 0x07C707F8 # Magic number
|
19
|
-
TCP_MAX_BLOCK_SIZE =
|
20
|
-
|
21
|
-
def __init__(
|
22
|
-
|
19
|
+
TCP_MAX_BLOCK_SIZE = 100 * 1024 * 1024 # 100 MB
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
address: Tuple[str, int],
|
24
|
+
ssl_verify: Union[bool, str],
|
25
|
+
ssl_client_cert: Optional[Union[str, Tuple[str, str]]] = None,
|
26
|
+
):
|
27
|
+
"""
|
28
|
+
Initialize TCP transport with TLS configuration.
|
23
29
|
|
24
30
|
Args:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
tcp_address (Tuple[str, int], optional): Address and port for establishing a TCP connection to the server.
|
40
|
-
Defaults to `("127.0.0.1", 6059)`.
|
31
|
+
address: Server address as (host, port) tuple.
|
32
|
+
|
33
|
+
ssl_verify: SSL server verification mode:
|
34
|
+
- True: Use system CA certificates
|
35
|
+
- False: Disable verification (insecure)
|
36
|
+
- str: Path to custom CA bundle or directory
|
37
|
+
|
38
|
+
ssl_client_cert: SSL client certificate:
|
39
|
+
- str: Path to PEM file with cert and key
|
40
|
+
- tuple: (cert_path, key_path)
|
41
|
+
|
42
|
+
Note:
|
43
|
+
TCP connections require an explicit full-chain CA specification for certificate validation,
|
44
|
+
unlike HTTP, which can retrieve intermediate certificates automatically.
|
41
45
|
"""
|
42
|
-
self.
|
46
|
+
self.address = address
|
43
47
|
self.ssl_verify = ssl_verify
|
44
48
|
self.ssl_client_cert = ssl_client_cert
|
45
|
-
self._socket = None
|
49
|
+
self._socket: Optional[ssl.SSLSocket] = None
|
46
50
|
|
47
51
|
def __enter__(self):
|
48
|
-
"""Context manager entry"""
|
52
|
+
"""Context manager entry."""
|
49
53
|
if not self.connected():
|
50
54
|
self.connect()
|
51
55
|
return self
|
52
56
|
|
53
57
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
54
|
-
"""Context manager exit"""
|
58
|
+
"""Context manager exit."""
|
55
59
|
self.close()
|
56
60
|
|
57
|
-
def __del__(self)
|
58
|
-
|
59
|
-
|
61
|
+
def __del__(self):
|
62
|
+
try:
|
63
|
+
self.close()
|
64
|
+
except Exception:
|
65
|
+
pass # suppress all exceptions
|
60
66
|
|
61
67
|
def connect(self) -> None:
|
62
|
-
"""Establish
|
68
|
+
"""Establish TLS connection to server."""
|
69
|
+
context = self._create_ssl_context()
|
70
|
+
# Create a TCP socket
|
71
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
72
|
+
sock.setblocking(True)
|
73
|
+
# sock.settimeout(30) # Connection timeout set to 30 seconds
|
74
|
+
|
75
|
+
try:
|
76
|
+
# Wrap the socket with SSL/TLS
|
77
|
+
ssl_socket = context.wrap_socket(sock, server_hostname=self.address[0])
|
78
|
+
ssl_socket.connect(self.address)
|
79
|
+
# Disable Nagle's algorithm
|
80
|
+
ssl_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
81
|
+
# After connecting, set separate timeout for recv/send
|
82
|
+
# ssl_socket.settimeout(20) # 20 seconds for recv/send
|
83
|
+
self._socket = ssl_socket
|
84
|
+
except (socket.error, ssl.SSLError) as e:
|
85
|
+
sock.close()
|
86
|
+
raise RuntimeError(f"Failed to connect to {self.address}: {e}") from e
|
87
|
+
|
88
|
+
def _create_ssl_context(self) -> ssl.SSLContext:
|
89
|
+
"""Create and configure SSL context."""
|
63
90
|
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
91
|
+
|
64
92
|
# Set minimum TLS version
|
65
93
|
if hasattr(context, "minimum_version"):
|
66
94
|
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
67
95
|
else:
|
68
96
|
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
97
|
+
|
69
98
|
# Configure SSL verification
|
70
99
|
if not self.ssl_verify:
|
71
100
|
context.check_hostname = False
|
@@ -73,46 +102,30 @@ class TCPTransport:
|
|
73
102
|
else:
|
74
103
|
context.verify_mode = ssl.CERT_REQUIRED # Require certificate verification
|
75
104
|
context.load_default_certs() # Load system's default CA certificates
|
105
|
+
|
76
106
|
if isinstance(self.ssl_verify, str):
|
77
|
-
|
107
|
+
path = Path(self.ssl_verify)
|
108
|
+
if path.is_file():
|
78
109
|
# Load custom CA certificate file
|
79
|
-
context.load_verify_locations(cafile=
|
80
|
-
elif
|
110
|
+
context.load_verify_locations(cafile=str(path))
|
111
|
+
elif path.is_dir():
|
81
112
|
# Load CA certificates from directory
|
82
|
-
context.load_verify_locations(capath=
|
113
|
+
context.load_verify_locations(capath=str(path))
|
83
114
|
else:
|
84
|
-
raise ValueError(f"ssl_verify path '{self.ssl_verify}' is
|
115
|
+
raise ValueError(f"ssl_verify path '{self.ssl_verify}' is invalid")
|
85
116
|
|
86
|
-
|
87
|
-
|
88
|
-
if isinstance(self.ssl_client_cert, tuple)
|
117
|
+
# Load client certificate if provided
|
118
|
+
if self.ssl_client_cert:
|
119
|
+
if isinstance(self.ssl_client_cert, tuple):
|
120
|
+
# Separate cert and key files
|
89
121
|
context.load_cert_chain(certfile=self.ssl_client_cert[0], keyfile=self.ssl_client_cert[1])
|
90
|
-
elif isinstance(self.ssl_client_cert, str):
|
91
|
-
# Handle case where cert and key are in the same file
|
92
|
-
context.load_cert_chain(certfile=self.ssl_client_cert)
|
93
122
|
else:
|
94
|
-
|
123
|
+
# Cert and key in the same PEM file
|
124
|
+
context.load_cert_chain(certfile=self.ssl_client_cert)
|
95
125
|
|
96
|
-
|
97
|
-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
98
|
-
sock.setblocking(True)
|
99
|
-
# sock.settimeout(30) # Connection timeout set to 30 seconds
|
100
|
-
try:
|
101
|
-
# Wrap the socket with SSL/TLS
|
102
|
-
ssl_socket = context.wrap_socket(sock, server_hostname=self.tcp_address[0])
|
103
|
-
# Connect to the server
|
104
|
-
ssl_socket.connect(self.tcp_address)
|
105
|
-
# Disable Nagle's algorithm
|
106
|
-
ssl_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
107
|
-
# After connecting, set separate timeout for recv/send
|
108
|
-
# ssl_socket.settimeout(20) # 20 seconds for recv/send
|
109
|
-
self._socket = ssl_socket
|
110
|
-
except (socket.error, ssl.SSLError) as e:
|
111
|
-
sock.close()
|
112
|
-
raise RuntimeError(f"Failed to connect to {self.tcp_address}: {e}") from e
|
126
|
+
return context
|
113
127
|
|
114
128
|
def close(self) -> None:
|
115
|
-
"""Close socket connection"""
|
116
129
|
if self._socket:
|
117
130
|
try:
|
118
131
|
self._socket.close()
|
@@ -122,18 +135,24 @@ class TCPTransport:
|
|
122
135
|
self._socket = None
|
123
136
|
|
124
137
|
def connected(self) -> bool:
|
125
|
-
"""Check whether socket is connected"""
|
126
138
|
return self._socket is not None
|
127
139
|
|
128
|
-
def send_message(self, data) -> None:
|
129
|
-
"""
|
130
|
-
|
140
|
+
def send_message(self, data: Union[bytes, bytearray, list]) -> None:
|
141
|
+
"""
|
142
|
+
Send a message with prefixed header.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
data: Message data to send, or empty list for EOF signal.
|
146
|
+
"""
|
147
|
+
if not self._socket:
|
131
148
|
raise RuntimeError("Cannot send message: not connected")
|
132
149
|
|
133
150
|
try:
|
134
|
-
length = len(data)
|
151
|
+
length = len(data) if data else 0
|
135
152
|
# Pack the header into 8 bytes using big-endian format
|
136
|
-
|
153
|
+
header = struct.pack("!II", self.TCP_MESSAGE_MAGIC, length)
|
154
|
+
self._socket.sendall(header)
|
155
|
+
|
137
156
|
if length > 0:
|
138
157
|
self._socket.sendall(data)
|
139
158
|
except (socket.error, ssl.SSLError) as e:
|
@@ -141,62 +160,69 @@ class TCPTransport:
|
|
141
160
|
raise RuntimeError(f"Error sending message: {e}") from e
|
142
161
|
|
143
162
|
def receive_message(self) -> Optional[bytearray]:
|
144
|
-
"""
|
145
|
-
|
163
|
+
"""
|
164
|
+
Receive a message with prefixed header.
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
Message data, or None for EOF signal.
|
168
|
+
"""
|
169
|
+
if not self._socket:
|
146
170
|
raise RuntimeError("Cannot receive message: not connected")
|
147
171
|
|
148
172
|
try:
|
149
173
|
# Unpack the data (big-endian format)
|
150
174
|
magic, length = struct.unpack("!II", self._recvall(self.TCP_MESSAGE_HEADER_LENGTH))
|
175
|
+
|
151
176
|
if magic != self.TCP_MESSAGE_MAGIC:
|
152
|
-
raise ValueError(f"Invalid
|
177
|
+
raise ValueError(f"Invalid magic number: 0x{magic:X}")
|
178
|
+
|
153
179
|
if length > self.TCP_MAX_BLOCK_SIZE:
|
154
|
-
raise ValueError(f"Message size {length} exceeds
|
155
|
-
|
156
|
-
|
157
|
-
|
180
|
+
raise ValueError(f"Message size {length} exceeds maximum {self.TCP_MAX_BLOCK_SIZE}")
|
181
|
+
|
182
|
+
return self._recvall(length) if length > 0 else None
|
183
|
+
|
158
184
|
except (socket.error, ssl.SSLError) as e:
|
159
185
|
self.close()
|
160
186
|
raise RuntimeError(f"Error receiving message: {e}") from e
|
161
187
|
|
162
188
|
def _recvall(self, length: int) -> bytes:
|
163
|
-
"""
|
164
|
-
|
189
|
+
"""
|
190
|
+
Receive exactly `length` bytes from socket.
|
191
|
+
https://stackoverflow.com/questions/64466530/using-a-custom-socket-recvall-function-works-only-if-thread-is-put-to-sleep
|
165
192
|
|
166
193
|
Args:
|
167
|
-
length
|
194
|
+
length: Number of bytes to receive.
|
168
195
|
|
169
196
|
Returns:
|
170
|
-
|
197
|
+
Received data.
|
171
198
|
|
172
199
|
Raises:
|
173
200
|
EOFError: If connection closes before receiving all data.
|
174
201
|
ValueError: If length is not positive.
|
175
|
-
socket.timeout: If socket operation times out.
|
176
202
|
"""
|
177
203
|
if length <= 0:
|
178
204
|
raise ValueError(f"Invalid length: {length}")
|
179
205
|
|
180
206
|
# Pre-allocate buffer
|
181
207
|
buffer = bytearray(length)
|
182
|
-
|
208
|
+
view = memoryview(buffer)
|
183
209
|
bytes_received = 0
|
184
210
|
|
185
211
|
while bytes_received < length:
|
186
212
|
try:
|
187
213
|
# Receive directly into buffer
|
188
214
|
remaining = length - bytes_received
|
189
|
-
chunk_size = self._socket.recv_into(
|
215
|
+
chunk_size = self._socket.recv_into(view, remaining)
|
190
216
|
|
191
217
|
if chunk_size == 0:
|
192
218
|
raise EOFError("Connection closed by peer")
|
193
219
|
|
194
|
-
|
220
|
+
view = view[chunk_size:] # advance memoryview
|
195
221
|
bytes_received += chunk_size
|
196
222
|
|
197
223
|
except InterruptedError:
|
198
224
|
continue
|
199
225
|
except socket.timeout as e:
|
200
|
-
raise socket.timeout(f"Socket
|
226
|
+
raise socket.timeout(f"Socket timed out after receiving {bytes_received}/{length} bytes") from e
|
201
227
|
|
202
|
-
return bytes(buffer)
|
228
|
+
return bytes(buffer)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: appmesh
|
3
|
-
Version: 1.6.
|
3
|
+
Version: 1.6.13
|
4
4
|
Summary: Client SDK for App Mesh
|
5
5
|
Home-page: https://github.com/laoshanxi/app-mesh
|
6
6
|
Author: laoshanxi
|
@@ -8,7 +8,7 @@ Author-email: 178029200@qq.com
|
|
8
8
|
License: MIT
|
9
9
|
Keywords: appmesh AppMesh app-mesh
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier:
|
11
|
+
Classifier: Programming Language :: Python :: 3.6
|
12
12
|
Classifier: Operating System :: OS Independent
|
13
13
|
Requires-Python: >=3
|
14
14
|
Description-Content-Type: text/markdown
|
@@ -17,6 +17,7 @@ Requires-Dist: msgpack
|
|
17
17
|
Requires-Dist: requests_toolbelt
|
18
18
|
Requires-Dist: aniso8601
|
19
19
|
Requires-Dist: PyJWT
|
20
|
+
Requires-Dist: dataclasses; python_version < "3.7"
|
20
21
|
Dynamic: author
|
21
22
|
Dynamic: author-email
|
22
23
|
Dynamic: classifier
|
@@ -0,0 +1,16 @@
|
|
1
|
+
appmesh/__init__.py,sha256=on6GmTTwo6mrRbw_XUuf8sZdl5zBYsGT4CSf8l__NnI,2488
|
2
|
+
appmesh/app.py,sha256=5kzQCvGH3LRRUTHsI2_MKEPD3M2qs8kvTxLhbNUqit4,10601
|
3
|
+
appmesh/app_output.py,sha256=s6eqevFxETTVXSPTJX6JyGNpHHILv4ZyM7YWvlkuqQs,741
|
4
|
+
appmesh/app_run.py,sha256=m3ihaacx84o1rl2Oc3EbnppW8D-PTxdehftbWRe8rPk,1732
|
5
|
+
appmesh/appmesh_client.py,sha256=ywB2222PtJUffdfdxZcBfdhZs1KYyc7JvzMxwuK2qyI,378
|
6
|
+
appmesh/client_http.py,sha256=FXCldfTZVU_5RSuSknlH1K2UiC6gOeotj-4NDQzdEhY,60752
|
7
|
+
appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
|
8
|
+
appmesh/client_tcp.py,sha256=RX3T3OG4iOAju8ZPOnTjI_y97Y23_kWoNDjmKjtrBeU,11524
|
9
|
+
appmesh/server_http.py,sha256=wfyiIa2zC-uJR2ZNTGMjYWheqAfSRl0aAv5b_GYcwpE,4343
|
10
|
+
appmesh/server_tcp.py,sha256=J65kmN7DJftyW1LlF--S3keQ6VGmqXb778E79I1R0_k,1488
|
11
|
+
appmesh/tcp_messages.py,sha256=E0cKWUta7NjuLoTGk-Z9CvbdZyakwG7hO8st_07E5L4,1991
|
12
|
+
appmesh/tcp_transport.py,sha256=oUpcBXOaJhG5Hs6yqt9UG8_eENu0cEdNZIyC87LqI7Q,8165
|
13
|
+
appmesh-1.6.13.dist-info/METADATA,sha256=BJJB6M94Sz2yytdr9WRxCf56q1-LCzQ-2zQAfP5_1eU,11814
|
14
|
+
appmesh-1.6.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
appmesh-1.6.13.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
16
|
+
appmesh-1.6.13.dist-info/RECORD,,
|
appmesh-1.6.11.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
appmesh/__init__.py,sha256=uJ5LOadwZW2nOXkSuPOy2S3WH9Bx0BUn5wUvPBeFzoc,2217
|
2
|
-
appmesh/app.py,sha256=crD4DRFZJuHtZMfSsz7C-EwvjPmGZbFXYXvA_wCdvdI,10734
|
3
|
-
appmesh/app_output.py,sha256=N8BihWhuUgPUyqZabZMKhQylk8nTHXKk6kMIdidEkGM,1061
|
4
|
-
appmesh/app_run.py,sha256=aYq852a29OThIi32Xtx5s0sTXZ97T0lHD5WXH8yfPoc,2018
|
5
|
-
appmesh/appmesh_client.py,sha256=ywB2222PtJUffdfdxZcBfdhZs1KYyc7JvzMxwuK2qyI,378
|
6
|
-
appmesh/client_http.py,sha256=FXCldfTZVU_5RSuSknlH1K2UiC6gOeotj-4NDQzdEhY,60752
|
7
|
-
appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
|
8
|
-
appmesh/client_tcp.py,sha256=RX3T3OG4iOAju8ZPOnTjI_y97Y23_kWoNDjmKjtrBeU,11524
|
9
|
-
appmesh/server_http.py,sha256=wfyiIa2zC-uJR2ZNTGMjYWheqAfSRl0aAv5b_GYcwpE,4343
|
10
|
-
appmesh/server_tcp.py,sha256=J65kmN7DJftyW1LlF--S3keQ6VGmqXb778E79I1R0_k,1488
|
11
|
-
appmesh/tcp_messages.py,sha256=YB_AiMOSzN9j8oSrF0V9gD184Ugbsff-ZnezExwmvtk,1928
|
12
|
-
appmesh/tcp_transport.py,sha256=FCfTBHb9FLwMUIxZejOuH_NocHQFGaNDge57IUTJvgs,8973
|
13
|
-
appmesh-1.6.11.dist-info/METADATA,sha256=gHUSXnQdxvG61jPEUWcJBSJlrmR_PIHZ_qyyaYwTuIc,11764
|
14
|
-
appmesh-1.6.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
-
appmesh-1.6.11.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
16
|
-
appmesh-1.6.11.dist-info/RECORD,,
|
File without changes
|
File without changes
|