appmesh 1.6.10__py3-none-any.whl → 1.6.12__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 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__ = ["App", "AppMeshClient", "AppMeshClientTCP", "AppMeshClientOAuth", "AppMeshServer", "AppMeshServerTCP"]
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
- # Provide explicit imports for static analyzers and type checkers
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
- """Helper function for lazy importing."""
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 = __import__(f"{__name__}.{module_name}", fromlist=[attr_name])
44
- return getattr(module, attr_name)
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
- if sys.version_info >= (3, 7):
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
- @staticmethod
20
- def _get_str_item(data: dict, key: str) -> Optional[str]:
21
- """Retrieve a string value from a dictionary by key, if it exists and is a valid string."""
22
- return data[key] if (data and key in data and data[key] and isinstance(data[key], str)) else None
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
- @staticmethod
25
- def _get_int_item(data: dict, key: str) -> Optional[int]:
26
- """Retrieve an integer value from a dictionary by key, if it exists and is a valid integer."""
27
- if data and key in data and data[key] is not None:
28
- if isinstance(data[key], int):
29
- return data[key]
30
- elif isinstance(data[key], str) and data[key].isdigit():
31
- return int(data[key])
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
- @staticmethod
35
- def _get_bool_item(data: dict, key: str) -> Optional[bool]:
36
- """Retrieve a boolean value from a dictionary by key, if it exists and is boolean-like."""
37
- if data and key in data and data[key] is not None:
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
- @staticmethod
42
- def _get_native_item(data: dict, key: str) -> Optional[Any]:
43
- """Retrieve a deep copy of a value from a dictionary by key, if it exists."""
44
- return copy.deepcopy(data[key]) if (data and key in data and data[key] is not None) else None
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
- """Defines application permission levels."""
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
- Manages application error handling behavior, including exit and control behaviors.
62
+ Application error handling behavior, including exit and control behaviors.
57
63
  """
58
64
 
59
65
  @unique
60
66
  class Action(Enum):
61
- """Defines actions for application exit behaviors."""
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 = App._get_str_item(data, "exit")
78
+ self.exit = _get_str(data, "exit")
73
79
  """Default exit behavior, options: 'restart', 'standby', 'keepalive', 'remove'."""
74
80
 
75
- self.control = App._get_native_item(data, "control") or {}
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
- Defines application availability within a daily time range.
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 = App._get_int_item(data, "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 = App._get_int_item(data, "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
- Defines application resource limits, such as CPU and memory usage.
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 = App._get_int_item(data, "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 = App._get_int_item(data, "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 = App._get_int_item(data, "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
- self.name = App._get_str_item(data, "name")
135
+ # Application configuration
136
+ self.name = _get_str(data, "name")
130
137
  """application name (unique)"""
131
- self.command = App._get_str_item(data, "command")
138
+ self.command = _get_str(data, "command")
132
139
  """full command line with arguments"""
133
- self.shell = App._get_bool_item(data, "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 = App._get_bool_item(data, "session_login")
142
+ self.session_login = _get_bool(data, "session_login")
136
143
  """app run in session login mode"""
137
- self.description = App._get_str_item(data, "description")
144
+ self.description = _get_str(data, "description")
138
145
  """application description string"""
139
- self.metadata = App._get_native_item(data, "metadata")
146
+ self.metadata = _get_item(data, "metadata")
140
147
  """metadata string/JSON (input for application, pass to process stdin)"""
141
- self.working_dir = App._get_str_item(data, "working_dir")
148
+ self.working_dir = _get_str(data, "working_dir")
142
149
  """working directory"""
143
- self.status = App._get_int_item(data, "status")
150
+ self.status = _get_int(data, "status")
144
151
  """initial application status (true is enable, false is disabled)"""
145
- self.docker_image = App._get_str_item(data, "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 = App._get_int_item(data, "stdout_cache_num")
154
+ self.stdout_cache_num = _get_int(data, "stdout_cache_num")
148
155
  """stdout file cache number"""
149
- self.start_time = App._get_int_item(data, "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 = App._get_int_item(data, "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 = App._get_int_item(data, "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 = App._get_bool_item(data, "cron")
162
+ self.cron = _get_bool(data, "cron")
156
163
  """indicate interval parameter use cron expression or not"""
157
- self.daily_limitation = App.DailyLimitation(App._get_native_item(data, "daily_limitation"))
158
- self.retention = App._get_str_item(data, "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 = App._get_str_item(data, "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 = App._get_int_item(data, "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(App._get_native_item(data, "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 = App._get_int_item(data, "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(App._get_native_item(data, "resource_limit"))
179
+ self.resource_limit = App.ResourceLimitation(_get_item(data, "resource_limit"))
173
180
 
174
181
  # Read-only attributes
175
- self.owner = App._get_str_item(data, "owner")
182
+ self.owner = _get_str(data, "owner")
176
183
  """owner name"""
177
- self.user = App._get_str_item(data, "pid_user")
184
+ self.user = _get_str(data, "pid_user")
178
185
  """process user name"""
179
- self.pstree = App._get_str_item(data, "pstree")
186
+ self.pstree = _get_str(data, "pstree")
180
187
  """process tree"""
181
- self.container_id = App._get_str_item(data, "container_id")
188
+ self.container_id = _get_str(data, "container_id")
182
189
  """container id"""
183
- self.memory = App._get_int_item(data, "memory")
190
+ self.memory = _get_int(data, "memory")
184
191
  """memory usage"""
185
- self.cpu = App._get_int_item(data, "cpu")
192
+ self.cpu = _get_int(data, "cpu")
186
193
  """cpu usage"""
187
- self.fd = App._get_int_item(data, "fd")
194
+ self.fd = _get_int(data, "fd")
188
195
  """file descriptor usage"""
189
- self.last_start_time = App._get_int_item(data, "last_start_time")
196
+ self.last_start_time = _get_int(data, "last_start_time")
190
197
  """last start time"""
191
- self.last_exit_time = App._get_int_item(data, "last_exit_time")
198
+ self.last_exit_time = _get_int(data, "last_exit_time")
192
199
  """last exit time"""
193
- self.health = App._get_int_item(data, "health")
200
+ self.health = _get_int(data, "health")
194
201
  """health status"""
195
- self.version = App._get_int_item(data, "version")
202
+ self.version = _get_int(data, "version")
196
203
  """version number"""
197
- self.return_code = App._get_int_item(data, "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
- (self.sec_env if secure else self.env)[key] = value
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) -> dict:
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
- def clean_empty(data: dict) -> None:
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
- """Application output information"""
2
+ """Application output information."""
3
3
 
4
+ from dataclasses import dataclass
4
5
  from http import HTTPStatus
5
6
  from typing import Optional
6
7
 
7
- # pylint: disable=line-too-long
8
8
 
9
-
10
- class AppOutput(object):
9
+ @dataclass(frozen=True)
10
+ class AppOutput:
11
11
  """
12
- Represents the output information returned by the `app_output()` API.
13
- including the application's stdout , current read position, http status code, and process exit code.
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
- def __init__(self, status_code: HTTPStatus, output: str, out_position: Optional[int], exit_code: Optional[int]) -> None:
17
- self.status_code = status_code
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
- self.output = output
21
- """Captured stdout content of the application as returned by the `app_output()` API."""
21
+ output: str
22
+ """Captured stdout content of the application."""
22
23
 
23
- self.out_position = out_position
24
- """Current read position in the application's stdout stream, or `None` if not applicable."""
24
+ out_position: Optional[int]
25
+ """Current read position in stdout stream, or None if not applicable."""
25
26
 
26
- self.exit_code = exit_code
27
- """Exit code of the application, or `None` if the process is still running or hasn't exited."""
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
- # pylint: disable=line-too-long
7
+ if TYPE_CHECKING:
8
+ from .client_http import AppMeshClient
8
9
 
9
10
 
10
11
  class AppRun:
11
12
  """
12
- Represents an application run object initiated by `run_async()` for monitoring and retrieving
13
- the result of a remote application run.
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
- """Context manager to temporarily override the client's `forward_to` setting.
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
- Yields:
37
- None: Context for the overridden forward_to setting.
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
- """Wait for the asynchronous run to complete.
43
+ """
44
+ Wait for the asynchronous run to complete.
48
45
 
49
46
  Args:
50
- stdout_print: If `True`, prints remote stdout to local console. Defaults to `True`.
51
- timeout: Maximum time to wait in seconds. If `0`, waits indefinitely until completion.
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 `None` on timeout or exception.
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/client_http.py CHANGED
@@ -1,7 +1,10 @@
1
1
  # client_http.py
2
2
  # pylint: disable=broad-exception-raised,line-too-long,broad-exception-caught,too-many-lines,import-outside-toplevel
3
+
4
+ # Standard library imports
3
5
  import abc
4
6
  import base64
7
+ import http.cookiejar as cookiejar
5
8
  import json
6
9
  import locale
7
10
  import logging
@@ -9,19 +12,21 @@ import os
9
12
  import sys
10
13
  import threading
11
14
  import time
12
- import requests
13
- import http.cookiejar as cookiejar
14
15
  from datetime import datetime
15
16
  from enum import Enum, unique
16
17
  from http import HTTPStatus
17
- import threading
18
18
  from typing import Optional, Tuple, Union
19
19
  from urllib import parse
20
+
21
+ # Third-party imports
20
22
  import aniso8601
21
23
  import jwt
24
+ import requests
25
+
26
+ # Local imports
22
27
  from .app import App
23
- from .app_run import AppRun
24
28
  from .app_output import AppOutput
29
+ from .app_run import AppRun
25
30
 
26
31
 
27
32
  class AppMeshClient(metaclass=abc.ABCMeta):
@@ -220,7 +225,8 @@ class AppMeshClient(metaclass=abc.ABCMeta):
220
225
  rest_timeout (tuple, optional): HTTP connection timeouts for API requests, as `(connect_timeout, read_timeout)`.
221
226
  The default is `(60, 300)`, where `60` seconds is the maximum time to establish a connection and `300` seconds for the maximum read duration.
222
227
 
223
- rest_cookie_file (str, optional): Path to a file for storing session cookies. If provided, cookies will be saved to and loaded from this file to maintain session state across client instances.
228
+ rest_cookie_file (str, optional): Path to a file for storing session cookies.
229
+ If provided, cookies will be saved to and loaded from this file to maintain session state across client instances instead of keep jwt_token.
224
230
 
225
231
  jwt_token (str, optional): JWT token for API authentication, used in headers to authorize requests where required.
226
232
  auto_refresh_token (bool, optional): Enable automatic token refresh before expiration.
@@ -235,16 +241,16 @@ class AppMeshClient(metaclass=abc.ABCMeta):
235
241
  self.rest_timeout = rest_timeout
236
242
  self._forward_to = None
237
243
 
238
- # Session and cookie management
239
- self._lock = threading.Lock()
240
- self.session = requests.Session()
241
- self.cookie_file = self._load_cookies(rest_cookie_file)
242
-
243
244
  # Token auto-refresh
244
245
  self._token_refresh_timer = None
245
246
  self._auto_refresh_token = auto_refresh_token
246
247
  self.jwt_token = jwt_token # Set property last after all dependencies are initialized to setup refresh timer
247
248
 
249
+ # Session and cookie management
250
+ self._lock = threading.Lock()
251
+ self.session = requests.Session()
252
+ self.cookie_file = self._load_cookies(rest_cookie_file)
253
+
248
254
  @staticmethod
249
255
  def _ensure_logging_configured():
250
256
  """Ensure logging is configured. If no handlers are configured, add a default console handler."""
@@ -262,6 +268,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
262
268
  self.session.cookies = cookiejar.MozillaCookieJar(cookie_file)
263
269
  if os.path.exists(cookie_file):
264
270
  self.session.cookies.load(ignore_discard=True, ignore_expires=True)
271
+ self.jwt_token = self._get_cookie_value(self.session.cookies, self.COOKIE_TOKEN)
265
272
  else:
266
273
  os.makedirs(os.path.dirname(cookie_file), exist_ok=True)
267
274
  self.session.cookies.save(ignore_discard=True, ignore_expires=True)
@@ -453,7 +460,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
453
460
 
454
461
  # handle session
455
462
  with self._lock:
456
- if self.cookie_file:
463
+ if hasattr(self, "cookie_file") and self.cookie_file:
457
464
  self.session.cookies.save(ignore_discard=True, ignore_expires=True)
458
465
 
459
466
  @property
@@ -575,6 +582,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
575
582
  "totp_challenge": challenge,
576
583
  "expire_seconds": self._parse_duration(timeout),
577
584
  },
585
+ header={self.HTTP_HEADER_JWT_set_cookie: "true"} if self.cookie_file else {},
578
586
  )
579
587
  if resp.status_code == HTTPStatus.OK and "access_token" in resp.json():
580
588
  self.jwt_token = resp.json()["access_token"]
appmesh/client_tcp.py CHANGED
@@ -1,15 +1,20 @@
1
1
  # client_tcp.py
2
2
  # pylint: disable=line-too-long,broad-exception-raised,broad-exception-caught,import-outside-toplevel,protected-access
3
3
 
4
+ # Standard library imports
4
5
  import json
5
6
  import os
6
- import sys
7
7
  import socket
8
+ import sys
8
9
  import uuid
10
+
11
+ # Third-party imports
9
12
  import requests
13
+
14
+ # Local imports
10
15
  from .client_http import AppMeshClient
11
- from .tcp_transport import TCPTransport
12
16
  from .tcp_messages import RequestMessage, ResponseMessage
17
+ from .tcp_transport import TCPTransport
13
18
 
14
19
 
15
20
  class AppMeshClientTCP(AppMeshClient):
appmesh/server_http.py CHANGED
@@ -1,12 +1,15 @@
1
1
  # server_http.py
2
2
  # pylint: disable=line-too-long,broad-exception-raised,broad-exception-caught,import-outside-toplevel,protected-access
3
3
 
4
+ # Standard library imports
4
5
  import abc
5
6
  import logging
6
7
  import os
7
8
  import time
8
- from typing import Optional, Tuple, Union
9
9
  from http import HTTPStatus
10
+ from typing import Optional, Tuple, Union
11
+
12
+ # Local imports
10
13
  from .client_http import AppMeshClient
11
14
 
12
15
  logger = logging.getLogger(__name__)
appmesh/server_tcp.py CHANGED
@@ -1,9 +1,12 @@
1
1
  # server_tcp.py
2
2
  # pylint: disable=line-too-long,broad-exception-raised,broad-exception-caught,import-outside-toplevel,protected-access
3
3
 
4
- import os
4
+ # Standard library imports
5
5
  import logging
6
+ import os
6
7
  from typing import Optional, Tuple
8
+
9
+ # Local imports
7
10
  from .client_http import AppMeshClient
8
11
  from .client_tcp import AppMeshClientTCP
9
12
  from .server_http import AppMeshServer
appmesh/tcp_messages.py CHANGED
@@ -1,69 +1,69 @@
1
- # tcp_messages.py
1
+ """TCP message classes for HTTP-like communication."""
2
2
 
3
- from typing import get_type_hints
3
+ # Standard library imports
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Dict, Type, get_type_hints
6
+
7
+ # Third-party imports
4
8
  import msgpack
5
9
 
6
10
 
11
+ @dataclass
7
12
  class RequestMessage:
8
- """TCP request message for HTTP-like communication"""
13
+ """TCP request message for HTTP-like communication."""
9
14
 
10
- def __init__(self):
11
- self.uuid: str = ""
12
- self.request_uri: str = ""
13
- self.http_method: str = ""
14
- self.client_addr: str = ""
15
- self.body: bytes = b""
16
- self.headers: dict = {}
17
- 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)
18
22
 
19
23
  def serialize(self) -> bytes:
20
- """Serialize request message to bytes"""
21
- return msgpack.dumps(vars(self), use_bin_type=True)
24
+ """Serialize request message to bytes."""
25
+ return msgpack.packb(self.__dict__, use_bin_type=True)
22
26
 
23
27
 
28
+ @dataclass
24
29
  class ResponseMessage:
25
- """TCP response message for HTTP-like communication"""
26
-
27
- uuid: str
28
- request_uri: str
29
- http_status: int
30
- body_msg_type: str
31
- body: bytes
32
- headers: dict
33
-
34
- def __init__(self):
35
- self.uuid = ""
36
- self.request_uri = ""
37
- self.http_status = 0
38
- self.body_msg_type = ""
39
- self.body = b""
40
- self.headers = {}
41
-
42
- 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":
43
40
  """Deserialize TCP msgpack buffer with proper type conversion."""
44
- dic = msgpack.unpackb(buf, raw=False)
41
+ data = msgpack.unpackb(buf, raw=False)
45
42
  hints = get_type_hints(self.__class__)
46
43
 
47
- for k, v in dic.items():
48
- if k not in hints:
49
- continue # Skip unknown fields
50
-
51
- # handle all types (int, bytes, dict, str)
52
- t = hints[k]
53
- if t is str:
54
- if isinstance(v, bytes):
55
- v = v.decode("utf-8", errors="replace")
56
- elif v is None:
57
- v = ""
58
- else:
59
- v = str(v)
60
- elif t is bytes:
61
- if isinstance(v, str):
62
- v = v.encode("utf-8") # handle accidental str
63
- elif v is None:
64
- v = b""
65
- elif t is int:
66
- v = int(v or 0)
67
- 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]))
68
47
 
69
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,70 +1,100 @@
1
1
  # tcp_transport.py
2
+ """TCP Transport layer handling socket connections."""
2
3
 
3
- import os
4
4
  import socket
5
5
  import ssl
6
6
  import struct
7
- from typing import Optional, Tuple, Union
7
+ from pathlib import Path
8
+ from typing import Optional, Union, Tuple
8
9
 
9
10
 
10
11
  class TCPTransport:
11
- """TCP Transport layer handling socket connections"""
12
+ """TCP Transport layer with TLS support."""
12
13
 
13
14
  # Number of bytes used for the message length header
14
15
  # Must match the C++ service implementation which uses uint32_t (4 bytes)
15
16
  # Format: Big-endian unsigned 32-bit integer
16
17
  TCP_MESSAGE_HEADER_LENGTH = 8
17
18
  TCP_MESSAGE_MAGIC = 0x07C707F8 # Magic number
18
- TCP_MAX_BLOCK_SIZE = 1024 * 1024 * 100 # 100 MB message size limit
19
-
20
- def __init__(self, address: Tuple[str, int], ssl_verify: Union[bool, str], ssl_client_cert: Union[str, Tuple[str, str]]):
21
- """Construct an TCPTransport object to send and recieve TCP data.
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.
22
29
 
23
30
  Args:
24
- ssl_verify (Union[bool, str], optional): Specifies SSL certificate verification behavior. Can be:
25
- - `True`: Uses the system's default CA certificates to verify the server's identity.
26
- - `False`: Disables SSL certificate verification (insecure, intended for development).
27
- - `str`: Specifies a custom CA bundle or directory for server certificate verification. If a string is provided,
28
- it should either be a file path to a custom CA certificate (CA bundle) or a directory path containing multiple
29
- certificates (CA directory).
30
-
31
- **Note**: Unlike HTTP requests, TCP connections cannot automatically retrieve intermediate or public CA certificates.
32
- When `rest_ssl_verify` is a path, it explicitly identifies a CA issuer to ensure certificate validation.
33
-
34
- ssl_client_cert (Union[str, Tuple[str, str]], optional): Path to the SSL client certificate and key. Can be:
35
- - `str`: A path to a single PEM file containing both the client certificate and private key.
36
- - `tuple`: A pair of paths (`cert_file`, `key_file`), where `cert_file` is the client certificate file path and `key_file` is the private key file path.
37
-
38
- tcp_address (Tuple[str, int], optional): Address and port for establishing a TCP connection to the server.
39
- 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.
40
45
  """
41
- self.tcp_address = address
46
+ self.address = address
42
47
  self.ssl_verify = ssl_verify
43
48
  self.ssl_client_cert = ssl_client_cert
44
- self._socket = None
49
+ self._socket: Optional[ssl.SSLSocket] = None
45
50
 
46
51
  def __enter__(self):
47
- """Context manager entry"""
52
+ """Context manager entry."""
48
53
  if not self.connected():
49
54
  self.connect()
50
55
  return self
51
56
 
52
57
  def __exit__(self, exc_type, exc_val, exc_tb):
53
- """Context manager exit"""
58
+ """Context manager exit."""
54
59
  self.close()
55
60
 
56
- def __del__(self) -> None:
57
- """De-construction"""
58
- self.close()
61
+ def __del__(self):
62
+ try:
63
+ self.close()
64
+ except Exception:
65
+ pass # suppress all exceptions
59
66
 
60
67
  def connect(self) -> None:
61
- """Establish tcp connection"""
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."""
62
90
  context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
91
+
63
92
  # Set minimum TLS version
64
93
  if hasattr(context, "minimum_version"):
65
94
  context.minimum_version = ssl.TLSVersion.TLSv1_2
66
95
  else:
67
96
  context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
97
+
68
98
  # Configure SSL verification
69
99
  if not self.ssl_verify:
70
100
  context.check_hostname = False
@@ -72,46 +102,30 @@ class TCPTransport:
72
102
  else:
73
103
  context.verify_mode = ssl.CERT_REQUIRED # Require certificate verification
74
104
  context.load_default_certs() # Load system's default CA certificates
105
+
75
106
  if isinstance(self.ssl_verify, str):
76
- if os.path.isfile(self.ssl_verify):
107
+ path = Path(self.ssl_verify)
108
+ if path.is_file():
77
109
  # Load custom CA certificate file
78
- context.load_verify_locations(cafile=self.ssl_verify)
79
- elif os.path.isdir(self.ssl_verify):
110
+ context.load_verify_locations(cafile=str(path))
111
+ elif path.is_dir():
80
112
  # Load CA certificates from directory
81
- context.load_verify_locations(capath=self.ssl_verify)
113
+ context.load_verify_locations(capath=str(path))
82
114
  else:
83
- raise ValueError(f"ssl_verify path '{self.ssl_verify}' is neither a file nor a directory")
115
+ raise ValueError(f"ssl_verify path '{self.ssl_verify}' is invalid")
84
116
 
85
- if self.ssl_client_cert is not None:
86
- # Load client-side certificate and private key
87
- if isinstance(self.ssl_client_cert, tuple) and len(self.ssl_client_cert) == 2:
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
88
121
  context.load_cert_chain(certfile=self.ssl_client_cert[0], keyfile=self.ssl_client_cert[1])
89
- elif isinstance(self.ssl_client_cert, str):
90
- # Handle case where cert and key are in the same file
91
- context.load_cert_chain(certfile=self.ssl_client_cert)
92
122
  else:
93
- raise ValueError("ssl_client_cert must be a string filepath or a tuple of (cert_file, key_file)")
123
+ # Cert and key in the same PEM file
124
+ context.load_cert_chain(certfile=self.ssl_client_cert)
94
125
 
95
- # Create a TCP socket
96
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
97
- sock.setblocking(True)
98
- # sock.settimeout(30) # Connection timeout set to 30 seconds
99
- try:
100
- # Wrap the socket with SSL/TLS
101
- ssl_socket = context.wrap_socket(sock, server_hostname=self.tcp_address[0])
102
- # Connect to the server
103
- ssl_socket.connect(self.tcp_address)
104
- # Disable Nagle's algorithm
105
- ssl_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
106
- # After connecting, set separate timeout for recv/send
107
- # ssl_socket.settimeout(20) # 20 seconds for recv/send
108
- self._socket = ssl_socket
109
- except (socket.error, ssl.SSLError) as e:
110
- sock.close()
111
- raise RuntimeError(f"Failed to connect to {self.tcp_address}: {e}") from e
126
+ return context
112
127
 
113
128
  def close(self) -> None:
114
- """Close socket connection"""
115
129
  if self._socket:
116
130
  try:
117
131
  self._socket.close()
@@ -121,18 +135,24 @@ class TCPTransport:
121
135
  self._socket = None
122
136
 
123
137
  def connected(self) -> bool:
124
- """Check whether socket is connected"""
125
138
  return self._socket is not None
126
139
 
127
- def send_message(self, data) -> None:
128
- """Send a message with a prefixed header indicating its length"""
129
- if self._socket is None:
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:
130
148
  raise RuntimeError("Cannot send message: not connected")
131
149
 
132
150
  try:
133
- length = len(data)
151
+ length = len(data) if data else 0
134
152
  # Pack the header into 8 bytes using big-endian format
135
- self._socket.sendall(struct.pack("!II", self.TCP_MESSAGE_MAGIC, length))
153
+ header = struct.pack("!II", self.TCP_MESSAGE_MAGIC, length)
154
+ self._socket.sendall(header)
155
+
136
156
  if length > 0:
137
157
  self._socket.sendall(data)
138
158
  except (socket.error, ssl.SSLError) as e:
@@ -140,62 +160,69 @@ class TCPTransport:
140
160
  raise RuntimeError(f"Error sending message: {e}") from e
141
161
 
142
162
  def receive_message(self) -> Optional[bytearray]:
143
- """Receive a message with a prefixed header indicating its length and validate it"""
144
- if self._socket is None:
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:
145
170
  raise RuntimeError("Cannot receive message: not connected")
146
171
 
147
172
  try:
148
173
  # Unpack the data (big-endian format)
149
174
  magic, length = struct.unpack("!II", self._recvall(self.TCP_MESSAGE_HEADER_LENGTH))
175
+
150
176
  if magic != self.TCP_MESSAGE_MAGIC:
151
- raise ValueError(f"Invalid message: incorrect magic number 0x{magic:X}.")
177
+ raise ValueError(f"Invalid magic number: 0x{magic:X}")
178
+
152
179
  if length > self.TCP_MAX_BLOCK_SIZE:
153
- raise ValueError(f"Message size {length} exceeds the maximum allowed size of {self.TCP_MAX_BLOCK_SIZE} bytes.")
154
- if length > 0:
155
- return self._recvall(length)
156
- return None
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
+
157
184
  except (socket.error, ssl.SSLError) as e:
158
185
  self.close()
159
186
  raise RuntimeError(f"Error receiving message: {e}") from e
160
187
 
161
188
  def _recvall(self, length: int) -> bytes:
162
- """Receive exactly `length` bytes from the socket.
163
- https://stackoverflow.com/questions/64466530/using-a-custom-socket-recvall-function-works-only-if-thread-is-put-to-sleep
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
164
192
 
165
193
  Args:
166
- length (int): Number of bytes to receive.
194
+ length: Number of bytes to receive.
167
195
 
168
196
  Returns:
169
- bytes: Received data.
197
+ Received data.
170
198
 
171
199
  Raises:
172
200
  EOFError: If connection closes before receiving all data.
173
201
  ValueError: If length is not positive.
174
- socket.timeout: If socket operation times out.
175
202
  """
176
203
  if length <= 0:
177
204
  raise ValueError(f"Invalid length: {length}")
178
205
 
179
206
  # Pre-allocate buffer
180
207
  buffer = bytearray(length)
181
- mv = memoryview(buffer)
208
+ view = memoryview(buffer)
182
209
  bytes_received = 0
183
210
 
184
211
  while bytes_received < length:
185
212
  try:
186
213
  # Receive directly into buffer
187
214
  remaining = length - bytes_received
188
- chunk_size = self._socket.recv_into(mv, remaining)
215
+ chunk_size = self._socket.recv_into(view, remaining)
189
216
 
190
217
  if chunk_size == 0:
191
218
  raise EOFError("Connection closed by peer")
192
219
 
193
- mv = mv[chunk_size:] # advance memoryview
220
+ view = view[chunk_size:] # advance memoryview
194
221
  bytes_received += chunk_size
195
222
 
196
223
  except InterruptedError:
197
224
  continue
198
225
  except socket.timeout as e:
199
- raise socket.timeout(f"Socket operation timed out after receiving {bytes_received}/{length} bytes") from e
226
+ raise socket.timeout(f"Socket timed out after receiving {bytes_received}/{length} bytes") from e
200
227
 
201
- return bytes(buffer) # safer than bytearray
228
+ return bytes(buffer)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appmesh
3
- Version: 1.6.10
3
+ Version: 1.6.12
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: License :: OSI Approved :: MIT License
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
@@ -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.12.dist-info/METADATA,sha256=JChlAYlrD7tF3nv1O79104J5633iaEa87qEoZt6KPsE,11763
14
+ appmesh-1.6.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ appmesh-1.6.12.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
16
+ appmesh-1.6.12.dist-info/RECORD,,
@@ -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=vfn322AyixblI8DbXds08h6L_ybObiaRSifsA1-Xcoo,1035
4
- appmesh/app_run.py,sha256=aYq852a29OThIi32Xtx5s0sTXZ97T0lHD5WXH8yfPoc,2018
5
- appmesh/appmesh_client.py,sha256=ywB2222PtJUffdfdxZcBfdhZs1KYyc7JvzMxwuK2qyI,378
6
- appmesh/client_http.py,sha256=KD4AMcMbHqaLJWSrra0J03kMqWAiwYHiyusUc5kpr6o,60443
7
- appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
8
- appmesh/client_tcp.py,sha256=aq6UUzytZA4ibE9WQMMWdo1uW8sHETEhJjsbM6IYSno,11457
9
- appmesh/server_http.py,sha256=rBIYO9rbR-r3x1Jcry440Sp--IM-OWKRaOhNpGdkxh8,4299
10
- appmesh/server_tcp.py,sha256=-CU5tw97WJmDcUNsNPWqpdZ0wxRzRD6kUP3XyNZUTHc,1444
11
- appmesh/tcp_messages.py,sha256=H9S_iCy0IuufY2v50_SUgRvcyQmJsySG65tBe_xb3Ko,1878
12
- appmesh/tcp_transport.py,sha256=0hRSp5fpL9wKB05JIyIRIuyBC8w1IdokryhMDHqtN4M,8946
13
- appmesh-1.6.10.dist-info/METADATA,sha256=do80dYMa4qK9tg1qKDM4M0klzIgzfcEGlWo6w1OIBfg,11764
14
- appmesh-1.6.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- appmesh-1.6.10.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
16
- appmesh-1.6.10.dist-info/RECORD,,