matlab-proxy 0.20.0__py3-none-any.whl → 0.21.0__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.

Potentially problematic release.


This version of matlab-proxy might be problematic. Click here for more details.

Files changed (37) hide show
  1. matlab_proxy/gui/asset-manifest.json +6 -6
  2. matlab_proxy/gui/index.html +1 -1
  3. matlab_proxy/gui/static/css/main.6cd0caba.css +13 -0
  4. matlab_proxy/gui/static/css/main.6cd0caba.css.map +1 -0
  5. matlab_proxy/gui/static/js/main.61c661b8.js +3 -0
  6. matlab_proxy/gui/static/js/{main.9c68c75c.js.LICENSE.txt → main.61c661b8.js.LICENSE.txt} +0 -2
  7. matlab_proxy/gui/static/js/main.61c661b8.js.map +1 -0
  8. matlab_proxy/settings.py +1 -1
  9. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/METADATA +2 -1
  10. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/RECORD +32 -15
  11. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/entry_points.txt +1 -0
  12. matlab_proxy-0.21.0.dist-info/top_level.txt +3 -0
  13. matlab_proxy_manager/__init__.py +6 -0
  14. matlab_proxy_manager/lib/__init__.py +1 -0
  15. matlab_proxy_manager/lib/api.py +295 -0
  16. matlab_proxy_manager/storage/__init__.py +1 -0
  17. matlab_proxy_manager/storage/file_repository.py +144 -0
  18. matlab_proxy_manager/storage/interface.py +62 -0
  19. matlab_proxy_manager/storage/server.py +144 -0
  20. matlab_proxy_manager/utils/__init__.py +1 -0
  21. matlab_proxy_manager/utils/auth.py +77 -0
  22. matlab_proxy_manager/utils/constants.py +5 -0
  23. matlab_proxy_manager/utils/environment_variables.py +46 -0
  24. matlab_proxy_manager/utils/helpers.py +284 -0
  25. matlab_proxy_manager/utils/logger.py +73 -0
  26. matlab_proxy_manager/web/__init__.py +1 -0
  27. matlab_proxy_manager/web/app.py +447 -0
  28. matlab_proxy_manager/web/monitor.py +45 -0
  29. matlab_proxy_manager/web/watcher.py +54 -0
  30. tests/unit/test_app.py +1 -1
  31. matlab_proxy/gui/static/css/main.da9c4eb8.css +0 -13
  32. matlab_proxy/gui/static/css/main.da9c4eb8.css.map +0 -1
  33. matlab_proxy/gui/static/js/main.9c68c75c.js +0 -3
  34. matlab_proxy/gui/static/js/main.9c68c75c.js.map +0 -1
  35. matlab_proxy-0.20.0.dist-info/top_level.txt +0 -2
  36. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/LICENSE.md +0 -0
  37. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/WHEEL +0 -0
matlab_proxy/settings.py CHANGED
@@ -493,7 +493,7 @@ def create_xvfb_cmd():
493
493
  str(dpipe[1]),
494
494
  "-screen",
495
495
  "0",
496
- "1600x1200x24",
496
+ "3840x2160x24",
497
497
  "-dpi",
498
498
  "100",
499
499
  # "-ac",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: matlab-proxy
3
- Version: 0.20.0
3
+ Version: 0.21.0
4
4
  Summary: Python® package enables you to launch MATLAB® and access it from a web browser.
5
5
  Home-page: https://github.com/mathworks/matlab-proxy/
6
6
  Author: The MathWorks, Inc.
@@ -22,6 +22,7 @@ Requires-Dist: aiohttp-session[secure]
22
22
  Requires-Dist: importlib-metadata
23
23
  Requires-Dist: importlib-resources
24
24
  Requires-Dist: psutil
25
+ Requires-Dist: watchdog
25
26
  Provides-Extra: dev
26
27
  Requires-Dist: aiohttp-devtools; extra == "dev"
27
28
  Requires-Dist: black; extra == "dev"
@@ -4,21 +4,21 @@ matlab_proxy/app_state.py,sha256=pt6s3Yy6NV6LB-GxT1Y4cOpeDaTFMm_ItN_qPstDTwE,712
4
4
  matlab_proxy/constants.py,sha256=-gEUNjBATe3_4Cd7CN-VhkEn-iMRfiZ-zdDDD6fM_II,1161
5
5
  matlab_proxy/default_configuration.py,sha256=DxQaHzAivzstiPl_nDfxs8SOyP9oaK9v3RP4LtroJl4,843
6
6
  matlab_proxy/devel.py,sha256=nR6XPVBUEdQ-RZGtYvX1YHTp8gj9cuw5Hp8ahasMBc8,14310
7
- matlab_proxy/settings.py,sha256=Jrk6vkYCKt-cFogkkNpfMcEzZcrDgzfeHqSl6ndgkUA,26722
7
+ matlab_proxy/settings.py,sha256=63pyCUvT7WLBRUpUr3F4r4nL13pQ2Hn6-oZODAhuGLo,26722
8
8
  matlab_proxy/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- matlab_proxy/gui/asset-manifest.json,sha256=FIqY-YJXdswnTR3PplkXTm1ovUWpvfsN9EvnweOFJaA,3516
9
+ matlab_proxy/gui/asset-manifest.json,sha256=IarJ9XmKoHJovFfsx1gHk1frgKLGgyCzzYmZNz1DT3A,3516
10
10
  matlab_proxy/gui/favicon.ico,sha256=7w7Ki1uQP2Rgwc64dOV4-NrTu97I3WsZw8OvRSoY1A0,130876
11
- matlab_proxy/gui/index.html,sha256=g044AdWTU8FxXh9klGlneK8YF4swSUr_A8ZNe5mG6kk,637
11
+ matlab_proxy/gui/index.html,sha256=BvGtBVGQxdCjSKGhUFEXZPK3SaFXB7TF3gbFxyx7miQ,637
12
12
  matlab_proxy/gui/manifest.json,sha256=NwDbrALM5auYyj2bbEf4aGwAUDqNl1FzMFQpPiG2Ty4,286
13
13
  matlab_proxy/gui/robots.txt,sha256=kNJLw79pisHhc3OVAimMzKcq3x9WT6sF9IS4xI0crdI,67
14
14
  matlab_proxy/gui/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  matlab_proxy/gui/static/css/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- matlab_proxy/gui/static/css/main.da9c4eb8.css,sha256=etg2mMerqvNiYKBJgve5vBUc-X8ZnnCVnf5gc-DMaPA,274453
17
- matlab_proxy/gui/static/css/main.da9c4eb8.css.map,sha256=favITMsubUPvF7E0vWcdc2RXJ9Pj5ljIWdPuhSBfDVU,351326
16
+ matlab_proxy/gui/static/css/main.6cd0caba.css,sha256=Ks90yx_8kQC9UQXlkvh5ecz2vk2jEN2FKkMCLn0hTb8,269168
17
+ matlab_proxy/gui/static/css/main.6cd0caba.css.map,sha256=-pczDWw0U0T9Yd7pVYDklDlsc6OJqxCYlGmS6qtAhqU,350517
18
18
  matlab_proxy/gui/static/js/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- matlab_proxy/gui/static/js/main.9c68c75c.js,sha256=iJwMC3mRnN8nVfU1tv4nkDQ0P5lp7ziT6Q0mupcchPk,298434
20
- matlab_proxy/gui/static/js/main.9c68c75c.js.LICENSE.txt,sha256=3cj3DrwM51esz1ogL9VVU1ZyXyXJ6u-Ec2CI9CCcI_A,1689
21
- matlab_proxy/gui/static/js/main.9c68c75c.js.map,sha256=VUnC1tai6A0N5CaBcyXMLlcy2eo_30-JcCTnq6uLqhM,955703
19
+ matlab_proxy/gui/static/js/main.61c661b8.js,sha256=w44nS4qbZOF7mS5ASEhmhDPLk1KPDP4f4qF3eNZRzBg,283166
20
+ matlab_proxy/gui/static/js/main.61c661b8.js.LICENSE.txt,sha256=uJRPpXtA1Wzfw2dMAUGOPgwqYk2GvDcIafoGMMNLECQ,1539
21
+ matlab_proxy/gui/static/js/main.61c661b8.js.map,sha256=02MznaDjhNTG_3yo4quVaeyxtnsJVacK_LbN4pTXJdA,911172
22
22
  matlab_proxy/gui/static/media/MATLAB-env-blur.4fc94edbc82d3184e5cb.png,sha256=QpmQTLDvBu2-b7ev83Rvpt0Q72R6wdQGkuJMPPpjv7M,220290
23
23
  matlab_proxy/gui/static/media/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  matlab_proxy/gui/static/media/arrow.0c2968b90bd9a64c8c3f.svg,sha256=XtmvDWzGZnwCZm08TKBnqt5hc1wphJnNupG0Fx_faAY,327
@@ -71,6 +71,23 @@ matlab_proxy/util/mwi/validators.py,sha256=br-22Gyef_vxVSSH2Qkb97Nfph9sfl7fyhQ8-
71
71
  matlab_proxy/util/mwi/embedded_connector/__init__.py,sha256=Vfl2hNC7V1IwoK9_wrwfENs4BC8P-Mvvqh4BNGi2n48,119
72
72
  matlab_proxy/util/mwi/embedded_connector/helpers.py,sha256=aOn-AvcDy6jBQJIffiv_agIa4UVldAIl3--QnDpXWDM,3656
73
73
  matlab_proxy/util/mwi/embedded_connector/request.py,sha256=-IzTDjy3qViHfLJpK3OnFtEyV7dgwJKPQAfav9lqILc,4317
74
+ matlab_proxy_manager/__init__.py,sha256=CMqm2aSYUWo5sxV3vyqWudrQU31muouSqZRDesJNJSA,178
75
+ matlab_proxy_manager/lib/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
76
+ matlab_proxy_manager/lib/api.py,sha256=X-VSbZ41TPosqGODve8PSG6G9dZfqi0kmY_iGWFW3EA,10615
77
+ matlab_proxy_manager/storage/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
78
+ matlab_proxy_manager/storage/file_repository.py,sha256=U4FAw0zFN9z7YNlaMsYZXWm5ccs3rp3bzZL-W2BNhxA,5187
79
+ matlab_proxy_manager/storage/interface.py,sha256=pnRRD0Ku3gzbruAOM3J3NI2Kk8do3-_yRw9Pag1IqnE,1883
80
+ matlab_proxy_manager/storage/server.py,sha256=kQ4jtG2xqCa8c7zqDBFYxOhxMxwZwqTIITBxcJRDcvE,4848
81
+ matlab_proxy_manager/utils/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
82
+ matlab_proxy_manager/utils/auth.py,sha256=60vi16eQ7LWp3I4CNv2easTjObw50irEm518fiMA5YI,2526
83
+ matlab_proxy_manager/utils/constants.py,sha256=pyg-bkk6wWfmy60nvhroZDMZt__FcbZbuvU-b9m2Fkg,163
84
+ matlab_proxy_manager/utils/environment_variables.py,sha256=rbDeWnyJp77Yr6btK3eXKZQ5thwiwhOGZcvDetGPOH8,1436
85
+ matlab_proxy_manager/utils/helpers.py,sha256=5yTRM0NjGUC6lcQ6O8ilDrG2AnICGfvJYdIPho41Egw,9193
86
+ matlab_proxy_manager/utils/logger.py,sha256=GSRGD-yf518o-2b1BxEeJYuNiEz2eEqpl0Solqbwpb4,1869
87
+ matlab_proxy_manager/web/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
88
+ matlab_proxy_manager/web/app.py,sha256=Lu6sl3Mft98oL-Z1NwBdmmG1OvnwYEN31pU2bOdzFi0,16572
89
+ matlab_proxy_manager/web/monitor.py,sha256=Gj0DxwX0c1PEAly5jWmuIGqNJYGDDjTkQIzbVXu4zCQ,1589
90
+ matlab_proxy_manager/web/watcher.py,sha256=aNa_UDwvzaZrIdHyvWGX7dILH199uc6xVH4odrKU5-E,1817
74
91
  tests/integration/__init__.py,sha256=ttzJ8xKWGxOJZz56qOiWOn6sp5LGomkZMn_w4KJLRMU,42
75
92
  tests/integration/integration_tests_with_license/__init__.py,sha256=vVYZCur-QhmIGCxUmn-WZjIywtDQidaLDmlmrRHRlgY,37
76
93
  tests/integration/integration_tests_with_license/conftest.py,sha256=sCaIXB8d4vf05C7JWSVA7g5gnPjbpRq3dftuBpWyp1s,1599
@@ -83,7 +100,7 @@ tests/integration/utils/integration_tests_utils.py,sha256=IbJ9CedFHiz3k85FBY-8Gw
83
100
  tests/integration/utils/licensing.py,sha256=rEBjvMXO8R3mL6KnePu2lojmOsjD4GXl9frf9N0Wacs,4842
84
101
  tests/unit/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
85
102
  tests/unit/conftest.py,sha256=Hfxq3h8IZuLJkRMh5jdEFiq78CIAdKvm-6KryRDZ0FY,1918
86
- tests/unit/test_app.py,sha256=LkIqION-F_4dDpbT0VXzdL_ue-1HGXhPRP9-AprcRKY,38443
103
+ tests/unit/test_app.py,sha256=15kSWsfIEQpGzr6QoDoFpV501SYA5XkszrwDp8pJi74,38442
87
104
  tests/unit/test_app_state.py,sha256=ZtM79HAe5W6pVVWV-NICjDJBlmuxJ4NnZn-urGGtH-E,33429
88
105
  tests/unit/test_constants.py,sha256=2nXxTmDP8utr8krsfZ4c_Bh4_mWPcDO5uI8MXeq4Usg,158
89
106
  tests/unit/test_ddux.py,sha256=a2J2iM8j_nnfJVuMI38p5AjwrRdoMj3N88gFgS2I4hg,713
@@ -104,9 +121,9 @@ tests/unit/util/mwi/embedded_connector/test_helpers.py,sha256=vYTWNUTuDeaygo16JG
104
121
  tests/unit/util/mwi/embedded_connector/test_request.py,sha256=PR-jddnXDEiip-lD7A_QSvRwEkwo3eQ8owZlk-r9vnk,1867
105
122
  tests/utils/__init__.py,sha256=ttzJ8xKWGxOJZz56qOiWOn6sp5LGomkZMn_w4KJLRMU,42
106
123
  tests/utils/logging_util.py,sha256=VBy_NRvwau3C_CVTBjK5RMROrQimnJYHO2U0aKSZiRw,2234
107
- matlab_proxy-0.20.0.dist-info/LICENSE.md,sha256=oF0h3UdSF-rlUiMGYwi086ZHqelzz7yOOk9HFDv9ELo,2344
108
- matlab_proxy-0.20.0.dist-info/METADATA,sha256=xQBYP0FaWmdGuubXOWSYQs-EhU61M1A139fkywkLbvg,10166
109
- matlab_proxy-0.20.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
110
- matlab_proxy-0.20.0.dist-info/entry_points.txt,sha256=DbBLYgnRt8UGiOpd0zHigRTyyMdZYhMdvCvSYP7wPN0,244
111
- matlab_proxy-0.20.0.dist-info/top_level.txt,sha256=9uVTjsUCAS4TwsxueTBxrBg3PdBiTSsYowAkHPv9VY0,19
112
- matlab_proxy-0.20.0.dist-info/RECORD,,
124
+ matlab_proxy-0.21.0.dist-info/LICENSE.md,sha256=oF0h3UdSF-rlUiMGYwi086ZHqelzz7yOOk9HFDv9ELo,2344
125
+ matlab_proxy-0.21.0.dist-info/METADATA,sha256=gnssXFWPF13KwXQjNiEuEaaE1IN-zOHHslCQ8TnnpGU,10190
126
+ matlab_proxy-0.21.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
127
+ matlab_proxy-0.21.0.dist-info/entry_points.txt,sha256=ZAlCUsgKzGcAeQaMZOq31FrTB5tQ8Ypq8Op_8U600-A,305
128
+ matlab_proxy-0.21.0.dist-info/top_level.txt,sha256=KF-347aoRGsfHTpiSqfIPUZ95bzK5-oMIu8S_TUcu-w,40
129
+ matlab_proxy-0.21.0.dist-info/RECORD,,
@@ -1,6 +1,7 @@
1
1
  [console_scripts]
2
2
  matlab-proxy-app = matlab_proxy.app:main
3
3
  matlab-proxy-app-list-servers = matlab_proxy.util.list_servers:print_server_info
4
+ matlab-proxy-manager-app = matlab_proxy_manager.web.app:main
4
5
 
5
6
  [matlab_proxy_configs]
6
7
  default_configuration_matlab_proxy = matlab_proxy.default_configuration:config
@@ -0,0 +1,3 @@
1
+ matlab_proxy
2
+ matlab_proxy_manager
3
+ tests
@@ -0,0 +1,6 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+
3
+
4
+ def get_executable_name() -> str:
5
+ """Fetches and returns matlab proxy manager executable name"""
6
+ return "matlab-proxy-manager-app"
@@ -0,0 +1 @@
1
+ # Copyright 2024 The MathWorks, Inc.
@@ -0,0 +1,295 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ import asyncio
3
+ import os
4
+ import secrets
5
+ import subprocess
6
+ from typing import Optional
7
+
8
+ import matlab_proxy
9
+ import matlab_proxy.util.system as mwi_sys
10
+ from matlab_proxy_manager.storage.file_repository import FileRepository
11
+ from matlab_proxy_manager.storage.server import ServerProcess
12
+ from matlab_proxy_manager.utils import constants, helpers, logger
13
+
14
+ # Used to list all the public-facing APIs exported by this module.
15
+ __all__ = ["shutdown", "start_matlab_proxy_for_kernel", "start_matlab_proxy_for_jsp"]
16
+
17
+ log = logger.get()
18
+ shutdown_lock = asyncio.Lock()
19
+ log = logger.get(init=True)
20
+
21
+
22
+ async def start_matlab_proxy_for_kernel(
23
+ caller_id: str, parent_id: str, is_isolated_matlab: bool
24
+ ):
25
+ """
26
+ Starts a MATLAB proxy server specifically for MATLAB Kernel.
27
+
28
+ This function is a wrapper around the `start_matlab_proxy` function, with mpm_auth_token
29
+ set to None, for starting the MATLAB proxy server via proxy manager.
30
+ """
31
+ return await _start_matlab_proxy(
32
+ caller_id=caller_id, ctx=parent_id, is_isolated_matlab=is_isolated_matlab
33
+ )
34
+
35
+
36
+ async def start_matlab_proxy_for_jsp(
37
+ parent_id: str, is_isolated_matlab: bool, mpm_auth_token: str
38
+ ):
39
+ """
40
+ Starts a MATLAB proxy server specifically for Jupyter Server Proxy (JSP) - Open MATLAB launcher.
41
+
42
+ This function is a wrapper around the `start_matlab_proxy` function, providing
43
+ a more specific context (mpm_auth_token) for starting the MATLAB proxy server via proxy manager.
44
+ """
45
+ return await _start_matlab_proxy(
46
+ caller_id="jsp",
47
+ ctx=parent_id,
48
+ is_isolated_matlab=is_isolated_matlab,
49
+ mpm_auth_token=mpm_auth_token,
50
+ )
51
+
52
+
53
+ async def _start_matlab_proxy(**options) -> Optional[dict]:
54
+ """
55
+ Start a MATLAB proxy server.
56
+
57
+ This function starts a MATLAB proxy server based on the provided context and caller ID.
58
+ It handles the creation of new servers and the reuse of existing ones.
59
+
60
+ Args (keyword arguments):
61
+ - caller_id (str): The identifier for the caller (kernel id for kernels, "jsp" for JSP).
62
+ - ctx (str): The context in which the server is being started (parent pid).
63
+ - is_isolated_matlab (bool, optional): Whether to start an isolated MATLAB proxy instance.
64
+ Defaults to False.
65
+ - mpm_auth_token (str, optional): The MATLAB proxy manager token. If not provided,
66
+ a new token is generated. Defaults to None.
67
+
68
+ Returns:
69
+ ServerProcess: The process representing the MATLAB proxy server.
70
+
71
+ Raises:
72
+ ValueError: If `caller_id` is "default" and `is_isolated_matlab` is True.
73
+ """
74
+ caller_id: str = options.get("caller_id")
75
+ ctx: str = options.get("ctx")
76
+ is_isolated_matlab: bool = options.get("is_isolated_matlab", False)
77
+ mpm_auth_token: Optional[str] = options.get("mpm_auth_token", None)
78
+
79
+ if is_isolated_matlab and caller_id == "default":
80
+ raise ValueError(
81
+ "Caller id cannot be default when isolated_matlab is set to true"
82
+ )
83
+
84
+ mpm_auth_token = mpm_auth_token or secrets.token_hex(32)
85
+
86
+ # Cleanup stale entries before starting new instance of matlab proxy server
87
+ helpers._are_orphaned_servers_deleted(ctx)
88
+
89
+ ident = caller_id if is_isolated_matlab else "default"
90
+ key = f"{ctx}_{ident}"
91
+ log.debug("Starting matlab proxy using %s, %s, %s", ctx, ident, is_isolated_matlab)
92
+
93
+ data_dir = helpers.create_and_get_proxy_manager_data_dir()
94
+ server_process = ServerProcess.find_existing_server(data_dir, key)
95
+
96
+ if server_process:
97
+ log.debug("Found existing server for aliasing")
98
+
99
+ # Create a backend file for this caller for reference tracking
100
+ helpers.create_state_file(data_dir, server_process, f"{ctx}_{caller_id}")
101
+
102
+ # Create a new matlab proxy server
103
+ else:
104
+ server_process: ServerProcess | None = (
105
+ await _start_subprocess_and_check_for_readiness(
106
+ ident, ctx, key, is_isolated_matlab, mpm_auth_token
107
+ )
108
+ )
109
+
110
+ # Store the newly created server into filesystem
111
+ if server_process:
112
+ helpers.create_state_file(data_dir, server_process, f"{ctx}_{caller_id}")
113
+
114
+ return server_process.as_dict() if server_process else None
115
+
116
+
117
+ async def _start_subprocess_and_check_for_readiness(
118
+ server_id: str, ctx: str, key: str, isolated: bool, mpm_auth_token: str
119
+ ) -> Optional[ServerProcess]:
120
+ """
121
+ Starts a MATLAB proxy server.
122
+
123
+ This function performs the following steps:
124
+ 1. Prepares the command and environment variables required to start the MATLAB proxy server.
125
+ 2. Initializes the MATLAB proxy process.
126
+ 3. Checks if the MATLAB proxy server is ready.
127
+ 4. Creates and returns a ServerProcess instance if the server is ready.
128
+
129
+ Returns:
130
+ Optional[ServerProcess]: An instance of ServerProcess if the server is successfully started,
131
+ otherwise None.
132
+ """
133
+ log.debug("Starting new matlab proxy server")
134
+
135
+ # Prepare matlab proxy command and required environment variables
136
+ matlab_proxy_cmd, matlab_proxy_env = _prepare_cmd_and_env_for_matlab_proxy()
137
+
138
+ # Start the matlab proxy process
139
+ process_id, url, mwi_base_url = await _start_subprocess(
140
+ matlab_proxy_cmd, matlab_proxy_env, server_id
141
+ )
142
+
143
+ server_process = None
144
+
145
+ # Check for the matlab proxy server readiness
146
+ if helpers.is_server_ready(f"{url}{mwi_base_url}"):
147
+ log.debug("Matlab proxy process info: %s, %s", url, mwi_base_url)
148
+ server_process = ServerProcess(
149
+ server_url=url,
150
+ mwi_base_url=mwi_base_url,
151
+ headers=helpers.convert_mwi_env_vars_to_header_format(
152
+ matlab_proxy_env, "MWI"
153
+ ),
154
+ pid=process_id,
155
+ parent_pid=ctx,
156
+ id=key,
157
+ type="named" if isolated else "shared",
158
+ mpm_auth_token=mpm_auth_token,
159
+ )
160
+ else:
161
+ log.error("Could not start matlab proxy")
162
+
163
+ return server_process
164
+
165
+
166
+ def _prepare_cmd_and_env_for_matlab_proxy():
167
+ """
168
+ Prepare the command and environment variables for starting the MATLAB proxy.
169
+
170
+ Returns:
171
+ Tuple: A tuple containing the MATLAB proxy command and environment variables.
172
+ """
173
+ from jupyter_matlab_proxy import config
174
+
175
+ # Get the command to start matlab-proxy
176
+ matlab_proxy_cmd: list = [
177
+ matlab_proxy.get_executable_name(),
178
+ "--config",
179
+ config.get("extension_name"),
180
+ ]
181
+
182
+ input_env: dict = {
183
+ "MWI_AUTH_TOKEN": secrets.token_urlsafe(32),
184
+ }
185
+
186
+ matlab_proxy_env: dict = os.environ.copy()
187
+ matlab_proxy_env.update(input_env)
188
+
189
+ return matlab_proxy_cmd, matlab_proxy_env
190
+
191
+
192
+ async def _start_subprocess(cmd, env, server_id) -> Optional[int]:
193
+ """
194
+ Initializes and starts a subprocess using the specified command and provided environment.
195
+
196
+ Returns:
197
+ Optional[int]: The process ID if the process is successfully created, otherwise None.
198
+ """
199
+ process = None
200
+ mwi_base_url: str = f"{constants.MWI_BASE_URL_PREFIX}{server_id}"
201
+
202
+ # Get a free port, closer to starting the matlab proxy appx
203
+ port: str = helpers.find_free_port()
204
+ env.update(
205
+ {
206
+ "MWI_APP_PORT": port,
207
+ "MWI_BASE_URL": mwi_base_url,
208
+ }
209
+ )
210
+
211
+ # Using loopback address so that DNS resolution doesn't add latency in Windows
212
+ url: str = f"http://127.0.0.1:{port}"
213
+
214
+ if mwi_sys.is_posix():
215
+ process = await asyncio.create_subprocess_exec(
216
+ *cmd,
217
+ env=env,
218
+ )
219
+ log.debug("Started matlab proxy subprocess for posix")
220
+ else:
221
+ process = subprocess.Popen(
222
+ cmd,
223
+ env=env,
224
+ )
225
+ log.debug("Started matlab proxy subprocess for windows")
226
+
227
+ if not process:
228
+ log.error("Matlab proxy process not created %d", process.returncode)
229
+ return None
230
+
231
+ process_pid = process.pid
232
+ log.debug("MATLAB proxy info: pid = %s, rc = %s", process_pid, process.returncode)
233
+ return process_pid, url, mwi_base_url
234
+
235
+
236
+ async def shutdown(parent_pid: str, caller_id: str, mpm_auth_token: str):
237
+ """
238
+ Shutdown the MATLAB proxy server if the provided authentication token is valid.
239
+
240
+ This function attempts to shut down the MATLAB proxy server identified by the
241
+ given context and ID, provided the correct authentication token is supplied.
242
+ It ensures that the shutdown process is thread-safe using an asyncio lock.
243
+
244
+ Args:
245
+ parent_pid (str): The context identifier for the server.
246
+ caller_id (str): The unique identifier for the server.
247
+ mpm_auth_token (str): The authentication token for proxy manager and client communication.
248
+
249
+ Returns:
250
+ Optional[None]: Returns None if the shutdown process is successful or if
251
+ required arguments are missing.
252
+
253
+ Raises:
254
+ FileNotFoundError: If the state file for the server does not exist.
255
+ ValueError: If the authentication token is invalid.
256
+ Exception: If any other error occurs during the shutdown process.
257
+ """
258
+ if not parent_pid or not caller_id or not mpm_auth_token:
259
+ log.debug(
260
+ "Required arguments (parent_pid | caller_id | mpm_auth_token) for shutdown missing"
261
+ )
262
+ return
263
+
264
+ try:
265
+ data_dir = helpers.create_and_get_proxy_manager_data_dir()
266
+ storage = FileRepository(data_dir)
267
+ filename = f"{parent_pid}_{caller_id}"
268
+ full_file_path, server = storage.get(filename)
269
+
270
+ if not server:
271
+ log.debug("State file for this server not found, filename: %s", filename)
272
+ return
273
+
274
+ if mpm_auth_token != server.mpm_auth_token:
275
+ raise ValueError("Invalid authentication token")
276
+
277
+ # Using asyncio lock to ensure thread-safe shutdown: clicking shutdown
278
+ # all on Kernel UI sends shutdown request in parallel which could lead
279
+ # to a scenario where the kernels' shutdown just cleans the files from
280
+ # filesystem and doesn't shut down the backend matlab proxy server.
281
+ async with shutdown_lock:
282
+ if helpers.is_only_reference(full_file_path):
283
+ server.shutdown()
284
+
285
+ # Delete the file for this server
286
+ storage.delete(f"{filename}.info")
287
+ except FileNotFoundError as e:
288
+ log.error("State file for server %s not found: %s", filename, e)
289
+ return
290
+ except ValueError as e:
291
+ log.error("Authentication error for server %s: %s", filename, e)
292
+ return
293
+ except Exception as e:
294
+ log.error("Error during shutdown of server %s: %s", filename, e)
295
+ raise
@@ -0,0 +1 @@
1
+ # Copyright 2024 The MathWorks, Inc.
@@ -0,0 +1,144 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ import glob
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from matlab_proxy_manager.utils import logger
9
+
10
+ from .interface import IRepository
11
+
12
+ log = logger.get()
13
+
14
+
15
+ class FileRepository(IRepository):
16
+ """
17
+ A repository for managing MATLAB proxy server processes using the file system.
18
+ """
19
+
20
+ def __init__(self, data_dir) -> None:
21
+ super().__init__()
22
+ self.data_dir = data_dir
23
+ self.encoding = "utf-8"
24
+
25
+ def get_all(self):
26
+ """Retrieves all server processes from the repository.
27
+
28
+ Returns:
29
+ A dictionary mapping file paths to server process instances.
30
+ """
31
+ from matlab_proxy_manager.storage.server import ServerProcess
32
+
33
+ servers = {}
34
+
35
+ # Read all the files in data_dir
36
+ all_files = glob.glob(f"{self.data_dir}/**/*.info", recursive=True)
37
+
38
+ for file in all_files:
39
+ try:
40
+ with open(file, "r", encoding=self.encoding) as f:
41
+ data = f.read().strip()
42
+
43
+ # Convert the content of each file to ServerProcess
44
+ if data:
45
+ server_process = ServerProcess.instantiate_from_string(data)
46
+ servers[file] = server_process
47
+ except Exception as ex:
48
+ log.debug("ServerProcess instantiation failed for %s: %s", file, ex)
49
+ return servers
50
+
51
+ def get(self, name) -> tuple:
52
+ """
53
+ Retrieves a server process from the repository by its filename.
54
+
55
+ Args:
56
+ name (str): The name of the server process file.
57
+
58
+ Returns:
59
+ Tuple[Optional[str], Optional[ServerProcess]]: A tuple containing the file path
60
+ and the server process instance.
61
+ """
62
+ from matlab_proxy_manager.storage.server import ServerProcess
63
+
64
+ server_process = None
65
+ full_file_path: Optional[str] = None
66
+ current_files = glob.glob(f"{self.data_dir}/**/{name}.info", recursive=True)
67
+ if current_files:
68
+ full_file_path = current_files[0]
69
+ with open(full_file_path, "r", encoding=self.encoding) as f:
70
+ try:
71
+ data = f.read().strip()
72
+ if data:
73
+ server_process = ServerProcess.instantiate_from_string(data)
74
+ except Exception as ex:
75
+ log.debug(
76
+ "ServerProcess instantiation failed for %s: %s",
77
+ full_file_path,
78
+ ex,
79
+ )
80
+
81
+ return full_file_path, server_process
82
+
83
+ def add(self, server, filename: str) -> None:
84
+ """
85
+ Adds a server process to the repository.
86
+ Creates a directory like <ctx>_default|<kernel_id> and then creates a file as
87
+ default|<kernel_id>.info in that dir
88
+
89
+ Args:
90
+ server (ServerProcess): The server process instance to add.
91
+ filename (str): The filename to associate with the server process.
92
+ """
93
+ # Creates a child dir under the data_dir
94
+ server_dir = Path(f"{self.data_dir}", f"{server.id}")
95
+ Path.mkdir(server_dir, parents=True, exist_ok=True)
96
+ server_dict = {}
97
+
98
+ server_file = Path(server_dir, f"{filename}.info")
99
+ with open(server_file, "w", encoding=self.encoding) as f:
100
+ server_dict[server.id] = server.as_dict()
101
+ file_content = json.dumps(server_dict)
102
+ f.write(file_content)
103
+
104
+ def delete(self, filename: str) -> None:
105
+ """
106
+ Deletes a server process from the repository by its filename.
107
+
108
+ Args:
109
+ filename (str): The filename associated with the server process to delete.
110
+ """
111
+ # <path to proxy manager dir>/<parent_pid>_<name>/<name>.info
112
+ full_file_path, parent_dir = FileRepository._find_file_and_get_parent(
113
+ self.data_dir, filename
114
+ )
115
+ if full_file_path:
116
+ Path(full_file_path).unlink(missing_ok=True)
117
+ log.debug("Deleted file: %s", filename)
118
+
119
+ # delete the sub-directory (<parent_pid>_<id>) only if it is empty
120
+ if parent_dir and not len(os.listdir(parent_dir)):
121
+ os.rmdir(parent_dir)
122
+ log.debug("Deleted dir: %s", parent_dir)
123
+
124
+ @staticmethod
125
+ def _find_file_and_get_parent(data_dir: str, filename: str):
126
+ """
127
+ Finds the file and its parent directory in the given data directory.
128
+
129
+ Args:
130
+ data_dir (str): The base directory to search within.
131
+ filename (str): The filename to search for.
132
+
133
+ Returns:
134
+ Tuple[Optional[str], Optional[str]]: A tuple containing the full file path and the parent directory.
135
+ """
136
+ for dirpath, _, filenames in os.walk(data_dir):
137
+ # Check if target file is in the current directory's files
138
+ if filename in filenames:
139
+ full_path = os.path.join(dirpath, filename)
140
+ parent_dir = os.path.dirname(full_path)
141
+ return full_path, parent_dir
142
+
143
+ # Return None if the file was not found
144
+ return None, None
@@ -0,0 +1,62 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ from typing import Protocol
3
+
4
+
5
+ class IRepository(Protocol):
6
+ """
7
+ Protocol for a repository that manages MATLAB proxy server processes.
8
+ This protocol defines the required methods for adding, retrieving, and deleting
9
+ server process instances to the storage system (files).
10
+ """
11
+
12
+ def add(self, server, filename: str):
13
+ """
14
+ Adds a server process to the repository.
15
+
16
+ Args:
17
+ server (server.ServerProcess): The server process instance to add.
18
+ filename (str): The filename to associate with the server process.
19
+
20
+ Raises:
21
+ NotImplementedError
22
+ """
23
+ raise NotImplementedError("add not implemented")
24
+
25
+ def get(self, name: str) -> tuple:
26
+ """
27
+ Retrieves a server process from the repository by its filename.
28
+
29
+ Args:
30
+ filename (str): The filename associated with the server process.
31
+
32
+ Returns:
33
+ tuple (str, server.ServerProcess): Full file path and the retrieved server process instance.
34
+
35
+ Raises:
36
+ NotImplementedError
37
+ """
38
+ raise NotImplementedError("get not implemented")
39
+
40
+ def get_all(self):
41
+ """
42
+ Retrieves all server processes from the repository.
43
+
44
+ Returns:
45
+ Dict[str, server.ServerProcess]: Dict with filename as key and corresponding server process as value.
46
+
47
+ Raises:
48
+ NotImplementedError
49
+ """
50
+ raise NotImplementedError("get_all not implemented")
51
+
52
+ def delete(self, filename: str) -> None:
53
+ """
54
+ Deletes a server process from the repository by its filename.
55
+
56
+ Args:
57
+ filename (str): The filename associated with the server process to delete.
58
+
59
+ Raises:
60
+ NotImplementedError
61
+ """
62
+ raise NotImplementedError("delete not implemented")