appmesh 1.6.2__py3-none-any.whl → 1.6.4__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/app.py CHANGED
@@ -5,7 +5,7 @@ import json
5
5
  import copy
6
6
 
7
7
  from datetime import datetime
8
- from typing import Optional
8
+ from typing import Optional, Any
9
9
  from enum import Enum, unique
10
10
 
11
11
  # pylint: disable=line-too-long
@@ -39,7 +39,7 @@ class App:
39
39
  return None
40
40
 
41
41
  @staticmethod
42
- def _get_native_item(data: dict, key: str) -> Optional[object]:
42
+ def _get_native_item(data: dict, key: str) -> Optional[Any]:
43
43
  """Retrieve a deep copy of a value from a dictionary by key, if it exists."""
44
44
  return copy.deepcopy(data[key]) if (data and key in data and data[key] is not None) else None
45
45
 
appmesh/app_run.py CHANGED
@@ -2,6 +2,7 @@
2
2
  """Application run object for remote application execution."""
3
3
 
4
4
  from contextlib import contextmanager
5
+ from typing import Optional
5
6
 
6
7
  # pylint: disable=line-too-long
7
8
 
@@ -42,7 +43,7 @@ class AppRun:
42
43
  finally:
43
44
  self._client.forward_to = original_value
44
45
 
45
- def wait(self, stdout_print: bool = True, timeout: int = 0) -> int:
46
+ def wait(self, stdout_print: bool = True, timeout: int = 0) -> Optional[int]:
46
47
  """Wait for the asynchronous run to complete.
47
48
 
48
49
  Args:
appmesh/appmesh_client.py CHANGED
@@ -5,4 +5,6 @@
5
5
  # AppMeshClient, App, and AppOutput classes. The updated implementation can be found
6
6
  # in client_http.py, where these classes are now primarily maintained.
7
7
 
8
- from .client_http import AppMeshClient, App, AppOutput
8
+ from .client_http import AppMeshClient
9
+ from .app import App
10
+ from .app_output import AppOutput
appmesh/client_http.py CHANGED
@@ -3,8 +3,10 @@
3
3
  import abc
4
4
  import base64
5
5
  import json
6
+ import locale
6
7
  import logging
7
8
  import os
9
+ import sys
8
10
  import time
9
11
  from datetime import datetime
10
12
  from enum import Enum, unique
@@ -72,6 +74,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
72
74
  - wait_for_async_run()
73
75
  - run_app_sync()
74
76
  - run_task()
77
+ - cancle_task()
75
78
 
76
79
  # System Management
77
80
  - forward_to
@@ -134,6 +137,53 @@ class AppMeshClient(metaclass=abc.ABCMeta):
134
137
  DELETE = "DELETE"
135
138
  POST_STREAM = "POST_STREAM"
136
139
 
140
+ class EncodingResponse(requests.Response):
141
+ """Response subclass that handles encoding conversion on Windows."""
142
+
143
+ def __init__(self, response: requests.Response):
144
+ super().__init__()
145
+
146
+ # copy essential fields from response
147
+ self.__dict__.update(response.__dict__)
148
+
149
+ self._converted_text = None
150
+ self._should_convert = False
151
+
152
+ # Check if we need to convert encoding on Windows
153
+ if sys.platform == "win32":
154
+ content_type = response.headers.get("Content-Type", "").lower()
155
+ if response.status_code == HTTPStatus.OK and "text/plain" in content_type and "utf-8" in content_type:
156
+ try:
157
+ local_encoding = locale.getpreferredencoding()
158
+
159
+ if local_encoding.lower() not in ["utf-8", "utf8"]:
160
+ # Ensure response is decoded as UTF-8 first
161
+ self.encoding = "utf-8"
162
+ utf8_text = self.text # This gives us proper Unicode string
163
+
164
+ # Convert Unicode to local encoding, then back to Unicode
165
+ # This simulates how text would appear in local encoding
166
+ try:
167
+ local_bytes = utf8_text.encode(local_encoding, errors="replace")
168
+ self._converted_text = local_bytes.decode(local_encoding)
169
+ self._should_convert = True
170
+ except (UnicodeEncodeError, LookupError):
171
+ # If local encoding can't handle the characters, fall back to UTF-8
172
+ self._converted_text = utf8_text
173
+ self._should_convert = True
174
+
175
+ except (UnicodeError, LookupError):
176
+ # If any conversion fails, keep original UTF-8
177
+ self.encoding = "utf-8"
178
+
179
+ @property
180
+ def text(self):
181
+ """Return converted text if needed, otherwise original text."""
182
+ if self._should_convert and self._converted_text is not None:
183
+ return self._converted_text
184
+ # return the original text from _response without modification
185
+ return super().text
186
+
137
187
  def __init__(
138
188
  self,
139
189
  rest_url: str = "https://127.0.0.1:6060",
@@ -1100,7 +1150,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
1100
1150
  fp.write(chunk)
1101
1151
 
1102
1152
  # Apply file attributes (permissions, owner, group) if requested
1103
- if apply_file_attributes:
1153
+ if apply_file_attributes and sys.platform != "win32":
1104
1154
  if "X-File-Mode" in resp.headers:
1105
1155
  os.chmod(path=local_file, mode=int(resp.headers["X-File-Mode"]))
1106
1156
  if "X-File-User" in resp.headers and "X-File-Group" in resp.headers:
@@ -1186,6 +1236,22 @@ class AppMeshClient(metaclass=abc.ABCMeta):
1186
1236
 
1187
1237
  return resp.text
1188
1238
 
1239
+ def cancle_task(self, app_name: str) -> bool:
1240
+ """Client cancle a running task to a App Mesh application.
1241
+
1242
+ Args:
1243
+ app_name (str): Name of the target application (as registered in App Mesh).
1244
+
1245
+ Returns:
1246
+ bool: Task exist and cancled status.
1247
+ """
1248
+ path = f"/appmesh/app/{app_name}/task"
1249
+ resp = self._request_http(
1250
+ AppMeshClient.Method.DELETE,
1251
+ path=path,
1252
+ )
1253
+ return resp.status_code == HTTPStatus.OK
1254
+
1189
1255
  def run_app_async(
1190
1256
  self,
1191
1257
  app: Union[App, str],
@@ -1367,13 +1433,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
1367
1433
  else:
1368
1434
  raise Exception("Invalid http method", method)
1369
1435
 
1370
- # Ensure response text decoding uses UTF-8 by default
1371
- try:
1372
- resp.encoding = "utf-8"
1373
- except Exception:
1374
- # If setting encoding fails for any reason, ignore and return the response as-is
1375
- pass
1376
-
1377
- return resp
1436
+ # Wrap the response for encoding handling
1437
+ return AppMeshClient.EncodingResponse(resp)
1378
1438
  except requests.exceptions.RequestException as e:
1379
1439
  raise Exception(f"HTTP request failed: {str(e)}")
appmesh/client_tcp.py CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  import json
5
5
  import os
6
+ import sys
6
7
  import socket
7
8
  import uuid
8
9
  import requests
@@ -56,7 +57,7 @@ class AppMeshClientTCP(AppMeshClient):
56
57
  rest_ssl_verify=AppMeshClient.DEFAULT_SSL_CA_CERT_PATH if os.path.exists(AppMeshClient.DEFAULT_SSL_CA_CERT_PATH) else False,
57
58
  rest_ssl_client_cert=None,
58
59
  jwt_token=None,
59
- tcp_address=("localhost", 6059),
60
+ tcp_address=("127.0.0.1", 6059),
60
61
  ):
61
62
  """Construct an App Mesh client TCP object to communicate securely with an App Mesh server over TLS.
62
63
 
@@ -78,7 +79,7 @@ class AppMeshClientTCP(AppMeshClient):
78
79
  jwt_token (str, optional): JWT token for authentication. Used in methods requiring login and user authorization.
79
80
 
80
81
  tcp_address (Tuple[str, int], optional): Address and port for establishing a TCP connection to the server.
81
- Defaults to `("localhost", 6059)`.
82
+ Defaults to `("127.0.0.1", 6059)`.
82
83
  """
83
84
  self.tcp_transport = TCPTransport(address=tcp_address, ssl_verify=rest_ssl_verify, ssl_client_cert=rest_ssl_client_cert)
84
85
  super().__init__(rest_ssl_verify=rest_ssl_verify, rest_ssl_client_cert=rest_ssl_client_cert, jwt_token=jwt_token)
@@ -140,7 +141,7 @@ class AppMeshClientTCP(AppMeshClient):
140
141
  appmesh_request.headers[k] = v
141
142
  if query:
142
143
  for k, v in query.items():
143
- appmesh_request.querys[k] = v
144
+ appmesh_request.query[k] = v
144
145
 
145
146
  data = appmesh_request.serialize()
146
147
  self.tcp_transport.send_message(data)
@@ -153,13 +154,13 @@ class AppMeshClientTCP(AppMeshClient):
153
154
  appmesh_resp = ResponseMessage().deserialize(resp_data)
154
155
  response = requests.Response()
155
156
  response.status_code = appmesh_resp.http_status
156
- response.encoding = self.ENCODING_UTF8
157
- response._content = appmesh_resp.body.encode(self.ENCODING_UTF8)
157
+ # response.encoding = self.ENCODING_UTF8 # only need when charset not in appmesh_resp.body_msg_type
158
+ response._content = appmesh_resp.body if isinstance(appmesh_resp.body, bytes) else str(appmesh_resp.body).encode(self.ENCODING_UTF8)
158
159
  response.headers = appmesh_resp.headers
159
160
  if appmesh_resp.body_msg_type:
160
161
  response.headers["Content-Type"] = appmesh_resp.body_msg_type
161
162
 
162
- return response
163
+ return AppMeshClient.EncodingResponse(response)
163
164
 
164
165
  ########################################
165
166
  # File management
@@ -189,7 +190,7 @@ class AppMeshClientTCP(AppMeshClient):
189
190
  break
190
191
  fp.write(chunk_data)
191
192
 
192
- if apply_file_attributes:
193
+ if apply_file_attributes and sys.platform != "win32":
193
194
  if "X-File-Mode" in resp.headers:
194
195
  os.chmod(path=local_file, mode=int(resp.headers["X-File-Mode"]))
195
196
  if "X-File-User" in resp.headers and "X-File-Group" in resp.headers:
appmesh/server_http.py CHANGED
@@ -5,7 +5,7 @@ import abc
5
5
  import logging
6
6
  import os
7
7
  import time
8
- from typing import Optional, Tuple
8
+ from typing import Optional, Tuple, Union
9
9
  from http import HTTPStatus
10
10
  from .client_http import AppMeshClient
11
11
 
@@ -66,7 +66,7 @@ class AppMeshServer(metaclass=abc.ABCMeta):
66
66
  raise Exception("Missing environment variable: APP_MESH_APPLICATION_NAME. This must be set by App Mesh service.")
67
67
  return process_id, app_name
68
68
 
69
- def task_fetch(self) -> str:
69
+ def task_fetch(self) -> Union[str, bytes]:
70
70
  """Fetch task data in the currently running App Mesh application process.
71
71
 
72
72
  Used by App Mesh application process to obtain the payload from App Mesh service
@@ -74,7 +74,7 @@ class AppMeshServer(metaclass=abc.ABCMeta):
74
74
 
75
75
 
76
76
  Returns:
77
- str: The payload provided by the client as returned by the service.
77
+ Union[str, bytes]: The payload provided by the client as returned by the service.
78
78
  """
79
79
  process_id, app_name = self._get_runtime_env()
80
80
  path = f"/appmesh/app/{app_name}/task"
@@ -91,16 +91,16 @@ class AppMeshServer(metaclass=abc.ABCMeta):
91
91
  time.sleep(0.1)
92
92
  continue
93
93
 
94
- return resp.text
94
+ return resp.content
95
95
 
96
- def task_return(self, result: str) -> None:
96
+ def task_return(self, result: Union[str, bytes]) -> None:
97
97
  """Return the result of a server-side invocation back to the original client.
98
98
 
99
99
  Used by App Mesh application process to posts the `result` to App Mesh service
100
100
  after processed payload data so the invoking client can retrieve it.
101
101
 
102
102
  Args:
103
- result (str): Result payload to be delivered back to the client.
103
+ result (Union[str, bytes]): Result payload to be delivered back to the client.
104
104
  """
105
105
  process_id, app_name = self._get_runtime_env()
106
106
  path = f"/appmesh/app/{app_name}/task"
appmesh/server_tcp.py CHANGED
@@ -20,7 +20,7 @@ class AppMeshServerTCP(AppMeshServer):
20
20
  self,
21
21
  rest_ssl_verify=AppMeshClient.DEFAULT_SSL_CA_CERT_PATH if os.path.exists(AppMeshClient.DEFAULT_SSL_CA_CERT_PATH) else False,
22
22
  rest_ssl_client_cert=None,
23
- tcp_address: Tuple[str, int] = ("localhost", 6059),
23
+ tcp_address: Tuple[str, int] = ("127.0.0.1", 6059),
24
24
  *,
25
25
  logger_: Optional[logging.Logger] = None,
26
26
  ):
appmesh/tcp_messages.py CHANGED
@@ -1,41 +1,69 @@
1
1
  # tcp_messages.py
2
2
 
3
+ from typing import get_type_hints
3
4
  import msgpack
4
5
 
5
6
 
6
7
  class RequestMessage:
7
8
  """TCP request message for HTTP-like communication"""
8
9
 
9
- uuid: str = ""
10
- request_uri: str = ""
11
- http_method: str = ""
12
- client_addr: str = ""
13
- body: bytes = b""
14
- headers: dict = {}
15
- querys: dict = {}
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 = {}
16
18
 
17
19
  def serialize(self) -> bytes:
18
20
  """Serialize request message to bytes"""
19
- # http://www.cnitblog.com/luckydmz/archive/2019/11/20/91959.html
20
- self_dict = vars(self)
21
- self_dict["headers"] = self.headers
22
- self_dict["querys"] = self.querys
23
- return msgpack.dumps(self_dict)
21
+ return msgpack.dumps(vars(self), use_bin_type=True)
24
22
 
25
23
 
26
24
  class ResponseMessage:
27
25
  """TCP response message for HTTP-like communication"""
28
26
 
29
- uuid: str = ""
30
- request_uri: str = ""
31
- http_status: int = 0
32
- body_msg_type: str = ""
33
- body: str = ""
34
- headers: dict = {}
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 = {}
35
41
 
36
42
  def deserialize(self, buf: bytes):
37
- """Deserialize response message"""
38
- dic = msgpack.unpackb(buf)
43
+ """Deserialize TCP msgpack buffer with proper type conversion."""
44
+ dic = msgpack.unpackb(buf, raw=False)
45
+ hints = get_type_hints(self.__class__)
46
+
39
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)
40
67
  setattr(self, k, v)
68
+
41
69
  return self
appmesh/tcp_transport.py CHANGED
@@ -36,7 +36,7 @@ class TCPTransport:
36
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
37
 
38
38
  tcp_address (Tuple[str, int], optional): Address and port for establishing a TCP connection to the server.
39
- Defaults to `("localhost", 6059)`.
39
+ Defaults to `("127.0.0.1", 6059)`.
40
40
  """
41
41
  self.tcp_address = address
42
42
  self.ssl_verify = ssl_verify
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appmesh
3
- Version: 1.6.2
3
+ Version: 1.6.4
4
4
  Summary: Client SDK for App Mesh
5
5
  Home-page: https://github.com/laoshanxi/app-mesh
6
6
  Author: laoshanxi
@@ -40,9 +40,9 @@ Dynamic: summary
40
40
 
41
41
  # App Mesh: Advanced Application Management Platform
42
42
 
43
+ <div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/whatis.gif" align=center /></div>
43
44
  App Mesh is an open-source, multi-tenant application management platform designed for cloud-native environments. It efficiently manages, schedules, and monitors both microservices and traditional applications, offering a lightweight alternative to Kubernetes. App Mesh bridges the gap between simple process managers and complex container orchestration systems, making it ideal for organizations seeking to modernize their infrastructure without adopting full container-native complexity. Supporting both containerized and native applications, it provides a versatile solution for diverse enterprise needs.
44
45
 
45
- <div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/whatis.gif" align=center /></div>
46
46
  <div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/diagram.png" align=center /></div>
47
47
 
48
48
  ## Features
@@ -0,0 +1,16 @@
1
+ appmesh/__init__.py,sha256=TY1y5B5cE57uhraEzCFOZRWuo9SY1R-fYNRan8hCZOM,1670
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=hKv8t5ufqfnNkt4Bi4NCfDcn5LZisFP5qtNxzGsehVo,57457
7
+ appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
8
+ appmesh/client_tcp.py,sha256=ewKKltGHqjs7XM-9Hc1Jp9bciwvlNQR1k0Nzu3xviks,11442
9
+ appmesh/server_http.py,sha256=vf_Kh7ZIyEuBijZp8I2Rv-Fy9gxFdPFn5Pp2rUNCT1U,4319
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.4.dist-info/METADATA,sha256=9LUrGW7tdTI5__wP2srofLy7_mj6WK0_O3z1nUVgWvc,11828
14
+ appmesh-1.6.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ appmesh-1.6.4.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
16
+ appmesh-1.6.4.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- appmesh/__init__.py,sha256=TY1y5B5cE57uhraEzCFOZRWuo9SY1R-fYNRan8hCZOM,1670
2
- appmesh/app.py,sha256=4lo66ob1ZFDgL8VYKxH4_sh-vbHcGkZNThURE0go-d4,10732
3
- appmesh/app_output.py,sha256=vfn322AyixblI8DbXds08h6L_ybObiaRSifsA1-Xcoo,1035
4
- appmesh/app_run.py,sha256=TJ9xVX8xqUN9-OOmr_8JlgXoyg3hTmDFNKvGf4w_oR4,1980
5
- appmesh/appmesh_client.py,sha256=7yy9c_ygJbqMekrUsHWmRObwHLK82qP65xQLXxfGd8U,339
6
- appmesh/client_http.py,sha256=rVmGAc_nWkntoZBL7BCaQZtH9QOZXUis38tW9OhT2ps,54707
7
- appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
8
- appmesh/client_tcp.py,sha256=hE0T_2Z0OQZdF5zi--iuvu2_-ka0DfSSQ4BP3oHlg44,11243
9
- appmesh/server_http.py,sha256=zr9sfS8g_mtP2kuAfmz-qU7rLSFTVt3srbFXaCbl8Y0,4253
10
- appmesh/server_tcp.py,sha256=biBFF5IGWFOw2ru831cfmzn1DVXcBm9e-W6CP2VkfzE,1444
11
- appmesh/tcp_messages.py,sha256=4XRv5lSm_8ElMg_37SuyIRrfxI7XFNNP_7SdO7us3PA,1023
12
- appmesh/tcp_transport.py,sha256=f28zfZNH46tUHfT8F1PrCM1wUXiSBIW7R3ipMsXJqIU,8946
13
- appmesh-1.6.2.dist-info/METADATA,sha256=NljlBJBJxpevRRrgxFPyjNpxsYeu3FuUk7_klAhJCcs,11828
14
- appmesh-1.6.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- appmesh-1.6.2.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
16
- appmesh-1.6.2.dist-info/RECORD,,