appmesh 1.6.3__py3-none-any.whl → 1.6.5__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/client_http.py +59 -68
- appmesh/client_tcp.py +8 -7
- appmesh/server_http.py +3 -3
- appmesh/server_tcp.py +1 -1
- appmesh/tcp_transport.py +1 -1
- {appmesh-1.6.3.dist-info → appmesh-1.6.5.dist-info}/METADATA +1 -1
- appmesh-1.6.5.dist-info/RECORD +16 -0
- appmesh-1.6.3.dist-info/RECORD +0 -16
- {appmesh-1.6.3.dist-info → appmesh-1.6.5.dist-info}/WHEEL +0 -0
- {appmesh-1.6.3.dist-info → appmesh-1.6.5.dist-info}/top_level.txt +0 -0
appmesh/client_http.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# client_http.py
|
2
|
-
# pylint: disable=broad-exception-raised,line-too-long,broad-exception-caught,too-many-lines,
|
2
|
+
# pylint: disable=broad-exception-raised,line-too-long,broad-exception-caught,too-many-lines,import-outside-toplevel
|
3
3
|
import abc
|
4
4
|
import base64
|
5
5
|
import json
|
@@ -17,69 +17,12 @@ from urllib import parse
|
|
17
17
|
import aniso8601
|
18
18
|
import jwt
|
19
19
|
import requests
|
20
|
+
from requests.auth import _basic_auth_str
|
20
21
|
from .app import App
|
21
22
|
from .app_run import AppRun
|
22
23
|
from .app_output import AppOutput
|
23
24
|
|
24
25
|
|
25
|
-
class EncodingResponse:
|
26
|
-
"""Wrapper for requests.Response that handles encoding conversion on Windows."""
|
27
|
-
|
28
|
-
def __init__(self, response: requests.Response):
|
29
|
-
self._response = response
|
30
|
-
self._converted_text = None
|
31
|
-
self._should_convert = False
|
32
|
-
|
33
|
-
# Check if we need to convert encoding on Windows
|
34
|
-
if sys.platform == "win32":
|
35
|
-
content_type = response.headers.get("Content-Type", "").lower()
|
36
|
-
if response.status_code == HTTPStatus.OK and "text/plain" in content_type and "utf-8" in content_type:
|
37
|
-
try:
|
38
|
-
local_encoding = locale.getpreferredencoding()
|
39
|
-
|
40
|
-
if local_encoding.lower() not in ["utf-8", "utf8"]:
|
41
|
-
# Ensure response is decoded as UTF-8 first
|
42
|
-
response.encoding = "utf-8"
|
43
|
-
utf8_text = response.text # This gives us proper Unicode string
|
44
|
-
|
45
|
-
# Convert Unicode to local encoding, then back to Unicode
|
46
|
-
# This simulates how text would appear in local encoding
|
47
|
-
try:
|
48
|
-
local_bytes = utf8_text.encode(local_encoding, errors="replace")
|
49
|
-
self._converted_text = local_bytes.decode(local_encoding)
|
50
|
-
self._should_convert = True
|
51
|
-
except (UnicodeEncodeError, LookupError):
|
52
|
-
# If local encoding can't handle the characters, fall back to UTF-8
|
53
|
-
self._converted_text = utf8_text
|
54
|
-
self._should_convert = True
|
55
|
-
|
56
|
-
except (UnicodeError, LookupError):
|
57
|
-
# If any conversion fails, keep original UTF-8
|
58
|
-
response.encoding = "utf-8"
|
59
|
-
|
60
|
-
@property
|
61
|
-
def text(self):
|
62
|
-
"""Return converted text if needed, otherwise original text."""
|
63
|
-
if self._should_convert and self._converted_text is not None:
|
64
|
-
return self._converted_text
|
65
|
-
# return the original text from _response without modification
|
66
|
-
return self._response.text
|
67
|
-
|
68
|
-
def __getattr__(self, name):
|
69
|
-
"""Dynamically delegate attribute access to the original response"""
|
70
|
-
return getattr(self._response, name)
|
71
|
-
|
72
|
-
def __dir__(self):
|
73
|
-
"""Optional: allow dir() to show attributes of both wrapper and response"""
|
74
|
-
return list(set(dir(self._response) + list(self.__dict__.keys())))
|
75
|
-
|
76
|
-
@property
|
77
|
-
def __class__(self):
|
78
|
-
"""Optional: allow isinstance checks for requests.Response"""
|
79
|
-
# Pretend to be a requests.Response for isinstance checks
|
80
|
-
return requests.Response
|
81
|
-
|
82
|
-
|
83
26
|
class AppMeshClient(metaclass=abc.ABCMeta):
|
84
27
|
"""
|
85
28
|
Client SDK for interacting with the App Mesh service via REST API.
|
@@ -132,6 +75,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
132
75
|
- wait_for_async_run()
|
133
76
|
- run_app_sync()
|
134
77
|
- run_task()
|
78
|
+
- cancle_task()
|
135
79
|
|
136
80
|
# System Management
|
137
81
|
- forward_to
|
@@ -180,6 +124,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
180
124
|
|
181
125
|
JSON_KEY_MESSAGE = "message"
|
182
126
|
HTTP_USER_AGENT = "appmesh/python"
|
127
|
+
HTTP_HEADER_KEY_AUTH = "Authorization"
|
183
128
|
HTTP_HEADER_KEY_USER_AGENT = "User-Agent"
|
184
129
|
HTTP_HEADER_KEY_X_TARGET_HOST = "X-Target-Host"
|
185
130
|
HTTP_HEADER_KEY_X_FILE_PATH = "X-File-Path"
|
@@ -194,6 +139,53 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
194
139
|
DELETE = "DELETE"
|
195
140
|
POST_STREAM = "POST_STREAM"
|
196
141
|
|
142
|
+
class EncodingResponse(requests.Response):
|
143
|
+
"""Response subclass that handles encoding conversion on Windows."""
|
144
|
+
|
145
|
+
def __init__(self, response: requests.Response):
|
146
|
+
super().__init__()
|
147
|
+
|
148
|
+
# copy essential fields from response
|
149
|
+
self.__dict__.update(response.__dict__)
|
150
|
+
|
151
|
+
self._converted_text = None
|
152
|
+
self._should_convert = False
|
153
|
+
|
154
|
+
# Check if we need to convert encoding on Windows
|
155
|
+
if sys.platform == "win32":
|
156
|
+
content_type = response.headers.get("Content-Type", "").lower()
|
157
|
+
if response.status_code == HTTPStatus.OK and "text/plain" in content_type and "utf-8" in content_type:
|
158
|
+
try:
|
159
|
+
local_encoding = locale.getpreferredencoding()
|
160
|
+
|
161
|
+
if local_encoding.lower() not in ["utf-8", "utf8"]:
|
162
|
+
# Ensure response is decoded as UTF-8 first
|
163
|
+
self.encoding = "utf-8"
|
164
|
+
utf8_text = self.text # This gives us proper Unicode string
|
165
|
+
|
166
|
+
# Convert Unicode to local encoding, then back to Unicode
|
167
|
+
# This simulates how text would appear in local encoding
|
168
|
+
try:
|
169
|
+
local_bytes = utf8_text.encode(local_encoding, errors="replace")
|
170
|
+
self._converted_text = local_bytes.decode(local_encoding)
|
171
|
+
self._should_convert = True
|
172
|
+
except (UnicodeEncodeError, LookupError):
|
173
|
+
# If local encoding can't handle the characters, fall back to UTF-8
|
174
|
+
self._converted_text = utf8_text
|
175
|
+
self._should_convert = True
|
176
|
+
|
177
|
+
except (UnicodeError, LookupError):
|
178
|
+
# If any conversion fails, keep original UTF-8
|
179
|
+
self.encoding = "utf-8"
|
180
|
+
|
181
|
+
@property
|
182
|
+
def text(self):
|
183
|
+
"""Return converted text if needed, otherwise original text."""
|
184
|
+
if self._should_convert and self._converted_text is not None:
|
185
|
+
return self._converted_text
|
186
|
+
# return the original text from _response without modification
|
187
|
+
return super().text
|
188
|
+
|
197
189
|
def __init__(
|
198
190
|
self,
|
199
191
|
rest_url: str = "https://127.0.0.1:6060",
|
@@ -239,7 +231,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
239
231
|
# Token auto-refresh
|
240
232
|
self._token_refresh_timer = None
|
241
233
|
self._auto_refresh_token = auto_refresh_token
|
242
|
-
self.jwt_token = jwt_token # Set property last after all dependencies are initialized
|
234
|
+
self.jwt_token = jwt_token # Set property last after all dependencies are initialized to setup refresh timer
|
243
235
|
|
244
236
|
@staticmethod
|
245
237
|
def _ensure_logging_configured():
|
@@ -316,7 +308,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
316
308
|
check_interval = 1 # Almost immediate refresh
|
317
309
|
else:
|
318
310
|
# Check at earlier of 5 minutes before expiry or regular interval
|
319
|
-
check_interval = min(time_to_expiry - self.TOKEN_REFRESH_OFFSET, self.TOKEN_REFRESH_INTERVAL)
|
311
|
+
check_interval = max(1, min(time_to_expiry - self.TOKEN_REFRESH_OFFSET, self.TOKEN_REFRESH_INTERVAL))
|
320
312
|
|
321
313
|
# Create timer to execute refresh check
|
322
314
|
self._token_refresh_timer = threading.Timer(check_interval, self._check_and_refresh_token)
|
@@ -482,7 +474,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
482
474
|
AppMeshClient.Method.POST,
|
483
475
|
path="/appmesh/login",
|
484
476
|
header={
|
485
|
-
|
477
|
+
self.HTTP_HEADER_KEY_AUTH: _basic_auth_str(user_name, user_pwd),
|
486
478
|
"X-Expire-Seconds": str(self._parse_duration(timeout_seconds)),
|
487
479
|
**({"X-Audience": audience} if audience else {}),
|
488
480
|
# **({"X-Totp-Code": totp_code} if totp_code else {}),
|
@@ -1160,7 +1152,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1160
1152
|
fp.write(chunk)
|
1161
1153
|
|
1162
1154
|
# Apply file attributes (permissions, owner, group) if requested
|
1163
|
-
if apply_file_attributes:
|
1155
|
+
if apply_file_attributes and sys.platform != "win32":
|
1164
1156
|
if "X-File-Mode" in resp.headers:
|
1165
1157
|
os.chmod(path=local_file, mode=int(resp.headers["X-File-Mode"]))
|
1166
1158
|
if "X-File-User" in resp.headers and "X-File-Group" in resp.headers:
|
@@ -1406,9 +1398,8 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1406
1398
|
|
1407
1399
|
# Prepare headers
|
1408
1400
|
header = {} if header is None else header
|
1409
|
-
if self.
|
1410
|
-
|
1411
|
-
header["Authorization"] = "Bearer " + token
|
1401
|
+
if token := self._get_access_token():
|
1402
|
+
header[self.HTTP_HEADER_KEY_AUTH] = f"Bearer {token}"
|
1412
1403
|
if self.forward_to and len(self.forward_to) > 0:
|
1413
1404
|
if ":" in self.forward_to:
|
1414
1405
|
header[self.HTTP_HEADER_KEY_X_TARGET_HOST] = self.forward_to
|
@@ -1444,6 +1435,6 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1444
1435
|
raise Exception("Invalid http method", method)
|
1445
1436
|
|
1446
1437
|
# Wrap the response for encoding handling
|
1447
|
-
return EncodingResponse(resp)
|
1438
|
+
return AppMeshClient.EncodingResponse(resp)
|
1448
1439
|
except requests.exceptions.RequestException as e:
|
1449
|
-
raise Exception(f"HTTP request failed: {str(e)}")
|
1440
|
+
raise Exception(f"HTTP request failed: {str(e)}") from e
|
appmesh/client_tcp.py
CHANGED
@@ -3,10 +3,11 @@
|
|
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
|
9
|
-
from .client_http import AppMeshClient
|
10
|
+
from .client_http import AppMeshClient
|
10
11
|
from .tcp_transport import TCPTransport
|
11
12
|
from .tcp_messages import RequestMessage, ResponseMessage
|
12
13
|
|
@@ -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=("
|
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 `("
|
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)
|
@@ -115,8 +116,8 @@ class AppMeshClientTCP(AppMeshClient):
|
|
115
116
|
self.tcp_transport.connect()
|
116
117
|
|
117
118
|
appmesh_request = RequestMessage()
|
118
|
-
if
|
119
|
-
appmesh_request.headers[
|
119
|
+
if token := self._get_access_token():
|
120
|
+
appmesh_request.headers[self.HTTP_HEADER_KEY_AUTH] = token
|
120
121
|
if super().forward_to and len(super().forward_to) > 0:
|
121
122
|
raise Exception("Not support forward request in TCP mode")
|
122
123
|
appmesh_request.headers[self.HTTP_HEADER_KEY_USER_AGENT] = self.HTTP_USER_AGENT_TCP
|
@@ -159,7 +160,7 @@ class AppMeshClientTCP(AppMeshClient):
|
|
159
160
|
if appmesh_resp.body_msg_type:
|
160
161
|
response.headers["Content-Type"] = appmesh_resp.body_msg_type
|
161
162
|
|
162
|
-
return EncodingResponse(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
@@ -17,7 +17,7 @@ class AppMeshServer(metaclass=abc.ABCMeta):
|
|
17
17
|
Server SDK for App Mesh application interacting with the local App Mesh REST service over HTTPS.
|
18
18
|
|
19
19
|
Build-in runtime environment variables required:
|
20
|
-
-
|
20
|
+
- APP_MESH_PROCESS_KEY
|
21
21
|
- APP_MESH_APPLICATION_NAME
|
22
22
|
|
23
23
|
Methods:
|
@@ -58,10 +58,10 @@ class AppMeshServer(metaclass=abc.ABCMeta):
|
|
58
58
|
@staticmethod
|
59
59
|
def _get_runtime_env() -> Tuple[str, str]:
|
60
60
|
"""Read and validate required runtime environment variables."""
|
61
|
-
process_id = os.getenv("
|
61
|
+
process_id = os.getenv("APP_MESH_PROCESS_KEY")
|
62
62
|
app_name = os.getenv("APP_MESH_APPLICATION_NAME")
|
63
63
|
if not process_id:
|
64
|
-
raise Exception("Missing environment variable:
|
64
|
+
raise Exception("Missing environment variable: APP_MESH_PROCESS_KEY. This must be set by App Mesh service.")
|
65
65
|
if not app_name:
|
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
|
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] = ("
|
23
|
+
tcp_address: Tuple[str, int] = ("127.0.0.1", 6059),
|
24
24
|
*,
|
25
25
|
logger_: Optional[logging.Logger] = None,
|
26
26
|
):
|
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 `("
|
39
|
+
Defaults to `("127.0.0.1", 6059)`.
|
40
40
|
"""
|
41
41
|
self.tcp_address = address
|
42
42
|
self.ssl_verify = ssl_verify
|
@@ -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=Clx0Wx8I8Wo8T63f2dco_vDI12mbcqKOMt7d7xBXJFc,57516
|
7
|
+
appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
|
8
|
+
appmesh/client_tcp.py,sha256=hQ4s9ab1Drqaqhfx-pmLhqhFL48jbwNbkzOv1fCSUnw,11444
|
9
|
+
appmesh/server_http.py,sha256=H6DfMhU2NxlBqxjmRG2C8NXMaGDxy_5QVyBmaVrelfI,4322
|
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.5.dist-info/METADATA,sha256=vYzVg5U0rZNL8_HaPR3-CPuw2RFoLsUiB8UB7YDNyJI,11828
|
14
|
+
appmesh-1.6.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
appmesh-1.6.5.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
16
|
+
appmesh-1.6.5.dist-info/RECORD,,
|
appmesh-1.6.3.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
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=WLdHmi2ryzCiXFyIIAbKH3arfcs__rxt4kwehpO_w6s,57711
|
7
|
-
appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
|
8
|
-
appmesh/client_tcp.py,sha256=OrKmJ-SMSmCPXj5WVhWwBXiK8Z_WyCrDmTim0pe1pc0,11407
|
9
|
-
appmesh/server_http.py,sha256=vf_Kh7ZIyEuBijZp8I2Rv-Fy9gxFdPFn5Pp2rUNCT1U,4319
|
10
|
-
appmesh/server_tcp.py,sha256=biBFF5IGWFOw2ru831cfmzn1DVXcBm9e-W6CP2VkfzE,1444
|
11
|
-
appmesh/tcp_messages.py,sha256=H9S_iCy0IuufY2v50_SUgRvcyQmJsySG65tBe_xb3Ko,1878
|
12
|
-
appmesh/tcp_transport.py,sha256=f28zfZNH46tUHfT8F1PrCM1wUXiSBIW7R3ipMsXJqIU,8946
|
13
|
-
appmesh-1.6.3.dist-info/METADATA,sha256=Hcymb5xi90fgxLHqHNWHzicT2KLZzGPo4DonII-IQBQ,11828
|
14
|
-
appmesh-1.6.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
-
appmesh-1.6.3.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
16
|
-
appmesh-1.6.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|