matlab-proxy 0.5.3__py3-none-any.whl → 0.30.1__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.
Files changed (104) hide show
  1. matlab_proxy/app.py +578 -205
  2. matlab_proxy/app_state.py +1061 -431
  3. matlab_proxy/constants.py +37 -0
  4. matlab_proxy/default_configuration.py +39 -4
  5. matlab_proxy/devel.py +18 -22
  6. matlab_proxy/gui/index.html +20 -1
  7. matlab_proxy/gui/static/css/index.BedVwcEg.css +10 -0
  8. matlab_proxy/gui/static/js/index.pQwV1obF.js +64 -0
  9. matlab_proxy/gui/static/media/MATLAB-env-blur.NupTbPv_.png +0 -0
  10. matlab_proxy/matlab/evaluateUserMatlabCode.m +51 -0
  11. matlab_proxy/matlab/startup.m +3 -28
  12. matlab_proxy/settings.py +543 -112
  13. matlab_proxy/util/__init__.py +187 -59
  14. matlab_proxy/util/cookie_jar.py +72 -0
  15. matlab_proxy/util/event_loop.py +28 -10
  16. matlab_proxy/util/list_servers.py +71 -26
  17. matlab_proxy/util/mw.py +16 -15
  18. matlab_proxy/util/mwi/download.py +136 -0
  19. matlab_proxy/util/mwi/embedded_connector/__init__.py +1 -1
  20. matlab_proxy/util/mwi/embedded_connector/helpers.py +12 -4
  21. matlab_proxy/util/mwi/embedded_connector/request.py +78 -12
  22. matlab_proxy/util/mwi/environment_variables.py +120 -27
  23. matlab_proxy/util/mwi/exceptions.py +63 -9
  24. matlab_proxy/util/mwi/logger.py +141 -27
  25. matlab_proxy/util/mwi/session_name.py +28 -0
  26. matlab_proxy/util/mwi/token_auth.py +264 -121
  27. matlab_proxy/util/mwi/validators.py +231 -88
  28. matlab_proxy/util/system.py +9 -0
  29. matlab_proxy/util/windows.py +32 -6
  30. {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info}/METADATA +94 -49
  31. matlab_proxy-0.30.1.dist-info/RECORD +88 -0
  32. {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info}/WHEEL +1 -2
  33. {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info}/entry_points.txt +1 -1
  34. matlab_proxy_manager/README.md +85 -0
  35. matlab_proxy_manager/__init__.py +6 -0
  36. matlab_proxy_manager/lib/README.md +53 -0
  37. matlab_proxy_manager/lib/__init__.py +1 -0
  38. matlab_proxy_manager/lib/api.py +419 -0
  39. matlab_proxy_manager/storage/README.md +54 -0
  40. matlab_proxy_manager/storage/__init__.py +1 -0
  41. matlab_proxy_manager/storage/file_repository.py +144 -0
  42. matlab_proxy_manager/storage/interface.py +62 -0
  43. matlab_proxy_manager/storage/server.py +172 -0
  44. matlab_proxy_manager/utils/__init__.py +1 -0
  45. matlab_proxy_manager/utils/auth.py +77 -0
  46. matlab_proxy_manager/utils/constants.py +8 -0
  47. matlab_proxy_manager/utils/decorators.py +37 -0
  48. matlab_proxy_manager/utils/environment_variables.py +51 -0
  49. matlab_proxy_manager/utils/exceptions.py +45 -0
  50. matlab_proxy_manager/utils/helpers.py +314 -0
  51. matlab_proxy_manager/utils/logger.py +76 -0
  52. matlab_proxy_manager/web/README.md +37 -0
  53. matlab_proxy_manager/web/__init__.py +1 -0
  54. matlab_proxy_manager/web/app.py +536 -0
  55. matlab_proxy_manager/web/monitor.py +45 -0
  56. matlab_proxy_manager/web/watcher.py +65 -0
  57. matlab_proxy/gui/asset-manifest.json +0 -23
  58. matlab_proxy/gui/authorization.html +0 -115
  59. matlab_proxy/gui/bootstrap.3.4.1.min.css +0 -6
  60. matlab_proxy/gui/navbar.css +0 -8
  61. matlab_proxy/gui/signin.css +0 -42
  62. matlab_proxy/gui/static/css/main.d890078a.chunk.css +0 -13
  63. matlab_proxy/gui/static/css/main.d890078a.chunk.css.map +0 -1
  64. matlab_proxy/gui/static/js/2.13be6544.chunk.js +0 -3
  65. matlab_proxy/gui/static/js/2.13be6544.chunk.js.LICENSE.txt +0 -59
  66. matlab_proxy/gui/static/js/2.13be6544.chunk.js.map +0 -1
  67. matlab_proxy/gui/static/js/main.c311d854.chunk.js +0 -2
  68. matlab_proxy/gui/static/js/main.c311d854.chunk.js.map +0 -1
  69. matlab_proxy/gui/static/js/runtime-main.f70e4d5f.js +0 -2
  70. matlab_proxy/gui/static/js/runtime-main.f70e4d5f.js.map +0 -1
  71. matlab_proxy/gui/static/media/arrow.0c2968b9.svg +0 -4
  72. matlab_proxy/gui/static/media/feedback.6e8d50eb.svg +0 -1
  73. matlab_proxy/gui/static/media/gripper.9defbc5e.svg +0 -1
  74. matlab_proxy/gui/static/media/help.15e5bfab.svg +0 -1
  75. matlab_proxy/gui/static/media/ico-header-contact-hover.0958c442.svg +0 -17
  76. matlab_proxy/gui/static/media/ico-header-contact.ae9169c8.svg +0 -17
  77. matlab_proxy/gui/static/media/restart.7987508a.svg +0 -1
  78. matlab_proxy/gui/static/media/sign-out.08356b67.svg +0 -1
  79. matlab_proxy/gui/static/media/start.50c4596f.svg +0 -1
  80. matlab_proxy/gui/static/media/stop.30c9a9ab.svg +0 -1
  81. matlab_proxy/gui/static/media/terminate.7ea1363e.svg +0 -1
  82. matlab_proxy/gui/token.html +0 -123
  83. matlab_proxy-0.5.3.dist-info/RECORD +0 -84
  84. matlab_proxy-0.5.3.dist-info/top_level.txt +0 -1
  85. /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.82b1212e.woff → glyphicons-halflings-regular.BKjkU69z.woff} +0 -0
  86. /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.5be1347c.eot → glyphicons-halflings-regular.BUJKDMgK.eot} +0 -0
  87. /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.060b2710.svg → glyphicons-halflings-regular.CSehLiBc.svg} +0 -0
  88. /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.4692b9ec.ttf → glyphicons-halflings-regular.DrwTMapi.ttf} +0 -0
  89. /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.be810be3.woff2 → glyphicons-halflings-regular.DzqM6ju8.woff2} +0 -0
  90. /matlab_proxy/gui/static/media/{ico-header-account-hover.89438e91.svg → ico-header-account-hover.-jQHo6Wx.svg} +0 -0
  91. /matlab_proxy/gui/static/media/{ico-header-account.86b10d7b.svg → ico-header-account.CJCFoo5a.svg} +0 -0
  92. /matlab_proxy/gui/static/media/{ico-sprite.cbdb66c0.png → ico-sprite.DXGLgzq9.png} +0 -0
  93. /matlab_proxy/gui/static/media/{mathworks-eps.4d20e0ee.ttf → mathworks-eps.CGNQALa9.ttf} +0 -0
  94. /matlab_proxy/gui/static/media/{mathworks-eps.df1428df.svg → mathworks-eps.DrkCtQtG.svg} +0 -0
  95. /matlab_proxy/gui/static/media/{mathworks-eps.e5c41e84.woff → mathworks-eps.Ds7lQbql.woff} +0 -0
  96. /matlab_proxy/gui/static/media/{mathworks-pictograms.3fc6513a.woff → mathworks-pictograms.BdqxEfBR.woff} +0 -0
  97. /matlab_proxy/gui/static/media/{mathworks-pictograms.f6f087b0.svg → mathworks-pictograms.CCLweoD4.svg} +0 -0
  98. /matlab_proxy/gui/static/media/{mathworks-pictograms.6e128c0e.ttf → mathworks-pictograms.DZhFdRSm.ttf} +0 -0
  99. /matlab_proxy/gui/static/media/{mathworks.80a3218e.svg → mathworks.C-qsbhDy.svg} +0 -0
  100. /matlab_proxy/gui/static/media/{mathworks.c422935b.ttf → mathworks.Ceplx86V.ttf} +0 -0
  101. /matlab_proxy/gui/static/media/{mathworks.37a563ef.woff → mathworks.D08X1Vp8.woff} +0 -0
  102. /matlab_proxy/gui/static/media/{trigger-error.3f1c4ef2.svg → trigger-error.QEdsGL-m.svg} +0 -0
  103. /matlab_proxy/gui/static/media/{trigger-ok.7b9c238b.svg → trigger-ok.Dzg8OIrk.svg} +0 -0
  104. {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info/licenses}/LICENSE.md +0 -0
@@ -0,0 +1,172 @@
1
+ # Copyright 2024-2025 The MathWorks, Inc.
2
+ import json
3
+ from dataclasses import asdict, dataclass, field
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import psutil
8
+
9
+ from matlab_proxy_manager.utils import helpers, logger
10
+
11
+ log = logger.get()
12
+
13
+
14
+ @dataclass
15
+ class ServerProcess:
16
+ """
17
+ Represents a MATLAB server process with various attributes and methods
18
+ to manage its lifecycle.
19
+ """
20
+
21
+ server_url: Optional[str] = None
22
+ mwi_base_url: Optional[str] = None
23
+ headers: Optional[dict] = None
24
+ errors: Optional[list] = None
25
+ pid: Optional[str] = None
26
+ parent_pid: Optional[str] = None
27
+ absolute_url: Optional[str] = field(default=None)
28
+ id: Optional[str] = None
29
+ type: Optional[str] = None
30
+ mpm_auth_token: Optional[str] = None
31
+
32
+ def __post_init__(self) -> None:
33
+ if not self.absolute_url:
34
+ self.absolute_url = f"{self.server_url}{self.mwi_base_url}"
35
+
36
+ def __str__(self) -> str:
37
+ """
38
+ Returns a string representation of the ServerProcess instance.
39
+ """
40
+ return json.dumps(asdict(self))
41
+
42
+ def as_dict(self) -> dict:
43
+ """
44
+ Returns a dict representation of the ServerProcess instance.
45
+ """
46
+ return asdict(self)
47
+
48
+ @staticmethod
49
+ def instantiate_from_string(data: str) -> "ServerProcess":
50
+ """
51
+ Instantiates a ServerProcess object from a JSON string.
52
+
53
+ Args:
54
+ data (str): The JSON string representing a ServerProcess.
55
+
56
+ Returns:
57
+ ServerProcess: An instance of ServerProcess.
58
+
59
+ Raises:
60
+ ValueError: If the JSON string cannot be parsed or is missing required fields.
61
+ """
62
+ try:
63
+ full_dict = json.loads(data)
64
+ key = list(full_dict.keys())[0]
65
+ server = full_dict[key]
66
+ server_process = ServerProcess(
67
+ server_url=server["server_url"],
68
+ mwi_base_url=server["mwi_base_url"],
69
+ headers=server["headers"],
70
+ errors=server["errors"],
71
+ pid=server["pid"],
72
+ parent_pid=server["parent_pid"],
73
+ absolute_url=server["absolute_url"],
74
+ id=server["id"],
75
+ type=server["type"],
76
+ mpm_auth_token=server["mpm_auth_token"],
77
+ )
78
+ return server_process
79
+ except (json.JSONDecodeError, KeyError) as ex:
80
+ log.debug("Failed to instantiate server from %s: %s", data, ex)
81
+ raise ValueError(
82
+ "Invalid JSON string for ServerProcess instantiation"
83
+ ) from ex
84
+
85
+ def shutdown(self):
86
+ """
87
+ Shuts down the MATLAB proxy server by calling the shutdown_integration endpoint.
88
+ """
89
+ log.debug("Shutting down matlab proxy")
90
+ backend_server = self.absolute_url
91
+ url = f"{backend_server}/shutdown_integration"
92
+ try:
93
+ response = helpers.requests_retry_session(retries=1).delete(
94
+ url=url, headers=self.headers
95
+ )
96
+ shutdown_resp = response.json()
97
+ log.debug("Response from shutdown: %s", response.json())
98
+ return shutdown_resp
99
+ except Exception as e:
100
+ log.debug("Exception while shutting down matlab proxy: %s", e)
101
+
102
+ # Force kill matlab-proxy and its process tree if termination
103
+ # via shutdown_integration endpoint fails
104
+ try:
105
+ matlab_proxy_process = psutil.Process(int(self.pid))
106
+ self.terminate_process_tree(matlab_proxy_process)
107
+ except Exception as e:
108
+ log.debug("Exception while terminating child processes: %s", e)
109
+ return None
110
+
111
+ def terminate_process_tree(self, matlab_proxy_process):
112
+ """
113
+ Terminates the process tree of the MATLAB proxy server.
114
+
115
+ This method terminates all child processes of the MATLAB proxy process,
116
+ waits for a short period, and then forcefully kills any remaining processes.
117
+ Finally, it kills the main MATLAB proxy process itself.
118
+ """
119
+ matlab_proxy_child_processes = matlab_proxy_process.children(recursive=True)
120
+ for child_process in matlab_proxy_child_processes:
121
+ child_process.terminate()
122
+ _, alive = psutil.wait_procs(matlab_proxy_child_processes, timeout=5)
123
+ for process in alive:
124
+ # Kill the processes which are alive even after waiting for 5 seconds
125
+ process.kill()
126
+ matlab_proxy_process.kill()
127
+ log.debug("Killed matlab-proxy process with PID: %s", self.pid)
128
+
129
+ def is_server_alive(self) -> bool:
130
+ """
131
+ Checks if the server process is alive and ready.
132
+
133
+ Returns:
134
+ bool: True if the server process is alive and ready, False otherwise.
135
+ """
136
+ return helpers.does_process_exist(self.pid) and helpers.is_server_ready(
137
+ self.absolute_url, retries=0
138
+ )
139
+
140
+ @staticmethod
141
+ def find_existing_server(data_dir, key: str) -> Optional["ServerProcess"]:
142
+ """
143
+ Finds an existing server process by reading the server configuration from a file.
144
+
145
+ Args:
146
+ data_dir (str): The directory where server configuration files are stored.
147
+ key (str): The key corresponding to the specific server configuration.
148
+
149
+ Returns:
150
+ Optional[ServerProcess]: An instance of ServerProcess if a valid configuration is found,
151
+ otherwise None.
152
+ """
153
+ key_dir = Path(data_dir, key)
154
+ server_process = None
155
+
156
+ # Return early if the directory is not found
157
+ if not key_dir.is_dir():
158
+ return server_process
159
+
160
+ files = list(key_dir.iterdir())
161
+ if not files:
162
+ return server_process
163
+
164
+ try:
165
+ with open(files[0], "r", encoding="utf-8") as file:
166
+ data = file.read().strip()
167
+ if data:
168
+ server_process = ServerProcess.instantiate_from_string(data)
169
+ except (OSError, ValueError) as ex:
170
+ log.debug("Exception while checking for existing server: %s", ex)
171
+
172
+ return server_process
@@ -0,0 +1 @@
1
+ # Copyright 2024 The MathWorks, Inc.
@@ -0,0 +1,77 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ from hmac import compare_digest
3
+
4
+ from aiohttp import web
5
+
6
+ from matlab_proxy_manager.utils import logger
7
+ from matlab_proxy_manager.utils.constants import HEADER_MWI_MPM_AUTH_TOKEN
8
+
9
+ log = logger.get()
10
+
11
+
12
+ async def authenticate_request(request):
13
+ """Authenticates incoming request by verifying whether the expected token is in the request.
14
+
15
+ The MWI-MPM-AUTH-TOKEN must be present in the request's headers.
16
+
17
+ Returns True/False based on whether the request is authenticated.
18
+ """
19
+
20
+ log.debug("<======== Authenticate request: %s", request)
21
+ return await _is_valid_token_in_headers(request)
22
+
23
+
24
+ def authenticate_access_decorator(endpoint):
25
+ """This decorator verifies that the request to an endpoint exposed by matlab-proxy-manager
26
+ contains the correct mpm auth token before servicing an endpoint."""
27
+
28
+ async def protect_endpoint(request):
29
+ """Passes request to the endpoint after validating the token
30
+
31
+ Args:
32
+ request (HTTPRequest) : Web Request to endpoint
33
+
34
+ Raises:
35
+ web.HTTPForbidden: Thrown when validation of token fails
36
+ """
37
+ if await authenticate_request(request):
38
+ # request is authentic, proceed to execute the endpoint
39
+ return await endpoint(request)
40
+ raise web.HTTPForbidden(reason="Unauthorized access!")
41
+
42
+ return protect_endpoint
43
+
44
+
45
+ async def _is_valid_token_in_headers(request):
46
+ """Checks the request headers for mpm auth token
47
+
48
+ Args:
49
+ request (HTTPRequest) : Used to access app settings
50
+
51
+ Returns:
52
+ Boolean : True if valid token is found, else False
53
+ """
54
+ headers = request.headers
55
+ if HEADER_MWI_MPM_AUTH_TOKEN in headers:
56
+ return await _is_valid_token(headers[HEADER_MWI_MPM_AUTH_TOKEN], request)
57
+
58
+ log.debug("Header: %s not found in request headers", HEADER_MWI_MPM_AUTH_TOKEN)
59
+ return False
60
+
61
+
62
+ async def _is_valid_token(token, request):
63
+ """Checks if token contains expected value.
64
+
65
+ Args:
66
+ token (str): Token string to validate
67
+ request (HTTPRequest) : Used to access app settings
68
+
69
+ Returns:
70
+ _type_: True if token is valid, false otherwise.
71
+ """
72
+ # Check if the token provided in the request matches the original token
73
+ # equivalent to a == b, but protects against timing attacks
74
+ saved_token = request.app["auth_token"]
75
+ is_valid = compare_digest(token, saved_token)
76
+ log.debug("Token validation %s ", "successful." if is_valid else "failed.")
77
+ return is_valid
@@ -0,0 +1,8 @@
1
+ # Copyright 2024-2025 The MathWorks, Inc.
2
+ import re
3
+
4
+ MWI_BASE_URL_PREFIX = "/matlab/"
5
+ MWI_DEFAULT_MATLAB_PATH = MWI_BASE_URL_PREFIX + "default"
6
+ HEADER_MWI_MPM_CONTEXT = "MWI-MPM-CONTEXT"
7
+ HEADER_MWI_MPM_AUTH_TOKEN = "MWI-MPM-AUTH-TOKEN"
8
+ MATLAB_IN_REQ_PATH_PATTERN = re.compile(r".*?/matlab/([^/]+)/(.*)")
@@ -0,0 +1,37 @@
1
+ # Copyright 2025 The MathWorks, Inc.
2
+
3
+ import functools
4
+ from typing import Callable
5
+
6
+ from matlab_proxy_manager.utils.constants import (
7
+ HEADER_MWI_MPM_CONTEXT,
8
+ )
9
+ from matlab_proxy_manager.utils.helpers import render_error_page
10
+
11
+
12
+ def validate_incoming_request_decorator(endpoint: Callable):
13
+ """Decorator to validate incoming requests.
14
+
15
+ This decorator checks if the request contains the required MWI_MPM_CONTEXT header.
16
+ If the header is not found, it returns an error page. Otherwise, it adds the context
17
+ to the request object and proceeds with the endpoint execution.
18
+
19
+ Args:
20
+ endpoint (Callable): The endpoint function to be decorated.
21
+
22
+ Returns:
23
+ Callable: The wrapped function that validates the request before calling the endpoint.
24
+ """
25
+
26
+ @functools.wraps(endpoint)
27
+ async def wrapper(req):
28
+ ctx = req.headers.get(HEADER_MWI_MPM_CONTEXT)
29
+ if not ctx:
30
+ return render_error_page(
31
+ f"Required header: ${HEADER_MWI_MPM_CONTEXT} not found in the request"
32
+ )
33
+ # Add ctx to the request object
34
+ req.ctx = ctx
35
+ return await endpoint(req)
36
+
37
+ return wrapper
@@ -0,0 +1,51 @@
1
+ # Copyright 2020-2025 The MathWorks, Inc.
2
+ """This file lists and exposes the environment variables which are used by proxy manager."""
3
+
4
+ import os
5
+
6
+
7
+ def _is_env_set_to_true(env_name: str) -> bool:
8
+ """Helper function that returns True if the environment variable specified is set to True.
9
+
10
+ Args:
11
+ env_name (str): Name of the environment variable to check the state for.
12
+
13
+ Returns:
14
+ bool: True if the environment variable's value matches(case-insensitive) the string "True"
15
+ """
16
+ return os.environ.get(env_name, "").lower() == "true"
17
+
18
+
19
+ def get_env_name_logging_level():
20
+ """Specifies the logging level used by app's loggers"""
21
+ return "MWI_MPM_LOG_LEVEL"
22
+
23
+
24
+ def get_env_name_enable_web_logging():
25
+ """Enable the logging of asyncio web traffic by setting to true"""
26
+ return "MWI_MPM_ENABLE_WEB_LOGGING"
27
+
28
+
29
+ def get_env_name_mwi_mpm_auth_token():
30
+ """Authentication environment variable for authenticating with proxy manager"""
31
+ return "MWI_MPM_AUTH_TOKEN"
32
+
33
+
34
+ def get_env_name_mwi_mpm_port():
35
+ """Used to specify the port on which to start proxy manager"""
36
+ return "MWI_MPM_PORT"
37
+
38
+
39
+ def get_env_name_mwi_mpm_parent_pid():
40
+ """Used to specify the parent pid for the proxy manager process"""
41
+ return "MWI_MPM_PARENT_PID"
42
+
43
+
44
+ def get_env_name_base_url_prefix():
45
+ """Used to specify the base url prefix for setting base url on matlab (e.g. Jupyter base url)"""
46
+ return "MWI_MPM_BASE_URL_PREFIX"
47
+
48
+
49
+ def is_web_logging_enabled():
50
+ """Returns true if the web logging is required to be enabled"""
51
+ return _is_env_set_to_true(get_env_name_enable_web_logging())
@@ -0,0 +1,45 @@
1
+ # Copyright 2025 The MathWorks, Inc.
2
+
3
+
4
+ class MATLABProxyError(Exception):
5
+ """Base class for all MATLAB Proxy Manager exceptions."""
6
+
7
+ pass
8
+
9
+
10
+ class ProcessStartError(MATLABProxyError):
11
+ """Exception thrown when MATLAB proxy process fails to start."""
12
+
13
+ def __init__(
14
+ self, message="Failed to create matlab-proxy subprocess.", extra_info=None
15
+ ):
16
+ self.message = message
17
+ self.extra_info = extra_info
18
+ super().__init__(message)
19
+
20
+ def __str__(self):
21
+ return (
22
+ f"{self.message} Additional info: {self.extra_info}"
23
+ if self.extra_info
24
+ else self.message
25
+ )
26
+
27
+
28
+ class ServerReadinessError(MATLABProxyError):
29
+ """Exception thrown when MATLAB proxy server fails to become ready"""
30
+
31
+ def __init__(
32
+ self,
33
+ message="MATLAB Proxy Server unavailable: matlab-proxy-app failed to start or has timed out.",
34
+ extra_info=None,
35
+ ):
36
+ self.message = message
37
+ self.extra_info = extra_info
38
+ super().__init__(message)
39
+
40
+ def __str__(self):
41
+ return (
42
+ f"{self.message} Additional info: {self.extra_info}"
43
+ if self.extra_info
44
+ else self.message
45
+ )