experimaestro 1.5.1__py3-none-any.whl → 2.0.0a8__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 experimaestro might be problematic. Click here for more details.

Files changed (118) hide show
  1. experimaestro/__init__.py +14 -4
  2. experimaestro/__main__.py +3 -423
  3. experimaestro/annotations.py +14 -4
  4. experimaestro/cli/__init__.py +311 -0
  5. experimaestro/{filter.py → cli/filter.py} +23 -9
  6. experimaestro/cli/jobs.py +268 -0
  7. experimaestro/cli/progress.py +269 -0
  8. experimaestro/click.py +0 -35
  9. experimaestro/commandline.py +3 -7
  10. experimaestro/connectors/__init__.py +29 -14
  11. experimaestro/connectors/local.py +19 -10
  12. experimaestro/connectors/ssh.py +27 -8
  13. experimaestro/core/arguments.py +45 -3
  14. experimaestro/core/callbacks.py +52 -0
  15. experimaestro/core/context.py +8 -9
  16. experimaestro/core/identifier.py +310 -0
  17. experimaestro/core/objects/__init__.py +44 -0
  18. experimaestro/core/{objects.py → objects/config.py} +399 -772
  19. experimaestro/core/objects/config_utils.py +58 -0
  20. experimaestro/core/objects/config_walk.py +151 -0
  21. experimaestro/core/objects.pyi +15 -45
  22. experimaestro/core/serialization.py +63 -9
  23. experimaestro/core/serializers.py +1 -8
  24. experimaestro/core/types.py +104 -66
  25. experimaestro/experiments/cli.py +154 -72
  26. experimaestro/experiments/configuration.py +10 -1
  27. experimaestro/generators.py +6 -1
  28. experimaestro/ipc.py +4 -1
  29. experimaestro/launcherfinder/__init__.py +1 -1
  30. experimaestro/launcherfinder/base.py +2 -18
  31. experimaestro/launcherfinder/parser.py +8 -3
  32. experimaestro/launcherfinder/registry.py +52 -140
  33. experimaestro/launcherfinder/specs.py +49 -10
  34. experimaestro/launchers/direct.py +0 -47
  35. experimaestro/launchers/slurm/base.py +54 -14
  36. experimaestro/mkdocs/__init__.py +1 -1
  37. experimaestro/mkdocs/base.py +6 -8
  38. experimaestro/notifications.py +38 -12
  39. experimaestro/progress.py +406 -0
  40. experimaestro/run.py +24 -3
  41. experimaestro/scheduler/__init__.py +18 -1
  42. experimaestro/scheduler/base.py +108 -808
  43. experimaestro/scheduler/dynamic_outputs.py +184 -0
  44. experimaestro/scheduler/experiment.py +387 -0
  45. experimaestro/scheduler/jobs.py +475 -0
  46. experimaestro/scheduler/signal_handler.py +32 -0
  47. experimaestro/scheduler/state.py +75 -0
  48. experimaestro/scheduler/workspace.py +27 -8
  49. experimaestro/scriptbuilder.py +18 -3
  50. experimaestro/server/__init__.py +36 -5
  51. experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  52. experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  53. experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  54. experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  55. experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  56. experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  57. experimaestro/server/data/index.css +5187 -5068
  58. experimaestro/server/data/index.css.map +1 -1
  59. experimaestro/server/data/index.js +68887 -68064
  60. experimaestro/server/data/index.js.map +1 -1
  61. experimaestro/settings.py +45 -5
  62. experimaestro/sphinx/__init__.py +7 -17
  63. experimaestro/taskglobals.py +7 -2
  64. experimaestro/tests/core/__init__.py +0 -0
  65. experimaestro/tests/core/test_generics.py +206 -0
  66. experimaestro/tests/definitions_types.py +5 -3
  67. experimaestro/tests/launchers/bin/sbatch +34 -7
  68. experimaestro/tests/launchers/bin/srun +5 -0
  69. experimaestro/tests/launchers/common.py +17 -5
  70. experimaestro/tests/launchers/config_slurm/launchers.py +25 -0
  71. experimaestro/tests/restart.py +10 -5
  72. experimaestro/tests/tasks/all.py +23 -10
  73. experimaestro/tests/tasks/foreign.py +2 -4
  74. experimaestro/tests/test_checkers.py +2 -2
  75. experimaestro/tests/test_dependencies.py +11 -17
  76. experimaestro/tests/test_experiment.py +73 -0
  77. experimaestro/tests/test_file_progress.py +425 -0
  78. experimaestro/tests/test_file_progress_integration.py +477 -0
  79. experimaestro/tests/test_findlauncher.py +12 -5
  80. experimaestro/tests/test_forward.py +5 -5
  81. experimaestro/tests/test_generators.py +93 -0
  82. experimaestro/tests/test_identifier.py +182 -158
  83. experimaestro/tests/test_instance.py +19 -27
  84. experimaestro/tests/test_objects.py +13 -20
  85. experimaestro/tests/test_outputs.py +6 -6
  86. experimaestro/tests/test_param.py +68 -30
  87. experimaestro/tests/test_progress.py +4 -4
  88. experimaestro/tests/test_serializers.py +24 -64
  89. experimaestro/tests/test_ssh.py +7 -0
  90. experimaestro/tests/test_tags.py +50 -21
  91. experimaestro/tests/test_tasks.py +42 -51
  92. experimaestro/tests/test_tokens.py +11 -8
  93. experimaestro/tests/test_types.py +24 -21
  94. experimaestro/tests/test_validation.py +67 -110
  95. experimaestro/tests/token_reschedule.py +1 -1
  96. experimaestro/tokens.py +24 -13
  97. experimaestro/tools/diff.py +8 -1
  98. experimaestro/typingutils.py +20 -11
  99. experimaestro/utils/asyncio.py +6 -2
  100. experimaestro/utils/multiprocessing.py +44 -0
  101. experimaestro/utils/resources.py +11 -3
  102. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/METADATA +28 -36
  103. experimaestro-2.0.0a8.dist-info/RECORD +166 -0
  104. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/WHEEL +1 -1
  105. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info}/entry_points.txt +0 -4
  106. experimaestro/launchers/slurm/cli.py +0 -29
  107. experimaestro/launchers/slurm/configuration.py +0 -597
  108. experimaestro/scheduler/environment.py +0 -94
  109. experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
  110. experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
  111. experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
  112. experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
  113. experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
  114. experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
  115. experimaestro/tests/launchers/config_slurm/launchers.yaml +0 -134
  116. experimaestro/utils/yaml.py +0 -202
  117. experimaestro-1.5.1.dist-info/RECORD +0 -148
  118. {experimaestro-1.5.1.dist-info → experimaestro-2.0.0a8.dist-info/licenses}/LICENSE +0 -0
@@ -1,27 +1,29 @@
1
+ import datetime
2
+ import importlib
1
3
  import inspect
2
- import itertools
3
4
  import json
4
5
  import logging
5
6
  import sys
6
7
  from pathlib import Path
7
- from typing import Any, List, Optional, Protocol, Tuple, Dict
8
+ from typing import Any, List, Optional, Protocol, Tuple
8
9
 
9
10
  import click
10
11
  import omegaconf
11
12
  import yaml
12
- from experimaestro import LauncherRegistry, RunMode, experiment
13
- from experimaestro.experiments.configuration import ConfigurationBase
14
- from experimaestro.exceptions import HandledException
15
- from experimaestro.settings import get_settings, get_workspace
16
13
  from omegaconf import OmegaConf, SCMode
17
14
  from termcolor import cprint
18
15
 
16
+ from experimaestro import LauncherRegistry, RunMode, experiment
17
+ from experimaestro.exceptions import HandledException
18
+ from experimaestro.experiments.configuration import ConfigurationBase
19
+ from experimaestro.settings import find_workspace
20
+
19
21
 
20
22
  class ExperimentHelper:
21
23
  """Helper for experiments"""
22
24
 
23
- # The experiment
24
25
  xp: experiment
26
+ """The experiment object"""
25
27
 
26
28
  #: Run function
27
29
  callable: "ExperimentCallable"
@@ -54,18 +56,40 @@ class ExperimentCallable(Protocol):
54
56
  ...
55
57
 
56
58
 
57
- def load(yaml_file: Path):
58
- """Loads a YAML file, and parents one if they exist"""
59
- if not yaml_file.exists() and yaml_file.suffix != ".yaml":
60
- yaml_file = yaml_file.with_suffix(".yaml")
59
+ class ConfigurationLoader:
60
+ def __init__(self):
61
+ self.yamls = []
62
+ self.python_path = set()
63
+ self.yaml_module_file: None | Path = None
64
+
65
+ def load(self, yaml_file: Path):
66
+ """Loads a YAML file, and parents one if they exist"""
67
+ if not yaml_file.exists() and yaml_file.suffix != ".yaml":
68
+ yaml_file = yaml_file.with_suffix(".yaml")
69
+
70
+ with yaml_file.open("rt") as fp:
71
+ _data = yaml.full_load(fp)
72
+
73
+ if "file" in _data:
74
+ path = Path(_data["file"])
75
+ if not path.is_absolute():
76
+ _data["file"] = str((yaml_file.parent / path).resolve())
61
77
 
62
- with yaml_file.open("rt") as fp:
63
- _data = yaml.full_load(fp)
64
- data = [_data]
65
- if parent := _data.get("parent", None):
66
- data.extend(load(yaml_file.parent / parent))
78
+ if "module" in _data:
79
+ # Keeps track of the YAML file where the module was defined
80
+ self.yaml_module_file = yaml_file
67
81
 
68
- return data[::-1]
82
+ if parent := _data.get("parent", None):
83
+ self.load(yaml_file.parent / parent)
84
+
85
+ self.yamls.append(_data)
86
+
87
+ for path in _data.get("pythonpath", []):
88
+ path = Path(path)
89
+ if path.is_absolute():
90
+ self.python_path.add(path.resolve())
91
+ else:
92
+ self.python_path.add((yaml_file.parent / path).resolve())
69
93
 
70
94
 
71
95
  @click.option("--debug", is_flag=True, help="Print debug information")
@@ -103,13 +127,25 @@ def load(yaml_file: Path):
103
127
  help="Port for monitoring (can be defined in the settings.yaml file)",
104
128
  )
105
129
  @click.option(
106
- "--file", "xp_file", help="The file containing the main experimental code"
130
+ "--file",
131
+ "xp_file",
132
+ type=Path,
133
+ help="The file containing the main experimental code",
134
+ )
135
+ @click.option(
136
+ "--module-name", "module_name", help="Module containing the experimental code"
137
+ )
138
+ @click.option(
139
+ "--workspace",
140
+ type=str,
141
+ default=None,
142
+ help="Workspace ID (reads from settings.yaml in experimaestro config)",
107
143
  )
108
144
  @click.option(
109
145
  "--workdir",
110
146
  type=str,
111
147
  default=None,
112
- help="Working directory - if None, uses the default XPM " "working directory",
148
+ help="Working environment",
113
149
  )
114
150
  @click.option("--conf", "-c", "extra_conf", type=str, multiple=True)
115
151
  @click.option(
@@ -128,36 +164,57 @@ def experiments_cli( # noqa: C901
128
164
  port: int,
129
165
  xpm_config_dir: Path,
130
166
  workdir: Optional[Path],
167
+ workspace: Optional[str],
131
168
  env: List[Tuple[str, str]],
132
169
  run_mode: RunMode,
133
170
  extra_conf: List[str],
134
171
  pre_yaml: List[str],
135
172
  post_yaml: List[str],
173
+ module_name: Optional[str],
136
174
  args: List[str],
137
175
  show: bool,
138
176
  debug: bool,
139
177
  ):
140
178
  """Run an experiment"""
179
+
141
180
  # --- Set the logger
142
181
  logging.getLogger().setLevel(logging.DEBUG if debug else logging.INFO)
143
182
  logging.getLogger("xpm.hash").setLevel(logging.INFO)
144
183
 
145
184
  # --- Loads the YAML
146
- yamls = []
185
+ conf_loader = ConfigurationLoader()
147
186
  for y in pre_yaml:
148
- yamls.extend(load(Path(y)))
149
- yamls.extend(load(Path(yaml_file)))
187
+ conf_loader.load(Path(y))
188
+ conf_loader.load(Path(yaml_file))
150
189
  for y in post_yaml:
151
- yamls.extend(load(Path(y)))
190
+ conf_loader.load(Path(y))
191
+
192
+ # --- Merge the YAMLs
193
+ configuration = OmegaConf.merge(*conf_loader.yamls)
194
+ if extra_conf:
195
+ configuration.merge_with(OmegaConf.from_dotlist(extra_conf))
152
196
 
153
197
  # --- Get the XP file
154
- if xp_file is None:
155
- for data in yamls[::-1]:
156
- if xp_file := data.get("file", None):
157
- break
198
+ python_path = list(conf_loader.python_path)
199
+ if module_name is None:
200
+ module_name = configuration.get("module", None)
158
201
 
159
- if xp_file is None:
160
- raise ValueError("No experiment file given")
202
+ if xp_file is None:
203
+ xp_file = configuration.get("file", None)
204
+ if xp_file:
205
+ assert (
206
+ not module_name
207
+ ), "Module name and experiment file are mutually exclusive options"
208
+ xp_file = Path(xp_file)
209
+ if not python_path:
210
+ python_path.append(xp_file.parent.absolute())
211
+ logging.info(
212
+ "Using python path: %s", ", ".join(str(s) for s in python_path)
213
+ )
214
+
215
+ assert (
216
+ module_name or xp_file
217
+ ), "Either the module name or experiment file should be given"
161
218
 
162
219
  # --- Set some options
163
220
 
@@ -165,91 +222,116 @@ def experiments_cli( # noqa: C901
165
222
  assert xpm_config_dir.is_dir()
166
223
  LauncherRegistry.set_config_dir(xpm_config_dir)
167
224
 
168
- # --- Loads the XP file
169
- xp_file = Path(xp_file)
170
- if not xp_file.exists() and xp_file.suffix != ".py":
171
- xp_file = xp_file.with_suffix(".py")
172
- xp_file = Path(yaml_file).parent / xp_file
225
+ # --- Finds the "run" function
173
226
 
174
- with open(xp_file, "r") as f:
175
- source = f.read()
176
- if sys.version_info < (3, 9):
177
- the__file__ = str(xp_file)
178
- else:
179
- the__file__ = str(xp_file.absolute())
227
+ # Modifies the Python path
228
+ for path in python_path:
229
+ sys.path.append(str(path))
180
230
 
181
- code = compile(source, filename=the__file__, mode="exec")
182
- _locals: Dict[str, Any] = {}
231
+ # --- Adds automatically the experiment module if not found
232
+ if module_name and conf_loader.yaml_module_file:
233
+ try:
234
+ importlib.import_module(module_name)
235
+ except ModuleNotFoundError:
236
+ # Try to setup a path
237
+ path = conf_loader.yaml_module_file.resolve()
238
+ for _ in range(len(module_name.split("."))):
239
+ path = path.parent
240
+
241
+ logging.info("Appending %s to python path", path)
242
+ sys.path.append(str(path))
243
+ python_path.append(path)
244
+
245
+ if xp_file:
246
+ if not xp_file.exists() and xp_file.suffix != ".py":
247
+ xp_file = xp_file.with_suffix(".py")
248
+ xp_file: Path = Path(yaml_file).parent / xp_file
249
+ module_name = xp_file.with_suffix("").name
250
+ spec = importlib.util.spec_from_file_location(
251
+ module_name, str(xp_file.absolute())
252
+ )
253
+ mod = importlib.util.module_from_spec(spec)
254
+ spec.loader.exec_module(mod)
255
+ else:
256
+ # Module
257
+ try:
258
+ mod = importlib.import_module(module_name)
259
+ except ModuleNotFoundError as e:
260
+ logging.error("Module not found: %s with python path %s", e, sys.path)
261
+ raise
183
262
 
184
- sys.path.append(str(xp_file.parent.absolute()))
185
- try:
186
- exec(code, _locals, _locals)
187
- finally:
188
- sys.path.pop()
263
+ helper = getattr(mod, "run", None)
189
264
 
190
265
  # --- ... and runs it
191
- helper = _locals.get("run", None)
192
266
  if helper is None:
193
- raise ValueError(f"Could not find run function in {the__file__}")
267
+ raise click.ClickException(
268
+ f"Could not find run function in {xp_file if xp_file else module_name}"
269
+ )
194
270
 
195
271
  if not isinstance(helper, ExperimentHelper):
196
272
  helper = ExperimentHelper(helper)
197
273
 
198
274
  parameters = inspect.signature(helper.callable).parameters
199
275
  list_parameters = list(parameters.values())
200
- assert len(list_parameters) == 2, (
201
- "Callable function should only "
202
- f"have two arguments (got {len(list_parameters)})"
203
- )
276
+ if len(list_parameters) != 2:
277
+ raise click.ClickException(
278
+ f"run in {xp_file if xp_file else module_name} function should only "
279
+ f"have two arguments (got {len(list_parameters)}), "
280
+ )
204
281
 
205
282
  schema = list_parameters[1].annotation
206
283
  omegaconf_schema = OmegaConf.structured(schema())
207
284
 
208
- configuration = OmegaConf.merge(*yamls)
209
- if extra_conf:
210
- configuration.merge_with(OmegaConf.from_dotlist(extra_conf))
211
285
  if omegaconf_schema is not None:
212
286
  try:
213
287
  configuration = OmegaConf.merge(omegaconf_schema, configuration)
214
288
  except omegaconf.errors.ConfigKeyError as e:
215
289
  cprint(f"Error in configuration:\n\n{e}", "red", file=sys.stderr)
216
- sys.exit(1)
290
+ raise click.ClickException("Error in configuration")
217
291
 
218
292
  if show:
219
293
  print(json.dumps(OmegaConf.to_container(configuration))) # noqa: T201
220
294
  sys.exit(0)
221
295
 
222
296
  # Move to an object container
223
- configuration = OmegaConf.to_container(
297
+ xp_configuration: ConfigurationBase = OmegaConf.to_container(
224
298
  configuration, structured_config_mode=SCMode.INSTANTIATE
225
299
  )
226
300
 
227
- # Get the working directory
228
- settings = get_settings()
229
- ws_env = {}
230
- workdir = Path(workdir) if workdir else None
231
- if (workdir is None) or (not workdir.is_dir()):
232
- logging.info("Searching for workspace %s", workdir)
233
- ws_settings = get_workspace(str(workdir))
234
- workdir = ws_settings.path.expanduser()
235
- ws_env = ws_settings.env
301
+ # Define the workspace
302
+ ws_env = find_workspace(workdir=workdir, workspace=workspace)
303
+
304
+ workdir = ws_env.path
236
305
 
237
- logging.info("Using working directory %s", str(workdir.resolve()))
306
+ # --- Sets up the experiment ID
238
307
 
239
308
  # --- Runs the experiment
309
+ if xp_configuration.add_timestamp:
310
+ timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M")
311
+ experiment_id = f"""{xp_configuration.id}-{timestamp}"""
312
+ else:
313
+ experiment_id = xp_configuration.id
314
+
315
+ logging.info(
316
+ "Running experiment %s working directory %s",
317
+ experiment_id,
318
+ str(workdir.resolve()),
319
+ )
240
320
  with experiment(
241
- workdir, configuration.id, host=host, port=port, run_mode=run_mode
321
+ ws_env, experiment_id, host=host, port=port, run_mode=run_mode
242
322
  ) as xp:
243
323
  # Set up the environment
244
324
  # (1) global settings (2) workspace settings and (3) command line settings
245
- for key, value in itertools.chain(settings.env.items(), ws_env.items(), env):
246
- logging.info("Setting environment: %s=%s", key, value)
325
+ for key, value in env:
247
326
  xp.setenv(key, value)
248
327
 
328
+ # Sets the python path
329
+ xp.workspace.python_path.extend(python_path)
330
+
249
331
  try:
250
332
  # Run the experiment
251
333
  helper.xp = xp
252
- helper.run(list(args), configuration)
334
+ helper.run(list(args), xp_configuration)
253
335
 
254
336
  # ... and wait
255
337
  xp.wait()
@@ -1,5 +1,5 @@
1
1
  from omegaconf import MISSING
2
- from typing import Optional
2
+ from typing import Optional, List
3
3
  import attr
4
4
 
5
5
  try:
@@ -31,6 +31,12 @@ class ConfigurationBase:
31
31
  file: str = "experiment"
32
32
  """Relative path of the file containing a run function"""
33
33
 
34
+ module: Optional[str] = None
35
+ """Relative path of the file containing a run function"""
36
+
37
+ pythonpath: Optional[List[str]] = None
38
+ """Python path relative to the parent directory of the YAML file"""
39
+
34
40
  parent: Optional[str] = None
35
41
  """Relative path of a YAML file that should be merged"""
36
42
 
@@ -45,3 +51,6 @@ class ConfigurationBase:
45
51
 
46
52
  description: str = ""
47
53
  """Description of the experiment"""
54
+
55
+ add_timestamp: bool = False
56
+ """Adds a timestamp YYYY_MM_DD-HH_MM to the experiment ID"""
@@ -1,11 +1,12 @@
1
1
  import inspect
2
2
  from pathlib import Path
3
+ from abc import ABC, abstractmethod
3
4
  from typing import Callable, Union
4
5
  from experimaestro.core.arguments import ArgumentOptions, TypeAnnotation
5
6
  from experimaestro.core.objects import ConfigWalkContext, Config
6
7
 
7
8
 
8
- class Generator:
9
+ class Generator(ABC):
9
10
  """Base class for all generators"""
10
11
 
11
12
  def isoutput(self):
@@ -13,6 +14,10 @@ class Generator:
13
14
  path within the job folder)"""
14
15
  return False
15
16
 
17
+ @abstractmethod
18
+ def __call__(self, context: ConfigWalkContext, config: Config):
19
+ ...
20
+
16
21
 
17
22
  class PathGenerator(Generator):
18
23
  """Generates a path"""
experimaestro/ipc.py CHANGED
@@ -7,6 +7,7 @@ import sys
7
7
  import logging
8
8
  from .utils import logger
9
9
  from watchdog.observers import Observer
10
+ from watchdog.observers.api import ObservedWatch
10
11
  from watchdog.events import FileSystemEventHandler
11
12
 
12
13
 
@@ -20,7 +21,9 @@ class IPCom:
20
21
  self.observer.start()
21
22
  self.pid = os.getpid()
22
23
 
23
- def fswatch(self, watcher: FileSystemEventHandler, path: Path, recursive=False):
24
+ def fswatch(
25
+ self, watcher: FileSystemEventHandler, path: Path, recursive=False
26
+ ) -> ObservedWatch:
24
27
  if not self.observer.is_alive():
25
28
  logging.error("Observer is not alive")
26
29
 
@@ -10,5 +10,5 @@ from .specs import (
10
10
  HostRequirement,
11
11
  MatchRequirement,
12
12
  )
13
- from .registry import find_launcher, LauncherRegistry, YAMLDataClass
13
+ from .registry import find_launcher, LauncherRegistry
14
14
  from .parser import parse
@@ -1,33 +1,17 @@
1
- from dataclasses import dataclass, field
2
- from typing import TYPE_CHECKING, List, Optional
1
+ from typing import TYPE_CHECKING
3
2
 
4
- from experimaestro.utils.yaml import YAMLDataClass
5
- from .specs import HostRequirement
6
3
 
7
4
  if TYPE_CHECKING:
8
- from experimaestro.launchers import Launcher
9
5
  from experimaestro.connectors import Connector
10
6
  from experimaestro.tokens import Token
11
7
  from .registry import LauncherRegistry
12
8
 
13
9
 
14
- class LauncherConfiguration:
15
- tags: List[str]
16
- weight: int
17
-
18
- """Generic class for a launcher configuration"""
19
-
20
- def get(
21
- self, registry: "LauncherRegistry", requirement: HostRequirement
22
- ) -> Optional["Launcher"]:
23
- raise NotImplementedError(f"For {self.__class__}")
24
-
25
-
26
10
  class ConnectorConfiguration:
27
11
  def create(self, registry: "LauncherRegistry") -> "Connector":
28
12
  raise NotImplementedError(f"For {self.__class__}")
29
13
 
30
14
 
31
- class TokenConfiguration(YAMLDataClass):
15
+ class TokenConfiguration:
32
16
  def create(self, registry: "LauncherRegistry", identifier: str) -> "Token":
33
17
  raise NotImplementedError(f"For {self.__class__}")
@@ -23,7 +23,7 @@ class SuppressStrMatch(StrMatch):
23
23
 
24
24
 
25
25
  def mem_spec():
26
- return "mem", "=", RegExMatch(r"\d+(G|M)?")
26
+ return "mem", "=", RegExMatch(r"\d+(GiB|MiB|G|M)?")
27
27
 
28
28
 
29
29
  def cores_spec():
@@ -51,7 +51,12 @@ def cpu():
51
51
 
52
52
 
53
53
  def duration():
54
- return "duration", "=", RegExMatch(r"\d+"), RegExMatch(r"h(ours)?|d(ays)?")
54
+ return (
55
+ "duration",
56
+ "=",
57
+ RegExMatch(r"\d+"),
58
+ RegExMatch(r"h(ours?)?|d(ays?)?|m(ins?)?"),
59
+ )
55
60
 
56
61
 
57
62
  def one_spec():
@@ -67,7 +72,7 @@ def grammar():
67
72
 
68
73
  class Visitor(PTNodeVisitor):
69
74
  def visit_grammar(self, node, children):
70
- return [child for child in children]
75
+ return specs.RequirementUnion(*[child for child in children])
71
76
 
72
77
  def visit_one_spec(self, node, children):
73
78
  return reduce(lambda x, el: x & el, children)