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
@@ -1,30 +1,36 @@
1
- # Copyright (c) 2020-2022 The MathWorks, Inc.
2
- """This file contains validators for various runtime artefacts.
3
- A validator is defined as a function which verifies the input and
4
- returns it unchanged if validation passes.
1
+ # Copyright 2020-2025 The MathWorks, Inc.
2
+ """This file contains validators for various runtime artifacts.
3
+ A validator is defined as a function which verifies the input and
4
+ returns it unchanged if validation passes.
5
5
  Returning inputs allows validators to be used inline with the input.
6
6
 
7
- Example:
7
+ Example:
8
8
  Original code: if( input ):
9
9
  With validator: if (valid(input)):
10
10
 
11
11
  Exceptions are thrown to signal failure.
12
12
  """
13
+
13
14
  import errno
15
+ import math
14
16
  import os
15
17
  import socket
16
- import sys
18
+ from pathlib import Path
19
+ from typing import List
17
20
 
18
21
  import matlab_proxy
19
- import pkg_resources
22
+ from matlab_proxy import util
23
+ from matlab_proxy.constants import VERSION_INFO_FILE_NAME
24
+ from matlab_proxy.util import system
20
25
 
21
26
  from . import environment_variables as mwi_env
22
27
  from . import logger as mwi_logger
28
+ from .exceptions import FatalError, MatlabInstallError
23
29
 
24
30
  logger = mwi_logger.get()
25
31
 
26
32
 
27
- def validate_mlm_license_file(nlm_conn_str):
33
+ def validate_mlm_license_file(nlm_connections_str):
28
34
  """Validates and returns input if it passes validation.
29
35
  Throws exception when validation fails.
30
36
  The connection string should be in the form of port@hostname
@@ -39,44 +45,65 @@ def validate_mlm_license_file(nlm_conn_str):
39
45
  Returns:
40
46
  String: Returns the same argument passed to this function if its valid.
41
47
  """
48
+
49
+ """
50
+ nlm_connections_str can either be a valid path to a file or a
51
+ string with comma seperated values, each of the form port@hostname
52
+
53
+ Some valid nlm_connections_str values are:
54
+ 1) port@hostname
55
+ 2) port@hostname,
56
+ 3) port1@hostname1,port2@hostname2
57
+ 4) port1@hostname1,port2@hostname2,
58
+ 5) port1@hostname1,port2@hostname2,port3@hostname3,
59
+ """
42
60
  import re
43
61
 
44
62
  from .exceptions import NetworkLicensingError
45
63
 
46
- if nlm_conn_str is None:
64
+ if not nlm_connections_str:
65
+ # Handles empty strings and None values
47
66
  return None
48
67
 
49
- # TODO: The JS validation of this setting does not allow file path locations
50
- # The JS validation occurs before reaching the set_licensing_info endpoint.
51
-
52
68
  # Regular expression to match port@hostname,
53
69
  # where port is any number and hostname is alphanumeric
54
70
  # regex = Start of Line, Any number of 0-9 digits , @, any number of nonwhite space characters with "- _ ." allowed
55
71
  # "^[0-9]+[@](\w|\_|\-|\.)+$"
56
- # Server triad is of the form : port@host1,port@host2,port@host3
57
- regex = "(^[0-9]+[@](\w|\_|\-|\.)+$)|(^[0-9]+[@](\w|\_|\-|\.)+),([0-9]+[@](\w|\_|\-|\.)+),([0-9]+[@](\w|\_|\-|\.)+$)"
58
- if not re.search(regex, nlm_conn_str):
59
- logger.debug("NLM info is not in the form of port@hostname")
60
- if not os.path.isfile(nlm_conn_str):
61
- logger.debug("NLM info is not a valid path to a license file")
62
- error_message = (
63
- f"MLM_LICENSE_FILE validation failed for {nlm_conn_str}. "
64
- f"If set, the MLM_LICENSE_FILE environment variable must be a string which is either of the form port@hostname"
65
- f" OR path to a valid license file."
66
- )
67
- logger.error(error_message)
68
- raise NetworkLicensingError(error_message)
69
- else:
72
+ # Server triad is of the form : port@host1 or port@host1,port@host2,port@host3
73
+ nlm_connection_str_regex = r"(^[0-9]+[@](\w|\_|\-|\.)+$)"
74
+ error_message = (
75
+ f"MLM_LICENSE_FILE validation failed for {nlm_connections_str}. "
76
+ f"If set, the MLM_LICENSE_FILE environment variable must contain server names (each of the form port@hostname) separated by ':' on unix or ';' on windows(server triads however must be comma seperated)"
77
+ f" OR path to a valid license file."
78
+ )
79
+
80
+ seperator = system.get_mlm_license_file_seperator()
81
+ nlm_connection_strs = re.split(f"{seperator}|,", nlm_connections_str)
82
+
83
+ logger.debug(
84
+ "Validating individual parts of the environment variable MLM_LICENSE_FILE"
85
+ )
86
+ for nlm_connection_str in nlm_connection_strs:
87
+ # Individual parts of the MLM_LICENSE_FILE can either be a valid path to a license file or a server name.
88
+
89
+ if os.path.isfile(nlm_connection_str):
70
90
  logger.info(
71
- f"MLM_LICENSE_FILE with value: {nlm_conn_str} is a path to a file. MATLAB will attempt to use it."
91
+ f"{nlm_connections_str} is a path to a file. MATLAB will attempt to use it."
72
92
  )
73
- else:
74
- logger.info(
75
- f"MLM_LICENSE_FILE with value: {nlm_conn_str} is a license server, MATLAB will attempt to connect to it."
76
- )
77
93
 
78
- # Validation passed
79
- return nlm_conn_str
94
+ else:
95
+ match = re.search(nlm_connection_str_regex, nlm_connection_str)
96
+
97
+ if match:
98
+ logger.debug(f"Successfully validated {nlm_connection_str}")
99
+ else:
100
+ logger.error(f"Failed to validate:{nlm_connection_str}")
101
+ logger.error(
102
+ "NLM_info is not of the form port@hostname or a valid path to a file"
103
+ )
104
+ raise NetworkLicensingError(error_message)
105
+
106
+ return nlm_connections_str
80
107
 
81
108
 
82
109
  def validate_app_port_is_free(port):
@@ -107,10 +134,9 @@ def validate_app_port_is_free(port):
107
134
  return port
108
135
  except socket.error as e:
109
136
  if e.errno == errno.EADDRINUSE:
110
- logger.error(
111
- f"The port {port} is not available. Please set another value for the environment variable {mwi_env.get_env_name_app_port()}"
112
- )
113
- sys.exit(1)
137
+ error_message = f"The port {port} is not available. Please set another value for the environment variable {mwi_env.get_env_name_app_port()}"
138
+ logger.error(error_message)
139
+ raise FatalError(error_message)
114
140
  else:
115
141
  raise e
116
142
 
@@ -134,10 +160,9 @@ def validate_base_url(base_url):
134
160
 
135
161
  else:
136
162
  if not base_url.startswith("/"):
137
- logger.error(
138
- f'The value of environment variable {mwi_env.get_env_name_base_url()} must start with "/" '
139
- )
140
- sys.exit(1)
163
+ error_message = f'The value of environment variable {mwi_env.get_env_name_base_url()} must start with "/" '
164
+ logger.error(error_message)
165
+ raise FatalError(error_message)
141
166
 
142
167
  validated_base_url = base_url[:-1] if base_url.endswith("/") else base_url
143
168
 
@@ -154,30 +179,31 @@ def validate_env_config(config):
154
179
  Returns:
155
180
  Dict: Containing data specific to the environment in which MATLAB proxy is being used in.
156
181
  """
157
- available_configs = __get_configs()
182
+ from matlab_proxy.default_configuration import get_required_config
183
+
184
+ available_configs: dict = __get_configs()
158
185
  config = config.lower()
159
186
 
160
187
  # Check if supplied config is present in the available configs
161
188
  if config in available_configs:
162
- # Check if all keys are present in the supplied config
163
- default_config_keys = available_configs[
164
- matlab_proxy.get_default_config_name()
165
- ].keys()
166
189
  env_config = available_configs[config]
190
+ required_keys = get_required_config()
167
191
 
168
- for key in default_config_keys:
169
- if not key in env_config:
170
- logger.error(f"{key} missing in the provided {config} configuration")
171
- sys.exit(1)
192
+ # Check if all required keys are present in the supplied config
193
+ valid = all(key in env_config for key in required_keys)
194
+ if not valid:
195
+ error_message = (
196
+ f"Required key/s missing in the provided {config} configuration"
197
+ )
198
+ logger.error(error_message)
199
+ raise FatalError(error_message)
172
200
 
173
- logger.debug(f"Successfully validated provided {config} configuration")
201
+ logger.debug("Successfully validated provided %s configuration", config)
174
202
  return env_config
175
203
 
176
- else:
177
- logger.error(
178
- f"{config} is not a valid config. Available configs are : {list(available_configs.keys())}"
179
- )
180
- sys.exit(1)
204
+ error_message = f"{config} is not a valid config. Available configs are : {list(available_configs.keys())}"
205
+ logger.error(error_message)
206
+ raise FatalError(error_message)
181
207
 
182
208
 
183
209
  def __get_configs():
@@ -188,56 +214,173 @@ def __get_configs():
188
214
  Dict: Contains all the values present in 'matlab_web_desktop_configs' entry_point from all the packages
189
215
  installed in the current environment.
190
216
  """
217
+ import importlib.metadata as metadata
218
+
219
+ matlab_proxy_eps = metadata.entry_points(group=matlab_proxy.get_entrypoint_name())
191
220
  configs = {}
192
- for entry_point in pkg_resources.iter_entry_points(
193
- matlab_proxy.get_entrypoint_name()
194
- ):
221
+ for entry_point in matlab_proxy_eps:
195
222
  configs[entry_point.name.lower()] = entry_point.load()
196
223
 
197
224
  return configs
198
225
 
199
226
 
200
- def validate_ssl_cert_file(a_ssl_cert_file):
227
+ def validate_ssl_file(ssl_file, env_name):
201
228
  """Ensures that its a valid readable file"""
202
229
 
203
230
  # Empty strings are valid inputs
204
- if a_ssl_cert_file:
205
- # String is not empty, check to see if the file exists
206
- if not os.path.isfile(a_ssl_cert_file):
207
- logger.error(f"MWI_SSL_CERT_FILE is not a valid file: {a_ssl_cert_file}")
208
- sys.exit(1)
209
-
210
- # string is either empty, or is a valid file on disk
211
- return a_ssl_cert_file
231
+ if not ssl_file:
232
+ return None
212
233
 
234
+ # String is not empty, check to see if the file exists
235
+ if not os.path.isfile(ssl_file):
236
+ error_message = f"{env_name} is not a valid file: {ssl_file}"
237
+ logger.error(error_message)
238
+ raise FatalError(error_message)
213
239
 
214
- def validate_ssl_key_and_cert_file(a_ssl_key_file, a_ssl_cert_file):
215
- """Ensures that its a valid readable file"""
240
+ # string is a valid file on disk
241
+ return ssl_file
216
242
 
217
- if a_ssl_cert_file is None and a_ssl_key_file is None:
218
- # Both values are None, this is acceptable.
219
- return a_ssl_key_file, a_ssl_cert_file
220
243
 
221
- # Implies atleast one value is not None.
244
+ def validate_ssl_key_and_cert_file(ssl_key_file, ssl_cert_file):
245
+ """Validates that provided SSL files are valid readable files"""
246
+ env_name_ssl_cert_file = mwi_env.get_env_name_ssl_cert_file()
247
+ env_name_ssl_key_file = mwi_env.get_env_name_ssl_key_file()
222
248
 
223
- # Cert file is either empty or valid file.
224
- cert_file = validate_ssl_cert_file(a_ssl_cert_file=a_ssl_cert_file)
249
+ if not ssl_cert_file and not ssl_key_file:
250
+ # Both values are falsy, this is acceptable and signify that HTTPS communication is disabled.
251
+ return None, None
225
252
 
226
- if cert_file is None and a_ssl_key_file is not None:
227
- logger.error(f"MWI_SSL_CERT_FILE must be provided to use the MWI_SSL_KEY_FILE")
228
- sys.exit(1)
253
+ # Implies at least one value is not falsy.
229
254
 
230
- if a_ssl_key_file is None and cert_file is not None:
255
+ # Validating cert file- Cert file is either empty or valid file.
256
+ cert_file = validate_ssl_file(
257
+ ssl_file=ssl_cert_file, env_name=env_name_ssl_cert_file
258
+ )
259
+ if not cert_file:
260
+ error_message = f"{env_name_ssl_cert_file} must be provided to use the {env_name_ssl_key_file}"
261
+ logger.error(error_message)
262
+ raise FatalError(error_message)
263
+
264
+ # Validating key file
265
+ key_file = validate_ssl_file(ssl_file=ssl_key_file, env_name=env_name_ssl_key_file)
266
+ if not ssl_key_file:
231
267
  logger.info(
232
- f"MWI_SSL_KEY_FILE is not provided, ensure that your MWI_SSL_CERT_FILE : '{cert_file}' contains a private key"
268
+ f"{env_name_ssl_key_file} is not provided, ensure that your {env_name_ssl_cert_file} : '{cert_file}' contains a private key"
233
269
  )
234
270
 
235
- if a_ssl_key_file:
236
- if not os.path.isfile(a_ssl_key_file):
237
- logger.error(f"MWI_SSL_KEY_FILE is not a valid file: {a_ssl_key_file}")
238
- sys.exit(1)
239
-
240
271
  logger.info(
241
- f"SSL Keys provided were: MWI_SSL_CERT_FILE: {a_ssl_cert_file} & MWI_SSL_KEY_FILE: {a_ssl_key_file}"
272
+ f"SSL Keys provided were: {env_name_ssl_cert_file}: {cert_file} & {env_name_ssl_key_file}: {key_file}"
242
273
  )
243
- return a_ssl_key_file, a_ssl_cert_file
274
+ return key_file, cert_file
275
+
276
+
277
+ def validate_use_existing_licensing(use_existing_license):
278
+ """Returns true if use_existing_license is true
279
+
280
+ Args:
281
+ use_existing_license (str): value from the environment variable MWI_USE_EXISTING_LICENSE
282
+
283
+ Returns:
284
+ bool: if use_existing_license is set to true
285
+ """
286
+ return True if use_existing_license.casefold() == "true" else False
287
+
288
+
289
+ def __validate_if_paths_exist(paths: List[Path]):
290
+ """Validates if paths of directories or files exists on the file system.
291
+
292
+ Args:
293
+ paths ([pathlib.Path]): List of pathlib.Path's to directories or files
294
+
295
+ Raises:
296
+ OSError: When an invalid path is supplied
297
+
298
+ Returns:
299
+ [pathlib.Path] | None: [pathlib.Path] if valid paths are supplied else None
300
+ """
301
+ for path in paths:
302
+ if not util.is_valid_path(path):
303
+ raise OSError(f"Supplied invalid path:{path}")
304
+
305
+ return paths
306
+
307
+
308
+ def validate_matlab_root_path(matlab_root: Path, is_custom_matlab_root: bool):
309
+ """Validate if path supplied is MATLAB_ROOT by checking for the existence of VersionInfo.xml file
310
+ at matlab_root.
311
+
312
+ Args:
313
+ path (pathlib.Path): path to MATLAB root
314
+
315
+ Returns:
316
+ pathlib.Path | None: pathlib.Path if a valid path to MATLAB root is supplied else None
317
+
318
+ Raises:
319
+ MatlabInstallError
320
+ """
321
+
322
+ # When Custom MATLAB root is provided, validate the existence of the
323
+ # VersionInfo.xml file at the specified path else, its optional (for matlab wrapper usecase)
324
+ custom_matlab_root_warn_str = f"Edit the environment variable {mwi_env.get_env_name_custom_matlab_root()} to the correct path, and restart matlab-proxy. "
325
+
326
+ try:
327
+ __validate_if_paths_exist([matlab_root])
328
+ logger.debug(
329
+ f"MATLAB root path: {matlab_root} exists, continuing to verify its validity..."
330
+ )
331
+
332
+ except OSError as exc:
333
+ logger.error(". ".join(exc.args))
334
+ raise MatlabInstallError(
335
+ custom_matlab_root_warn_str if is_custom_matlab_root else ""
336
+ )
337
+
338
+ version_info_file_path = matlab_root / VERSION_INFO_FILE_NAME
339
+
340
+ if not version_info_file_path.is_file():
341
+ warn_str = f"Unable to locate {VERSION_INFO_FILE_NAME} at {matlab_root}"
342
+ # If VersionInfo.xml file is missing when a custom MATLAB root is provided, then
343
+ # raise an error with a detailed message
344
+ if is_custom_matlab_root:
345
+ log_error_string = custom_matlab_root_warn_str + warn_str
346
+ raise MatlabInstallError(log_error_string)
347
+
348
+ else:
349
+ # No VersionInfo.xml file is present and its not a custom MATLAB root, implies a matlab wrapper is
350
+ # being used, so warn the user and return None as MATLAB root could not be determined
351
+ logger.warning(warn_str)
352
+ return None
353
+
354
+ return matlab_root
355
+
356
+
357
+ def validate_idle_timeout(timeout):
358
+ """Validate if IDLE timeout for matlab-proxy
359
+
360
+ Args:
361
+ timeout (None | int): IDLE timeout for shutdown of matlab-proxy.
362
+
363
+ Raises:
364
+ ValueError: If a non-numerical value is supplied other than None.
365
+
366
+ Returns:
367
+ None | int : The timeout value.
368
+ """
369
+ if not timeout:
370
+ return timeout
371
+
372
+ try:
373
+ # Convert timeout to seconds
374
+ timeout = math.ceil(float(timeout) * 60)
375
+
376
+ if timeout <= 0:
377
+ raise ValueError
378
+
379
+ logger.info(f"MATLAB IDLE timeout set to {timeout} seconds")
380
+ return timeout
381
+
382
+ except ValueError:
383
+ logger.warning(
384
+ f"Invalid value supplied for {mwi_env.get_env_name_shutdown_on_idle_timeout()}: {timeout}. Continuing without any IDLE timeout."
385
+ )
386
+ return None
@@ -63,3 +63,12 @@ def get_supported_termination_signals():
63
63
  if is_posix()
64
64
  else [signal.SIGINT, signal.SIGTERM]
65
65
  )
66
+
67
+
68
+ def get_mlm_license_file_seperator():
69
+ """Returns OS specific seperator for MLM_LICENSE_FILE environment variable
70
+
71
+ Returns:
72
+ str: OS specific seperator for MLM_LICENSE_FILE
73
+ """
74
+ return ":" if is_posix() else ";"
@@ -1,9 +1,12 @@
1
- # Copyright 2022 The MathWorks, Inc.
1
+ # Copyright 2022-2024 The MathWorks, Inc.
2
2
  import asyncio
3
3
 
4
4
  from matlab_proxy import util
5
5
  from matlab_proxy.util import mwi
6
6
  from matlab_proxy.util.mwi import environment_variables as mwi_env
7
+ from matlab_proxy.util.mwi.exceptions import (
8
+ UIVisibleFatalError,
9
+ )
7
10
 
8
11
  """ This file contains methods specific to non-posix / windows OS.
9
12
  """
@@ -43,23 +46,28 @@ async def start_matlab(matlab_cmd, matlab_env):
43
46
  Returns:
44
47
  psutil.Process(): The MATLAB process object.
45
48
  """
49
+ import psutil
46
50
 
51
+ # The stdout is used to suppress the MATLAB outputs from being shown in the terminal.
52
+ # We set it to DEVNULL instead of PIPE because PIPE has a limited buffer size and can
53
+ # block the process if the output exceeds the buffer limit.
47
54
  intermediate_proc = await asyncio.create_subprocess_exec(
48
55
  *matlab_cmd,
49
56
  env=matlab_env,
57
+ stdout=asyncio.subprocess.DEVNULL,
50
58
  stderr=asyncio.subprocess.STDOUT,
51
59
  )
52
60
 
53
61
  # In testing mode, the devel.py file is run, which is the fake MATLAB server.
54
62
  # So, there is no need to check for an intermediate process when testing and can return
55
63
  # the same process as a psutil.Process() object.
56
- if mwi_env.is_testing_mode_enabled():
57
- import psutil
58
-
64
+ if mwi_env.is_testing_mode_enabled() or mwi_env.is_development_mode_enabled():
59
65
  proc = psutil.Process(intermediate_proc.pid)
60
66
 
61
67
  return proc
62
68
 
69
+ matlab = None
70
+
63
71
  try:
64
72
  children = util.get_child_processes(intermediate_proc)
65
73
 
@@ -74,8 +82,26 @@ async def start_matlab(matlab_cmd, matlab_env):
74
82
  "MATLAB.exe" == matlab.name()
75
83
  ), "Expecting the child process name to be MATLAB.exe"
76
84
 
77
- except AssertionError as err:
85
+ except (AssertionError, UIVisibleFatalError) as err:
78
86
  raise err
79
87
 
88
+ except psutil.NoSuchProcess:
89
+ # We reach here when the intermediate process launched by matlab-proxy died
90
+ # before we can query for its child processes. Hence, to find the actual MATLAB
91
+ # process, we check all the processes name and parent process id. Ideally, this
92
+ # approach should work in all cases unless MATLAB itself has exited / crashed.
93
+ logger.debug(
94
+ "Intermediate process not found. Querying all process to find MATLAB"
95
+ )
96
+ for process in psutil.process_iter():
97
+ if (
98
+ process.name() == "MATLAB.exe"
99
+ and process.ppid() == intermediate_proc.pid
100
+ ):
101
+ matlab = process
102
+ break
103
+
104
+ assert matlab != None, "MATLAB Process ID not found"
105
+
80
106
  # Return the actual MATLAB processes
81
- return children[0]
107
+ return matlab