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

Files changed (79) hide show
  1. experimaestro/__init__.py +3 -1
  2. experimaestro/annotations.py +13 -3
  3. experimaestro/cli/filter.py +3 -3
  4. experimaestro/cli/jobs.py +1 -1
  5. experimaestro/commandline.py +3 -7
  6. experimaestro/connectors/__init__.py +22 -10
  7. experimaestro/connectors/local.py +17 -8
  8. experimaestro/connectors/ssh.py +1 -1
  9. experimaestro/core/arguments.py +26 -3
  10. experimaestro/core/objects.py +90 -6
  11. experimaestro/core/objects.pyi +7 -1
  12. experimaestro/core/types.py +33 -2
  13. experimaestro/experiments/cli.py +7 -3
  14. experimaestro/generators.py +6 -1
  15. experimaestro/ipc.py +4 -1
  16. experimaestro/launcherfinder/registry.py +23 -5
  17. experimaestro/launchers/slurm/base.py +47 -9
  18. experimaestro/notifications.py +1 -1
  19. experimaestro/run.py +1 -1
  20. experimaestro/scheduler/base.py +98 -10
  21. experimaestro/scheduler/dynamic_outputs.py +184 -0
  22. experimaestro/scriptbuilder.py +3 -1
  23. experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
  24. experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  25. experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  26. experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  27. experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  28. experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  29. experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  30. experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  31. experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  32. experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  33. experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
  34. experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  35. experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  36. experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
  37. experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  38. experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  39. experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
  40. experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
  41. experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  42. experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  43. experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
  44. experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  45. experimaestro/server/data/favicon.ico +0 -0
  46. experimaestro/server/data/index.css +22963 -0
  47. experimaestro/server/data/index.css.map +1 -0
  48. experimaestro/server/data/index.html +27 -0
  49. experimaestro/server/data/index.js +101770 -0
  50. experimaestro/server/data/index.js.map +1 -0
  51. experimaestro/server/data/login.html +22 -0
  52. experimaestro/server/data/manifest.json +15 -0
  53. experimaestro/sphinx/__init__.py +7 -17
  54. experimaestro/taskglobals.py +7 -2
  55. experimaestro/tests/definitions_types.py +5 -3
  56. experimaestro/tests/launchers/bin/sbatch +34 -7
  57. experimaestro/tests/launchers/bin/srun +5 -0
  58. experimaestro/tests/launchers/common.py +16 -4
  59. experimaestro/tests/restart.py +6 -3
  60. experimaestro/tests/tasks/all.py +16 -10
  61. experimaestro/tests/tasks/foreign.py +2 -4
  62. experimaestro/tests/test_forward.py +5 -5
  63. experimaestro/tests/test_identifier.py +61 -66
  64. experimaestro/tests/test_instance.py +3 -6
  65. experimaestro/tests/test_param.py +40 -22
  66. experimaestro/tests/test_tags.py +5 -11
  67. experimaestro/tests/test_tokens.py +3 -2
  68. experimaestro/tests/test_types.py +17 -14
  69. experimaestro/tests/test_validation.py +48 -91
  70. experimaestro/tokens.py +16 -5
  71. experimaestro/typingutils.py +7 -0
  72. experimaestro/utils/asyncio.py +6 -2
  73. experimaestro/utils/resources.py +7 -3
  74. {experimaestro-1.6.2.dist-info → experimaestro-1.7.0.dist-info}/METADATA +3 -4
  75. experimaestro-1.7.0.dist-info/RECORD +154 -0
  76. {experimaestro-1.6.2.dist-info → experimaestro-1.7.0.dist-info}/WHEEL +1 -1
  77. experimaestro-1.6.2.dist-info/RECORD +0 -122
  78. {experimaestro-1.6.2.dist-info → experimaestro-1.7.0.dist-info}/LICENSE +0 -0
  79. {experimaestro-1.6.2.dist-info → experimaestro-1.7.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1, shrink-to-fit=no"
8
+ />
9
+ <meta name="theme-color" content="#000000" />
10
+ <title>Experimaestro</title>
11
+ </head>
12
+ <body>
13
+ <h1>Experimaestro</h1>
14
+ <form action="/auth" method="GET">
15
+ <div>
16
+ Token
17
+ <input type="text" name="xpm-token" />
18
+ <input type="submit" value="submit" />
19
+ </div>
20
+ </form>
21
+ </body>
22
+ </html>
@@ -0,0 +1,15 @@
1
+ {
2
+ "short_name": "Experimaestro",
3
+ "name": "Experimaestro Client",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ }
10
+ ],
11
+ "start_url": "./index.html",
12
+ "display": "standalone",
13
+ "theme_color": "#000000",
14
+ "background_color": "#ffffff"
15
+ }
@@ -10,19 +10,13 @@ from docutils import nodes
10
10
  from sphinx.application import Sphinx
11
11
  from sphinx import addnodes
12
12
  from sphinx.ext.autodoc import ClassDocumenter, Documenter, restify
13
- from sphinx.locale import _, __
13
+ from sphinx.locale import _
14
14
  from sphinx.util import inspect, logging
15
- from sphinx.domains.python import (
16
- PyClasslike,
17
- PyAttribute,
18
- PyObject,
19
- directives,
20
- desc_signature,
21
- _parse_annotation,
22
- )
15
+ from sphinx.domains.python import PyClasslike, PyAttribute, directives
16
+ from sphinx.domains.python import PyObject # noqa: F401
17
+ from sphinx.addnodes import desc_signature
23
18
  from sphinx.util.typing import OptionSpec
24
19
  from docutils.statemachine import StringList
25
- import logging
26
20
  import re
27
21
 
28
22
  from experimaestro import Config, Task
@@ -97,9 +91,6 @@ class ConfigDocumenter(ClassDocumenter):
97
91
  can_document = inspect.isclass(member) and issubclass(member, Config)
98
92
  return can_document
99
93
 
100
- def add_directive_header(self, sig: str) -> None:
101
- super().add_directive_header(sig)
102
-
103
94
  def get_object_members(self, want_all: bool): # -> Tuple[bool, ObjectMembers]:
104
95
  r = super().get_object_members(want_all)
105
96
  return r
@@ -157,7 +148,7 @@ class ConfigDocumenter(ClassDocumenter):
157
148
 
158
149
  # Our specific code
159
150
  if issubclass(self.object, Task):
160
- self.add_line(f" :task:", sourcename)
151
+ self.add_line(" :task:", sourcename)
161
152
 
162
153
  # add inheritance info, if wanted
163
154
  if not self.doc_as_attr and self.options.show_inheritance:
@@ -196,7 +187,6 @@ class ConfigDocumenter(ClassDocumenter):
196
187
  def add_content(
197
188
  self, more_content: Optional[StringList], no_docstring: bool = False
198
189
  ) -> None:
199
-
200
190
  xpminfo = getxpminfo(self.object)
201
191
  source_name = self.get_sourcename()
202
192
 
@@ -214,9 +204,9 @@ class ConfigDocumenter(ClassDocumenter):
214
204
  source_name,
215
205
  )
216
206
  if argument.generator:
217
- self.add_line(f" :generated:", source_name)
207
+ self.add_line(" :generated:", source_name)
218
208
  elif argument.constant:
219
- self.add_line(f" :constant:", source_name)
209
+ self.add_line(" :constant:", source_name)
220
210
 
221
211
  # self.add_line("", source_name)
222
212
  if argument.help:
@@ -1,7 +1,6 @@
1
+ from functools import cached_property
1
2
  from pathlib import Path
2
3
  from typing import Optional
3
- import os
4
- import logging
5
4
 
6
5
 
7
6
  class Env:
@@ -18,6 +17,12 @@ class Env:
18
17
  # - no progress report
19
18
  slave: bool = False
20
19
 
20
+ @cached_property
21
+ def xpm_path(self):
22
+ path = self.taskpath / ".experimaestro"
23
+ path.mkdir(exist_ok=True)
24
+ return path
25
+
21
26
  @staticmethod
22
27
  def instance():
23
28
  if Env._instance is None:
@@ -1,15 +1,17 @@
1
- from experimaestro import argument, Task
1
+ from experimaestro import Param, Task
2
2
 
3
3
 
4
- @argument("value", type=int)
5
4
  class IntegerTask(Task):
5
+ value: Param[int]
6
+
6
7
  def execute(self):
7
8
  if not isinstance(self.value, int):
8
9
  raise AssertionError("Not an integer")
9
10
 
10
11
 
11
- @argument("value", type=float)
12
12
  class FloatTask(Task):
13
+ value: Param[float]
14
+
13
15
  def execute(self):
14
16
  if not isinstance(self.value, float):
15
17
  raise AssertionError("Not a float but %s" % type(self.value))
@@ -2,15 +2,28 @@
2
2
 
3
3
  # slurm test suite
4
4
 
5
+ pwd 1>&2
6
+ CURDIR="$(realpath "$(dirname "$0")"/..)"
7
+
5
8
  # Where we store the jobs
6
- XPM_SLURM_DIR="$(realpath "$(dirname "$0")"/..)/slurm"
9
+ XPM_SLURM_DIR="${CURDIR}/slurm"
7
10
  if ! test -d "$XPM_SLURM_DIR"; then
8
11
  echo "Directory $XPM_SLURM_DIR does not exist" 1>&2
9
12
  exit 1
10
13
  fi
11
14
 
12
15
  mkdir -p "$XPM_SLURM_DIR/jobs"
13
- echo "Slurm directory: $XPM_SLURM_DIR" >&2
16
+ echo "Slurm directory: $XPM_SLURM_DIR" >&2
17
+
18
+ RED='\033[0;31m'
19
+ NC='\033[0m' # No Color
20
+
21
+
22
+ if ! which flock > /dev/null; then
23
+ echo -e "[${RED}ERROR${NC}] flock is not installed on this system" 1>&2
24
+ exit 1
25
+ fi
26
+
14
27
 
15
28
  lockpath() {
16
29
  fid="$1"
@@ -18,7 +31,7 @@ lockpath() {
18
31
 
19
32
  echo "Locking $path..." 1>&2
20
33
  eval exec "$fid<>" $path
21
- if ! flock --timeout 2 $fid; then
34
+ if ! flock --timeout 2 $fid; then
22
35
  echo Could not lock "$path" - stopping 1>&2
23
36
  exit 017
24
37
  fi
@@ -37,12 +50,26 @@ while true; do
37
50
  -o) shift; stdout="$1"; shift;;
38
51
  -e) shift; stderr="$1"; shift;;
39
52
  --*) args+=("$1"); shift;;
40
- *) break 2;;
53
+ *) break 2;;
41
54
  esac
42
55
  done
43
56
 
44
- echo "Starting $@ ${args[@]} > $stdout" >&2
45
- (eval "$@" "${args[@]}"; echo $? > "$XPM_SLURM_DIR/jobs/$$.status") > $stdout 2> $stderr &
57
+ chdir=$(pwd)
58
+ while IFS= read -r line; do
59
+ case "$line" in
60
+ "#SBATCH --output="*)stdout=${line#*#SBATCH --output=};;
61
+ "#SBATCH --error="*) stderr=${line#*#SBATCH --error=};;
62
+ "#SBATCH --chdir="*) chdir=${line#*#SBATCH --chdir=};;
63
+ esac
64
+ done < "$1"
65
+
66
+ cd "$chdir"
67
+ echo "Starting $@ ${args[@]} > $stdout 2> $stderr" >&2
68
+ (
69
+ export PATH="${CURDIR}/bin:$PATH"
70
+ eval "$@" "${args[@]}"
71
+ echo $? > "$XPM_SLURM_DIR/jobs/$$.status"
72
+ ) > $stdout 2> $stderr &
46
73
  JOBID="$$"
47
74
  date > "$XPM_SLURM_DIR/jobs/$JOBID.start"
48
75
  disown
@@ -51,4 +78,4 @@ if test "$parsable" == 0; then
51
78
  echo "Submitted batch job ${JOBID}"
52
79
  else
53
80
  echo "${JOBID};cluster"
54
- fi
81
+ fi
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+
3
+ # Just run the command
4
+
5
+ eval "$@"
@@ -68,24 +68,36 @@ class WaitUntilTouched(Task):
68
68
 
69
69
 
70
70
  def takeback(launcher, datapath, txp1, txp2):
71
+ """Launch two times the same task (with two experiments)
72
+
73
+ :param launcher: The launcher
74
+ :param datapath: The path containing the two files that control the task, namely (1) touching which is created by the task when starting, (2) waiting which is controlled here
75
+ :param txp1: The first experiment
76
+ :param txp2: The second experiment
77
+ """
71
78
  datapath.mkdir()
72
79
  touching = datapath / "touching"
73
80
  waiting = datapath / "waiting"
74
81
 
75
- with txp1 as xp1: # flake8: noqa: F841
76
- WaitUntilTouched(touching=touching, waiting=waiting).submit(launcher=launcher)
82
+ with txp1:
83
+ task: WaitUntilTouched = WaitUntilTouched(
84
+ touching=touching, waiting=waiting
85
+ ).submit(launcher=launcher)
77
86
 
78
87
  logger.debug("Waiting for task to create 'touching' file")
79
88
  while not touching.is_file():
89
+ if task.__xpm__.job.state.finished():
90
+ raise Exception("Job has finished... too early")
80
91
  time.sleep(0.01)
81
92
 
82
- with txp2 as xp2: # flake8: noqa: F841
93
+ with txp2:
83
94
  result = WaitUntilTouched(touching=touching, waiting=waiting).submit(
84
95
  launcher=launcher
85
96
  )
86
97
 
87
- logger.debug("Waiting for job to be in running mode")
98
+ logger.debug("Waiting for job to be running (scheduler)")
88
99
  while result.__xpm__.job.state != JobState.RUNNING:
89
100
  time.sleep(0.1)
90
101
 
102
+ logger.debug("OK, no we can notify the task")
91
103
  waiting.touch()
@@ -1,12 +1,14 @@
1
1
  import time
2
+ from pathlib import Path
2
3
  import sys
3
4
  from typing import Callable
4
- from experimaestro import Task, pathoption
5
+ from experimaestro import Task, Meta, field, PathGenerator
5
6
  import psutil
6
7
  import logging
7
8
  import subprocess
8
9
  import json
9
10
  import signal
11
+
10
12
  from experimaestro.scheduler.workspace import RunMode
11
13
  from experimaestro.tests.utils import TemporaryExperiment, is_posix
12
14
  from experimaestro.scheduler import JobState
@@ -26,9 +28,10 @@ if is_posix():
26
28
  TERMINATES_FUNC.append(sigint)
27
29
 
28
30
 
29
- @pathoption("touch", "touch")
30
- @pathoption("wait", "wait")
31
31
  class Restart(Task):
32
+ touch: Meta[Path] = field(default_factory=PathGenerator("touch"))
33
+ wait: Meta[Path] = field(default_factory=PathGenerator("wait"))
34
+
32
35
  def execute(self):
33
36
  # Write the file "touch" to notify that we started
34
37
  with open(self.touch, "w") as out:
@@ -1,11 +1,13 @@
1
+ from pathlib import Path
1
2
  import time
2
3
  from typing import List
3
4
  from experimaestro import (
4
- param,
5
+ Meta,
5
6
  Param,
7
+ field,
6
8
  Task,
9
+ PathGenerator,
7
10
  Config,
8
- pathoption,
9
11
  STDOUT,
10
12
  cache,
11
13
  )
@@ -18,8 +20,8 @@ class SimpleTask(Task):
18
20
  print(self.x) # noqa: T201
19
21
 
20
22
 
21
- @pathoption("out", STDOUT)
22
23
  class Say(Task):
24
+ out: Meta[Path] = field(default_factory=PathGenerator(STDOUT))
23
25
  word: Param[str]
24
26
 
25
27
  def execute(self):
@@ -38,19 +40,20 @@ class Concat(Task):
38
40
  print(" ".join(says)) # noqa: T201
39
41
 
40
42
 
41
- @param("x", type=int)
42
43
  class ForeignClassB1(Config):
43
- pass
44
+ x: Param[int]
44
45
 
45
46
 
46
- @param("b", type=ForeignClassB1)
47
47
  class ForeignTaskA(Task):
48
+ b: Param[ForeignClassB1]
49
+
48
50
  def execute(self):
49
51
  print(self.b.x) # noqa: T201
50
52
 
51
53
 
52
- @pathoption("wait", "wait")
53
54
  class Fail(Task):
55
+ wait: Meta[Path] = field(default_factory=PathGenerator("wait"))
56
+
54
57
  def execute(self):
55
58
  while not self.wait.is_file():
56
59
  time.sleep(0.01)
@@ -64,14 +67,16 @@ class Fail(Task):
64
67
  out.write("hello")
65
68
 
66
69
 
67
- @param("fail", Fail)
68
70
  class FailConsumer(Task):
71
+ fail: Param[Fail]
72
+
69
73
  def execute(self):
70
74
  return True
71
75
 
72
76
 
73
- @param("a", int)
74
77
  class Method(Task):
78
+ a: Param[int]
79
+
75
80
  def execute(self):
76
81
  assert self.a == 1
77
82
 
@@ -92,7 +97,8 @@ class CacheConfig(Config):
92
97
  return path.read_text()
93
98
 
94
99
 
95
- @param("data", type=CacheConfig)
96
100
  class CacheConfigTask(Task):
101
+ data: Param[CacheConfig]
102
+
97
103
  def execute(self):
98
104
  assert self.data.get() == "hello"
@@ -1,8 +1,6 @@
1
- from experimaestro import param, config
1
+ from experimaestro import Param
2
2
  from .all import ForeignClassB1
3
3
 
4
4
 
5
- @param("y", type=int)
6
- @config()
7
5
  class ForeignClassB2(ForeignClassB1):
8
- pass
6
+ y: Param[int]
@@ -1,12 +1,12 @@
1
- from experimaestro import argument, Config
1
+ from experimaestro import Param, Config
2
2
  from experimaestro.click import forwardoption
3
3
  import click
4
4
 
5
5
 
6
6
  def test_main():
7
- @argument("epochs", type=int, default=100, help="Number of learning epochs")
8
7
  class MyModel(Config):
9
- pass
8
+ epochs: Param[int] = 100
9
+ """Number of learning epochs"""
10
10
 
11
11
  @forwardoption.epochs(MyModel)
12
12
  @click.command()
@@ -18,9 +18,9 @@ def test_main():
18
18
 
19
19
 
20
20
  def test_rename():
21
- @argument("epochs", type=int, default=100, help="Number of learning epochs")
22
21
  class MyModel(Config):
23
- pass
22
+ epochs: Param[int] = 100
23
+ """Number of learning epochs"""
24
24
 
25
25
  @forwardoption.epochs(MyModel, "my-epochs")
26
26
  @click.command()