appmesh 1.3.3__py3-none-any.whl → 1.3.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/appmesh_client.py CHANGED
@@ -25,13 +25,16 @@ DEFAULT_TOKEN_EXPIRE_SECONDS = "P1W" # default 7 day(s)
25
25
  DEFAULT_RUN_APP_TIMEOUT_SECONDS = "P2D" # 2 days
26
26
  DEFAULT_RUN_APP_LIFECYCLE_SECONDS = "P2DT12H" # 2.5 days
27
27
 
28
+ DEFAULT_SSL_CA_PEM_FILE = "/opt/appmesh/ssl/ca.pem"
29
+ DEFAULT_SSL_CLIENT_PEM_FILE = "/opt/appmesh/ssl/client.pem"
30
+ DEFAULT_SSL_CLIENT_PEM_KEY_FILE = "/opt/appmesh/ssl/client-key.pem"
31
+
32
+ # TLS-optimized chunk size (slightly less than maximum TLS record size)
33
+ # leaves some room for TLS overhead (like headers) within the 16 KB limit.
34
+ TCP_CHUNK_BLOCK_SIZE = 16 * 1024 - 256 # target to 16KB
35
+ TCP_MESSAGE_HEADER_LENGTH = 4
28
36
  REST_TEXT_MESSAGE_JSON_KEY = "message"
29
37
  MESSAGE_ENCODING_UTF8 = "utf-8"
30
- TCP_MESSAGE_HEADER_LENGTH = 4
31
-
32
- _SSL_CA_PEM_FILE = "/opt/appmesh/ssl/ca.pem"
33
- _SSL_CLIENT_PEM_FILE = "/opt/appmesh/ssl/client.pem"
34
- _SSL_CLIENT_PEM_KEY_FILE = "/opt/appmesh/ssl/client-key.pem"
35
38
 
36
39
  HTTP_USER_AGENT = "appmesh/python"
37
40
  HTTP_USER_AGENT_TCP = "appmesh/python/tcp"
@@ -437,8 +440,8 @@ class AppMeshClient(metaclass=abc.ABCMeta):
437
440
  def __init__(
438
441
  self,
439
442
  rest_url: str = "https://127.0.0.1:6060",
440
- rest_ssl_verify=_SSL_CA_PEM_FILE if os.path.exists(_SSL_CA_PEM_FILE) else False,
441
- rest_ssl_client_cert=(_SSL_CLIENT_PEM_FILE, _SSL_CLIENT_PEM_KEY_FILE) if os.path.exists(_SSL_CLIENT_PEM_FILE) else None,
443
+ rest_ssl_verify=DEFAULT_SSL_CA_PEM_FILE if os.path.exists(DEFAULT_SSL_CA_PEM_FILE) else False,
444
+ rest_ssl_client_cert=(DEFAULT_SSL_CLIENT_PEM_FILE, DEFAULT_SSL_CLIENT_PEM_KEY_FILE) if os.path.exists(DEFAULT_SSL_CLIENT_PEM_FILE) else None,
442
445
  rest_timeout=(60, 300),
443
446
  jwt_token=None,
444
447
  ):
@@ -450,7 +453,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
450
453
  the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use. Defaults to ``True``.
451
454
  rest_ssl_client_cert (tuple, optional): SSL client certificate and key pair. If String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
452
455
  rest_timeout (tuple, optional): HTTP timeout, Defaults to 60 seconds for connect timeout and 300 seconds for read timeout
453
- jwt_token (str, optional): JWT token, provide correct token is same with login() & authenticate().
456
+ jwt_token (str, optional): JWT token, provide correct token is same with login() & authentication().
454
457
  """
455
458
 
456
459
  self.server_url = rest_url
@@ -1221,24 +1224,20 @@ class AppMeshClient(metaclass=abc.ABCMeta):
1221
1224
  ########################################
1222
1225
  # File management
1223
1226
  ########################################
1224
- def file_download(self, file_path: str, local_file: str, apply_file_attributes: bool = True) -> bool:
1227
+ def file_download(self, remote_file: str, local_file: str, apply_file_attributes: bool = True) -> None:
1225
1228
  """Copy a remote file to local. Optionally, the local file will have the same permission as the remote file.
1226
1229
 
1227
1230
  Args:
1228
- file_path (str): the remote file path.
1231
+ remote_file (str): the remote file path.
1229
1232
  local_file (str): the local file path to be downloaded.
1230
1233
  apply_file_attributes (bool): whether to apply file attributes (permissions, owner, group) to the local file.
1231
-
1232
- Returns:
1233
- bool: success or failure.
1234
1234
  """
1235
- resp = self._request_http(AppMeshClient.Method.GET, path="/appmesh/file/download", header={"File-Path": file_path})
1236
- if resp.status_code != HTTPStatus.OK:
1237
- raise Exception(resp.text)
1235
+ resp = self._request_http(AppMeshClient.Method.GET, path="/appmesh/file/download", header={"File-Path": remote_file})
1236
+ resp.raise_for_status()
1238
1237
 
1239
1238
  # Write the file content locally
1240
1239
  with open(local_file, "wb") as fp:
1241
- for chunk in resp.iter_content(chunk_size=512):
1240
+ for chunk in resp.iter_content(chunk_size=8 * 1024): # 8 KB
1242
1241
  if chunk:
1243
1242
  fp.write(chunk)
1244
1243
 
@@ -1251,11 +1250,10 @@ class AppMeshClient(metaclass=abc.ABCMeta):
1251
1250
  file_gid = int(resp.headers["File-Group"])
1252
1251
  try:
1253
1252
  os.chown(path=local_file, uid=file_uid, gid=file_gid)
1254
- except Exception as ex:
1255
- print(ex)
1256
- return resp.status_code == HTTPStatus.OK
1253
+ except PermissionError:
1254
+ print(f"Warning: Unable to change owner/group of {local_file}. Operation requires elevated privileges.")
1257
1255
 
1258
- def file_upload(self, local_file: str, file_path: str, apply_file_attributes: bool = True) -> bool:
1256
+ def file_upload(self, local_file: str, remote_file: str, apply_file_attributes: bool = True) -> None:
1259
1257
  """Upload a local file to the remote server. Optionally, the remote file will have the same permission as the local file.
1260
1258
 
1261
1259
  Dependency:
@@ -1264,22 +1262,22 @@ class AppMeshClient(metaclass=abc.ABCMeta):
1264
1262
 
1265
1263
  Args:
1266
1264
  local_file (str): the local file path.
1267
- file_path (str): the target remote file to be uploaded.
1265
+ remote_file (str): the target remote file to be uploaded.
1268
1266
  apply_file_attributes (bool): whether to upload file attributes (permissions, owner, group) along with the file.
1269
-
1270
- Returns:
1271
- bool: success or failure.
1272
1267
  """
1268
+ if not os.path.exists(local_file):
1269
+ raise FileNotFoundError(f"Local file not found: {local_file}")
1270
+
1273
1271
  from requests_toolbelt import MultipartEncoder
1274
1272
 
1275
1273
  with open(file=local_file, mode="rb") as fp:
1276
- encoder = MultipartEncoder(fields={"filename": os.path.basename(file_path), "file": ("filename", fp, "application/octet-stream")})
1277
- header = {"File-Path": file_path, "Content-Type": encoder.content_type}
1274
+ encoder = MultipartEncoder(fields={"filename": os.path.basename(remote_file), "file": ("filename", fp, "application/octet-stream")})
1275
+ header = {"File-Path": remote_file, "Content-Type": encoder.content_type}
1278
1276
 
1279
1277
  # Include file attributes (permissions, owner, group) if requested
1280
1278
  if apply_file_attributes:
1281
1279
  file_stat = os.stat(local_file)
1282
- header["File-Mode"] = str(file_stat.st_mode)
1280
+ header["File-Mode"] = str(file_stat.st_mode & 0o777) # Mask to keep only permission bits
1283
1281
  header["File-User"] = str(file_stat.st_uid)
1284
1282
  header["File-Group"] = str(file_stat.st_gid)
1285
1283
 
@@ -1291,9 +1289,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
1291
1289
  header=header,
1292
1290
  body=encoder,
1293
1291
  )
1294
- if resp.status_code != HTTPStatus.OK:
1295
- raise Exception(resp.text)
1296
- return True
1292
+ resp.raise_for_status()
1297
1293
 
1298
1294
  ########################################
1299
1295
  # Application run
@@ -1450,11 +1446,11 @@ class AppMeshClientTCP(AppMeshClient):
1450
1446
  """
1451
1447
  Client SDK for interacting with the App Mesh service over TCP, with enhanced support for large file transfers.
1452
1448
 
1453
- The `AppMeshClientTCP` class extends the functionality of `AppMeshClient` by offering a TCP-based communication layer
1454
- for the App Mesh REST API. It overrides the file download and upload methods to support large file transfers with
1449
+ The `AppMeshClientTCP` class extends the functionality of `AppMeshClient` by offering a TCP-based communication layer
1450
+ for the App Mesh REST API. It overrides the file download and upload methods to support large file transfers with
1455
1451
  improved performance, leveraging TCP for lower latency and higher throughput compared to HTTP.
1456
1452
 
1457
- This client is suitable for applications requiring efficient data transfers and high-throughput operations within the
1453
+ This client is suitable for applications requiring efficient data transfers and high-throughput operations within the
1458
1454
  App Mesh ecosystem, while maintaining compatibility with all other attributes and methods from `AppMeshClient`.
1459
1455
 
1460
1456
  Dependency:
@@ -1482,7 +1478,7 @@ class AppMeshClientTCP(AppMeshClient):
1482
1478
 
1483
1479
  def __init__(
1484
1480
  self,
1485
- rest_ssl_verify=_SSL_CA_PEM_FILE if os.path.exists(_SSL_CA_PEM_FILE) else False,
1481
+ rest_ssl_verify=DEFAULT_SSL_CA_PEM_FILE if os.path.exists(DEFAULT_SSL_CA_PEM_FILE) else False,
1486
1482
  rest_ssl_client_cert=None,
1487
1483
  jwt_token=None,
1488
1484
  tcp_address=("localhost", 6059),
@@ -1493,13 +1489,13 @@ class AppMeshClientTCP(AppMeshClient):
1493
1489
  rest_ssl_verify (str, optional): (optional) SSL CA certification. Either a boolean, in which case it controls whether we verify
1494
1490
  the server's TLS certificate, or a string, in which case it must be a path to a CA bundle to use. Defaults to ``True``.
1495
1491
  rest_ssl_client_cert (tuple, optional): SSL client certificate and key pair . If String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
1496
- jwt_token (str, optional): JWT token, provide correct token is same with login() & authenticate().
1492
+ jwt_token (str, optional): JWT token, provide correct token is same with login() & authentication().
1497
1493
 
1498
1494
  tcp_address (tuple, optional): TCP connect address.
1499
1495
  """
1500
- super().__init__(rest_ssl_verify=rest_ssl_verify, rest_ssl_client_cert=rest_ssl_client_cert, jwt_token=jwt_token)
1501
1496
  self.tcp_address = tcp_address
1502
1497
  self.__socket_client = None
1498
+ super().__init__(rest_ssl_verify=rest_ssl_verify, rest_ssl_client_cert=rest_ssl_client_cert, jwt_token=jwt_token)
1503
1499
 
1504
1500
  def __del__(self) -> None:
1505
1501
  """De-construction"""
@@ -1508,15 +1504,31 @@ class AppMeshClientTCP(AppMeshClient):
1508
1504
  def __connect_socket(self) -> None:
1509
1505
  """Establish tcp connection"""
1510
1506
  context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
1507
+ # Set minimum TLS version
1511
1508
  if hasattr(context, "minimum_version"):
1512
1509
  context.minimum_version = ssl.TLSVersion.TLSv1_2
1513
1510
  else:
1514
1511
  context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
1515
- if self.ssl_verify:
1516
- context.verify_mode = ssl.CERT_REQUIRED
1517
- if isinstance(self.ssl_verify, str):
1518
- # Load server-side certificate authority (CA) certificates
1519
- context.load_verify_locations(self.ssl_verify)
1512
+ # Configure SSL verification
1513
+ if not self.ssl_verify:
1514
+ context.verify_mode = ssl.CERT_NONE
1515
+ else:
1516
+ context.verify_mode = ssl.CERT_REQUIRED # Require certificate verification
1517
+ context.load_default_certs() # Load system's default CA certificates
1518
+ if isinstance(self.ssl_verify, str):
1519
+ if os.path.isfile(self.ssl_verify):
1520
+ # Add custom CA certificate file
1521
+ try:
1522
+ context.load_verify_locations(cafile=self.ssl_verify)
1523
+ except ssl.SSLError:
1524
+ # If loading fails, try using just the default CAs
1525
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
1526
+ elif os.path.isdir(self.ssl_verify):
1527
+ # Load CA certificates from directory
1528
+ context.load_verify_locations(capath=self.ssl_verify)
1529
+ else:
1530
+ raise ValueError(f"ssl_verify path '{self.ssl_verify}' is neither a file nor a directory")
1531
+
1520
1532
  if self.ssl_client_cert is not None:
1521
1533
  # Load client-side certificate and private key
1522
1534
  context.load_cert_chain(certfile=self.ssl_client_cert[0], keyfile=self.ssl_client_cert[1])
@@ -1664,31 +1676,34 @@ class AppMeshClientTCP(AppMeshClient):
1664
1676
  ########################################
1665
1677
  # File management
1666
1678
  ########################################
1667
- def file_download(self, file_path: str, local_file: str) -> bool:
1679
+ def file_download(self, remote_file: str, local_file: str, apply_file_attributes: bool = True) -> None:
1668
1680
  """Copy a remote file to local, the local file will have the same permission as the remote file
1669
1681
 
1670
1682
  Args:
1671
- file_path (str): the remote file path.
1683
+ remote_file (str): the remote file path.
1672
1684
  local_file (str): the local file path to be downloaded.
1673
-
1674
- Returns:
1675
- bool: success or failure.
1685
+ apply_file_attributes (bool): whether to apply file attributes (permissions, owner, group) to the local file.
1676
1686
  """
1677
- header = {}
1678
- header["File-Path"] = file_path
1687
+ header = {"File-Path": remote_file}
1679
1688
  header[HTTP_HEADER_KEY_X_RECV_FILE_SOCKET] = "true"
1680
1689
  resp = self._request_http(AppMeshClient.Method.GET, path="/appmesh/file/download", header=header)
1681
- if resp.status_code == HTTPStatus.OK and HTTP_HEADER_KEY_X_RECV_FILE_SOCKET in resp.headers:
1682
- with open(local_file, "wb") as fp:
1683
- chunk_data = bytes()
1690
+
1691
+ resp.raise_for_status()
1692
+ if HTTP_HEADER_KEY_X_RECV_FILE_SOCKET not in resp.headers:
1693
+ raise ValueError(f"Server did not respond with socket transfer option: {HTTP_HEADER_KEY_X_RECV_FILE_SOCKET}")
1694
+
1695
+ with open(local_file, "wb") as fp:
1696
+ chunk_data = bytes()
1697
+ chunk_size = int.from_bytes(self.__recvall(TCP_MESSAGE_HEADER_LENGTH), byteorder="big", signed=False)
1698
+ while chunk_size > 0:
1699
+ chunk_data = self.__recvall(chunk_size)
1700
+ if chunk_data is None or len(chunk_data) == 0:
1701
+ self.__close_socket()
1702
+ raise Exception("socket connection broken")
1703
+ fp.write(chunk_data)
1684
1704
  chunk_size = int.from_bytes(self.__recvall(TCP_MESSAGE_HEADER_LENGTH), byteorder="big", signed=False)
1685
- while chunk_size > 0:
1686
- chunk_data = self.__recvall(chunk_size)
1687
- if chunk_data is None or len(chunk_data) == 0:
1688
- self.__close_socket()
1689
- raise Exception("socket connection broken")
1690
- fp.write(chunk_data)
1691
- chunk_size = int.from_bytes(self.__recvall(TCP_MESSAGE_HEADER_LENGTH), byteorder="big", signed=False)
1705
+
1706
+ if apply_file_attributes:
1692
1707
  if "File-Mode" in resp.headers:
1693
1708
  os.chmod(path=local_file, mode=int(resp.headers["File-Mode"]))
1694
1709
  if "File-User" in resp.headers and "File-Group" in resp.headers:
@@ -1696,12 +1711,10 @@ class AppMeshClientTCP(AppMeshClient):
1696
1711
  file_gid = int(resp.headers["File-Group"])
1697
1712
  try:
1698
1713
  os.chown(path=local_file, uid=file_uid, gid=file_gid)
1699
- except Exception as ex:
1700
- print(ex)
1701
- return True
1702
- return False
1714
+ except PermissionError:
1715
+ print(f"Warning: Unable to change owner/group of {local_file}. Operation requires elevated privileges.")
1703
1716
 
1704
- def file_upload(self, local_file: str, file_path: str):
1717
+ def file_upload(self, local_file: str, remote_file: str, apply_file_attributes: bool = True) -> None:
1705
1718
  """Upload a local file to the remote server, the remote file will have the same permission as the local file
1706
1719
 
1707
1720
  Dependency:
@@ -1710,30 +1723,34 @@ class AppMeshClientTCP(AppMeshClient):
1710
1723
 
1711
1724
  Args:
1712
1725
  local_file (str): the local file path.
1713
- file_path (str): the target remote file to be uploaded.
1714
-
1715
- Returns:
1716
- bool: success or failure.
1717
- str: text message.
1726
+ remote_file (str): the target remote file to be uploaded.
1727
+ apply_file_attributes (bool): whether to upload file attributes (permissions, owner, group) along with the file.
1718
1728
  """
1729
+ if not os.path.exists(local_file):
1730
+ raise FileNotFoundError(f"Local file not found: {local_file}")
1731
+
1719
1732
  with open(file=local_file, mode="rb") as fp:
1720
- file_stat = os.stat(local_file)
1721
- header = {}
1722
- header["File-Path"] = file_path
1723
- header["File-Mode"] = str(file_stat.st_mode)
1724
- header["File-User"] = str(file_stat.st_uid)
1725
- header["File-Group"] = str(file_stat.st_gid)
1726
- header["Content-Type"] = "text/plain"
1733
+ header = {"File-Path": remote_file, "Content-Type": "text/plain"}
1727
1734
  header[HTTP_HEADER_KEY_X_SEND_FILE_SOCKET] = "true"
1735
+
1736
+ if apply_file_attributes:
1737
+ file_stat = os.stat(local_file)
1738
+ header["File-Mode"] = str(file_stat.st_mode & 0o777) # Mask to keep only permission bits
1739
+ header["File-User"] = str(file_stat.st_uid)
1740
+ header["File-Group"] = str(file_stat.st_gid)
1741
+
1728
1742
  # https://stackoverflow.com/questions/22567306/python-requests-file-upload
1729
1743
  resp = self._request_http(AppMeshClient.Method.POST, path="/appmesh/file/upload", header=header)
1730
- if resp.status_code == HTTPStatus.OK and HTTP_HEADER_KEY_X_SEND_FILE_SOCKET in resp.headers:
1731
- chunk_size = 8 * 1024 # (8 KB in bytes), 131072 bytes (128 KB) is default max ssl buffer size
1744
+
1745
+ resp.raise_for_status()
1746
+ if HTTP_HEADER_KEY_X_SEND_FILE_SOCKET not in resp.headers:
1747
+ raise ValueError(f"Server did not respond with socket transfer option: {HTTP_HEADER_KEY_X_SEND_FILE_SOCKET}")
1748
+
1749
+ chunk_size = TCP_CHUNK_BLOCK_SIZE
1750
+ while True:
1732
1751
  chunk_data = fp.read(chunk_size)
1733
- while chunk_data:
1734
- self.__socket_client.sendall(len(chunk_data).to_bytes(TCP_MESSAGE_HEADER_LENGTH, byteorder="big", signed=False))
1735
- self.__socket_client.sendall(chunk_data)
1736
- chunk_data = fp.read(chunk_size)
1737
- self.__socket_client.sendall(int(0).to_bytes(TCP_MESSAGE_HEADER_LENGTH, byteorder="big", signed=False))
1738
- return True, ""
1739
- return False, resp.json()[REST_TEXT_MESSAGE_JSON_KEY]
1752
+ if not chunk_data:
1753
+ self.__socket_client.sendall((0).to_bytes(TCP_MESSAGE_HEADER_LENGTH, byteorder="big", signed=False))
1754
+ break
1755
+ self.__socket_client.sendall(len(chunk_data).to_bytes(TCP_MESSAGE_HEADER_LENGTH, byteorder="big", signed=False))
1756
+ self.__socket_client.sendall(chunk_data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: appmesh
3
- Version: 1.3.3
3
+ Version: 1.3.5
4
4
  Summary: Client SDK for App Mesh
5
5
  Home-page: https://github.com/laoshanxi/app-mesh
6
6
  Author: laoshanxi
@@ -0,0 +1,6 @@
1
+ appmesh/__init__.py,sha256=xRdXeFHEieRauuJZElbEBASgXG0ZzU1a5_0isAhM7Gw,11
2
+ appmesh/appmesh_client.py,sha256=bduPEDpI36XQyK_U9y3EvpZvO9CbpDeAB5402yf9qhU,69329
3
+ appmesh-1.3.5.dist-info/METADATA,sha256=VSkQis_Azjvw2FwjrK8qjOvYKw2WRNB37J2tYm_KEbw,11191
4
+ appmesh-1.3.5.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
5
+ appmesh-1.3.5.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
6
+ appmesh-1.3.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +0,0 @@
1
- appmesh/__init__.py,sha256=xRdXeFHEieRauuJZElbEBASgXG0ZzU1a5_0isAhM7Gw,11
2
- appmesh/appmesh_client.py,sha256=Hsp4at6YCLcAoz8eRx928amyCTqN4ZAIFnVHxEbzDrI,67720
3
- appmesh-1.3.3.dist-info/METADATA,sha256=73_wYNYMLoHLoew35YGTd2bGKWhfScEObKhr07yyHx0,11191
4
- appmesh-1.3.3.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
5
- appmesh-1.3.3.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
6
- appmesh-1.3.3.dist-info/RECORD,,