experimaestro 1.6.1__py3-none-any.whl → 1.7.0rc0__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 (76) 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/connectors/__init__.py +17 -8
  6. experimaestro/connectors/local.py +8 -3
  7. experimaestro/core/arguments.py +26 -3
  8. experimaestro/core/objects.py +90 -6
  9. experimaestro/core/objects.pyi +7 -1
  10. experimaestro/core/types.py +33 -2
  11. experimaestro/experiments/cli.py +18 -10
  12. experimaestro/generators.py +6 -1
  13. experimaestro/ipc.py +4 -1
  14. experimaestro/launcherfinder/registry.py +7 -4
  15. experimaestro/notifications.py +1 -1
  16. experimaestro/run.py +1 -1
  17. experimaestro/scheduler/base.py +98 -6
  18. experimaestro/scheduler/dynamic_outputs.py +184 -0
  19. experimaestro/scheduler/workspace.py +2 -1
  20. experimaestro/scriptbuilder.py +10 -1
  21. experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
  22. experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  23. experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  24. experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  25. experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  26. experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  27. experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  28. experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  29. experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  30. experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  31. experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
  32. experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  33. experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  34. experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
  35. experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  36. experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  37. experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
  38. experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
  39. experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  40. experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  41. experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
  42. experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  43. experimaestro/server/data/favicon.ico +0 -0
  44. experimaestro/server/data/index.css +22963 -0
  45. experimaestro/server/data/index.css.map +1 -0
  46. experimaestro/server/data/index.html +27 -0
  47. experimaestro/server/data/index.js +101770 -0
  48. experimaestro/server/data/index.js.map +1 -0
  49. experimaestro/server/data/login.html +22 -0
  50. experimaestro/server/data/manifest.json +15 -0
  51. experimaestro/settings.py +2 -2
  52. experimaestro/taskglobals.py +7 -2
  53. experimaestro/tests/definitions_types.py +5 -3
  54. experimaestro/tests/launchers/bin/sbatch +18 -5
  55. experimaestro/tests/launchers/common.py +11 -3
  56. experimaestro/tests/restart.py +6 -3
  57. experimaestro/tests/tasks/all.py +16 -10
  58. experimaestro/tests/tasks/foreign.py +2 -4
  59. experimaestro/tests/test_forward.py +5 -5
  60. experimaestro/tests/test_identifier.py +61 -66
  61. experimaestro/tests/test_instance.py +3 -6
  62. experimaestro/tests/test_param.py +40 -22
  63. experimaestro/tests/test_tags.py +5 -11
  64. experimaestro/tests/test_tokens.py +3 -2
  65. experimaestro/tests/test_types.py +17 -14
  66. experimaestro/tests/test_validation.py +48 -91
  67. experimaestro/tokens.py +16 -5
  68. experimaestro/typingutils.py +7 -0
  69. experimaestro/utils/asyncio.py +6 -2
  70. experimaestro/utils/resources.py +7 -3
  71. {experimaestro-1.6.1.dist-info → experimaestro-1.7.0rc0.dist-info}/METADATA +3 -4
  72. experimaestro-1.7.0rc0.dist-info/RECORD +153 -0
  73. {experimaestro-1.6.1.dist-info → experimaestro-1.7.0rc0.dist-info}/WHEEL +1 -1
  74. experimaestro-1.6.1.dist-info/RECORD +0 -122
  75. {experimaestro-1.6.1.dist-info → experimaestro-1.7.0rc0.dist-info}/LICENSE +0 -0
  76. {experimaestro-1.6.1.dist-info → experimaestro-1.7.0rc0.dist-info}/entry_points.txt +0 -0
@@ -1,21 +1,18 @@
1
1
  from typing import Optional
2
- from experimaestro import config, Param, Config
2
+ from experimaestro import Param, Config
3
3
  from experimaestro.core.objects import TypeConfig
4
4
  from experimaestro.core.serializers import SerializationLWTask
5
5
 
6
6
 
7
- @config()
8
- class A:
7
+ class A(Config):
9
8
  x: Param[int] = 1
10
9
 
11
10
 
12
- @config()
13
11
  class A1(A):
14
12
  pass
15
13
 
16
14
 
17
- @config()
18
- class B:
15
+ class B(Config):
19
16
  a: Param[A]
20
17
 
21
18
 
@@ -13,14 +13,16 @@ from experimaestro.core.types import DictType, IntType, StrType
13
13
  from enum import Enum
14
14
  import pytest
15
15
  from experimaestro import (
16
- config,
17
16
  Option,
18
17
  Constant,
19
18
  Param,
20
19
  Task,
21
20
  default,
21
+ Meta,
22
22
  Config,
23
23
  pathgenerator,
24
+ PathGenerator,
25
+ field,
24
26
  Annotated,
25
27
  )
26
28
  import experimaestro.core.types as types
@@ -29,25 +31,24 @@ from experimaestro.xpmutils import DirectoryContext
29
31
  # --- Test manual name for configuration
30
32
 
31
33
 
32
- @config("annotations.b")
33
- class B:
34
+ class B(Config):
35
+ __xpmid__ = "annotations.b"
34
36
  pass
35
37
 
36
38
 
37
39
  def test_fullname():
38
- assert str(B.__xpmtype__.identifier) == "annotations.b"
40
+ assert str(B.__getxpmtype__().identifier) == "annotations.b"
39
41
 
40
42
 
41
43
  # --- Automatic name for configuration
42
44
 
43
45
 
44
- @config()
45
- class A:
46
+ class A(Config):
46
47
  pass
47
48
 
48
49
 
49
50
  def test_noname():
50
- assert str(A.__xpmtype__.identifier) == "experimaestro.tests.test_param.a"
51
+ assert str(A.__getxpmtype__().identifier) == "experimaestro.tests.test_param.a"
51
52
 
52
53
 
53
54
  def serializeCycle(config: Config):
@@ -69,8 +70,7 @@ def ArgumentValue(default=None, *, help=""):
69
70
  def test_type_hinting():
70
71
  """Test for type hinting"""
71
72
 
72
- @config()
73
- class MyConfig:
73
+ class MyConfig(Config):
74
74
  """A configuration
75
75
 
76
76
  Attributes:
@@ -89,7 +89,7 @@ def test_type_hinting():
89
89
  path: Annotated[Path, pathgenerator("world")]
90
90
  option: Option[str]
91
91
 
92
- ot = MyConfig.__xpmtype__
92
+ ot = MyConfig.__getxpmtype__()
93
93
 
94
94
  # Check required parameter
95
95
  arg_x = ot.getArgument("x")
@@ -139,7 +139,7 @@ def test_type_hinting():
139
139
 
140
140
  def test_generatedpath():
141
141
  class A(Config):
142
- path: Annotated[Path, pathgenerator("test.txt")]
142
+ path: Meta[Path] = field(default_factory=PathGenerator("test.txt"))
143
143
 
144
144
  class B(Config):
145
145
  a: Param[A]
@@ -179,8 +179,7 @@ def test_config_class():
179
179
 
180
180
 
181
181
  def test_constant():
182
- @config()
183
- class A:
182
+ class A(Config):
184
183
  x: Constant[int] = 2
185
184
 
186
185
  a = A()
@@ -224,16 +223,14 @@ def test_inheritance():
224
223
 
225
224
 
226
225
  def test_redefined_param():
227
- @config()
228
- class A:
226
+ class A(Config):
229
227
  x: Param[int]
230
228
 
231
- @config()
232
- class B:
229
+ class B(Config):
233
230
  x: Param[int] = 3
234
231
 
235
- atx = A.C.__xpmtype__.getArgument("x")
236
- btx = B.C.__xpmtype__.getArgument("x")
232
+ atx = A.C.__getxpmtype__().getArgument("x")
233
+ btx = B.C.__getxpmtype__().getArgument("x")
237
234
 
238
235
  assert atx.required
239
236
 
@@ -258,18 +255,39 @@ def test_param_dict():
258
255
  A(x={"wrong": 1.2})
259
256
 
260
257
 
258
+ # --- Default
259
+
260
+
261
+ class ConfigWithDefault(Config):
262
+ x: Param[int] = field(default=1)
263
+
264
+
265
+ def test_param_default():
266
+ assert ConfigWithDefault().x == 1
267
+
268
+
269
+ class ConfigWithDefaultFactory(Config):
270
+ x: Param[int] = field(default_factory=lambda: 1)
271
+
272
+
273
+ def test_param_default_factory():
274
+ value = ConfigWithDefaultFactory()
275
+ context = DirectoryContext(Path("/__fakepath__"))
276
+ value.__xpm__.seal(context)
277
+ assert value.x == 1
278
+
279
+
261
280
  # --- Task annotations
262
281
 
263
282
 
264
283
  def test_default_mismatch():
265
284
  """Test mismatch between default and type"""
266
285
 
267
- @config()
268
- class A:
286
+ class A(Config):
269
287
  x: Param[int] = 0.2
270
288
 
271
289
  with pytest.raises(TypeError):
272
- A.__xpmtype__.getArgument("x")
290
+ A.__getxpmtype__().getArgument("x")
273
291
 
274
292
 
275
293
  # --- Handling help annotations
@@ -3,8 +3,6 @@ from pathlib import Path
3
3
  from experimaestro import (
4
4
  tag,
5
5
  LightweightTask,
6
- config,
7
- argument,
8
6
  Config,
9
7
  Task,
10
8
  Param,
@@ -13,17 +11,13 @@ from experimaestro.scheduler.workspace import RunMode
13
11
  from experimaestro.xpmutils import DirectoryContext
14
12
 
15
13
 
16
- @argument("x", type=int)
17
- @config()
18
- class Config1:
19
- pass
14
+ class Config1(Config):
15
+ x: Param[int]
20
16
 
21
17
 
22
- @argument("x", type=int)
23
- @argument("c", type=Config1)
24
- @config()
25
- class Config2:
26
- pass
18
+ class Config2(Config):
19
+ x: Param[int]
20
+ c: Param[Config1]
27
21
 
28
22
 
29
23
  def test_tag():
@@ -7,7 +7,7 @@ import time
7
7
  from pathlib import Path
8
8
 
9
9
  import subprocess
10
- from experimaestro import Task, param
10
+ from experimaestro import Task, Param
11
11
  from experimaestro.tokens import CounterToken, TokenFile
12
12
  from experimaestro.scheduler import JobState
13
13
  from .utils import (
@@ -74,8 +74,9 @@ def test_token_ok():
74
74
  logging.info("Finished token_ok test")
75
75
 
76
76
 
77
- @param("x", type=int)
78
77
  class dummy_task(Task):
78
+ x: Param[int]
79
+
79
80
  def execute(self):
80
81
  pass
81
82
 
@@ -1,31 +1,26 @@
1
1
  # --- Task and types definitions
2
2
 
3
3
  import logging
4
- from experimaestro import Config, config
5
- from experimaestro.core.objects import TypeConfig
4
+ from experimaestro import Config, Param
5
+ from typing import Union
6
6
 
7
- from .utils import TemporaryExperiment
8
- from experimaestro.scheduler import JobState
7
+ import pytest
8
+ from experimaestro.core.objects import TypeConfig
9
9
 
10
10
 
11
11
  def test_multiple_inheritance():
12
- @config()
13
- class A:
12
+ class A(Config):
14
13
  pass
15
14
 
16
- @config()
17
- class B:
15
+ class B(Config):
18
16
  pass
19
17
 
20
- @config()
21
18
  class B1(B):
22
19
  pass
23
20
 
24
- @config()
25
21
  class C1(B1, A):
26
22
  pass
27
23
 
28
- @config()
29
24
  class C2(A, B1):
30
25
  pass
31
26
 
@@ -45,14 +40,12 @@ def test_multiple_inheritance():
45
40
 
46
41
 
47
42
  def test_missing_hierarchy():
48
- @config()
49
- class A:
43
+ class A(Config):
50
44
  pass
51
45
 
52
46
  class A1(A):
53
47
  pass
54
48
 
55
- @config()
56
49
  class B(A1):
57
50
  pass
58
51
 
@@ -60,3 +53,13 @@ def test_missing_hierarchy():
60
53
 
61
54
  assert issubclass(B, A)
62
55
  assert issubclass(B, A1)
56
+
57
+
58
+ def test_types_union():
59
+ class A(Config):
60
+ x: Param[Union[int, str]]
61
+
62
+ A(x=1)
63
+ A(x="hello")
64
+ with pytest.raises(ValueError):
65
+ A(x=[])
@@ -2,18 +2,9 @@
2
2
 
3
3
  import pytest
4
4
  from pathlib import Path
5
- from experimaestro import (
6
- config,
7
- Task,
8
- Identifier,
9
- argument,
10
- pathoption,
11
- ConstantParam,
12
- Param,
13
- Config,
14
- )
5
+ from experimaestro import Task, field, Identifier, Constant, Param, Config, Meta
15
6
  from enum import Enum
16
- import experimaestro.core.types as types
7
+ from experimaestro.generators import PathGenerator
17
8
  from experimaestro.scheduler import Job, JobContext
18
9
  from experimaestro.scheduler.workspace import RunMode
19
10
  from .utils import TemporaryExperiment
@@ -31,57 +22,50 @@ def expect_notvalidate(value):
31
22
  value.__xpm__.validate()
32
23
 
33
24
 
34
- @argument("value", type=int)
35
- @config()
36
- class A:
37
- pass
25
+ class A(Config):
26
+ value: Param[int]
38
27
 
39
28
 
40
- @argument("a", type=A)
41
- @config()
42
- class B:
43
- pass
29
+ class B(Config):
30
+ a: Param[A]
44
31
 
45
32
 
46
- @pathoption("path", "outdir")
47
- @config()
48
- class C:
33
+ class C(Config):
34
+ path: Meta[Path] = field(default_factory=PathGenerator("outdir"))
49
35
  pass
50
36
 
51
37
 
52
- def test_simple():
38
+ def test_validation_simple():
53
39
  expect_validate(A(value=1))
54
40
 
55
41
 
56
- def test_missing():
42
+ def test_validation_missing():
57
43
  expect_notvalidate(A())
58
44
 
59
45
 
60
- def test_simple_nested():
46
+ def test_validation_simple_nested():
61
47
  b = B()
62
48
  b.a = A(value=1)
63
49
  expect_validate(b)
64
50
 
65
51
 
66
- def test_missing_nested():
52
+ def test_validation_missing_nested():
67
53
  b = B()
68
54
  b.a = A()
69
55
  expect_notvalidate(b)
70
56
 
71
57
 
72
- def test_type():
73
- @config(valns.type.a)
74
- class A:
58
+ def test_validation_type():
59
+ class A(Config):
60
+ __xpmid__ = valns.type.a
75
61
  pass
76
62
 
77
- @config(valns.type.b)
78
- class B:
79
- pass
63
+ class B(Config):
64
+ __xpmid__ = valns.type.b
80
65
 
81
- @argument("a", A)
82
- @config(valns.type.c)
83
- class C:
84
- pass
66
+ class C(Config):
67
+ a: Param[A]
68
+ __xpmid__ = valns.type.c
85
69
 
86
70
  with pytest.raises(ValueError):
87
71
  C(a=B())
@@ -91,30 +75,26 @@ def test_type():
91
75
  c.a = B()
92
76
 
93
77
 
94
- def test_subtype():
95
- @config(valns.subtype.a)
96
- class A:
97
- pass
78
+ def test_validation_subtype():
79
+ class A(Config):
80
+ __xpmid__ = valns.subtype.a
98
81
 
99
- @config(valns.subtype.a1)
100
82
  class A1(A):
101
- pass
83
+ __xpmid__ = valns.subtype.a1
102
84
 
103
- @argument("a", A)
104
- @config(valns.subtype.b)
105
- class B:
106
- pass
85
+ class B(Config):
86
+ __xpmid__ = valns.subtype.b
87
+ a: Param[A]
107
88
 
108
89
  expect_validate(B(a=A1()))
109
90
 
110
91
 
111
- def test_path():
112
- """Test of @pathoption"""
92
+ def test_validation_path_generator():
93
+ """Test of path generator"""
113
94
 
114
- @pathoption("value", "file.txt")
115
- @config(valns.path.a)
116
- class A:
117
- pass
95
+ class A(Config):
96
+ __xpmid__ = valns.path.a
97
+ value: Meta[Path] = field(default_factory=PathGenerator("file.txt"))
118
98
 
119
99
  a = A()
120
100
  a.__xpm__.validate()
@@ -129,13 +109,12 @@ def test_path():
129
109
  assert a.value.parents[3] == xp.workspace.path
130
110
 
131
111
 
132
- def test_constant():
133
- """Test of @ConstantParam"""
112
+ def test_validation_constant():
113
+ """Test of constant"""
134
114
 
135
- @ConstantParam("value", 1)
136
- @config(valns.constant.a)
137
- class A:
138
- pass
115
+ class A(Config):
116
+ __xpmid__ = valns.constant.a
117
+ value: Constant[int] = 1
139
118
 
140
119
  a = A()
141
120
  a.__xpm__.validate()
@@ -145,31 +124,26 @@ def test_constant():
145
124
  assert a.value == 1
146
125
 
147
126
 
148
- @argument("x", type=int)
149
- @config()
150
- class Parent:
151
- pass
127
+ class Parent(Config):
128
+ x: Param[int]
152
129
 
153
130
 
154
- @config()
155
131
  class Child(Parent):
156
132
  pass
157
133
 
158
134
 
159
- def test_child():
135
+ def test_validation_child():
160
136
  expect_validate(Child(x=1))
161
137
 
162
138
 
163
139
  # --- Path argument checks
164
140
 
165
141
 
166
- @pathoption("x", "x")
167
- @config()
168
- class PathParent:
169
- pass
142
+ class PathParent(Config):
143
+ x: Meta[Path] = field(default_factory=PathGenerator("x"))
170
144
 
171
145
 
172
- def test_path_option():
146
+ def test_validation_path_option():
173
147
  c = PathParent()
174
148
  expect_validate(c)
175
149
 
@@ -177,28 +151,11 @@ def test_path_option():
177
151
  # --- Default value
178
152
 
179
153
 
180
- @pytest.mark.parametrize(
181
- "value,apitype",
182
- [(1.5, types.FloatType), (1, types.IntType), (False, types.BoolType)],
183
- )
184
- def test_default(value, apitype):
185
- @argument("default", default=value)
186
- @config(valns.default[str(type(value))])
187
- class Default:
188
- pass
189
-
190
- value = Default()
191
- expect_validate(value)
192
- assert Default.__xpmtype__.arguments["default"].type.__class__ == apitype
193
-
194
-
195
- def test_seal():
154
+ def test_validation_seal():
196
155
  """Test value sealing"""
197
156
 
198
- @argument("a", int)
199
- @config()
200
- class A:
201
- pass
157
+ class A(Config):
158
+ a: Param[int]
202
159
 
203
160
  a = A(a=2)
204
161
  a.__xpm__.seal(EmptyContext())
@@ -207,7 +164,7 @@ def test_seal():
207
164
  a.a = 1
208
165
 
209
166
 
210
- def test_validation_enum():
167
+ def test_validation_validation_enum():
211
168
  """Path arguments should be ignored"""
212
169
 
213
170
  class EnumParam(Enum):
@@ -241,7 +198,7 @@ class TaskConfigConsumer(Config):
241
198
  x: Param[TaskParentConfig]
242
199
 
243
200
 
244
- def test_taskargument():
201
+ def test_validation_taskargument():
245
202
  x = taskconfig()
246
203
  with TemporaryExperiment("fake"):
247
204
  x.submit(run_mode=RunMode.DRY_RUN)
experimaestro/tokens.py CHANGED
@@ -59,6 +59,8 @@ class CounterTokenLock(Lock):
59
59
 
60
60
 
61
61
  class CounterTokenDependency(Dependency):
62
+ """A dependency onto a token"""
63
+
62
64
  def __init__(self, token: "CounterToken", count: int):
63
65
  super().__init__(token)
64
66
  self._token = token
@@ -66,6 +68,7 @@ class CounterTokenDependency(Dependency):
66
68
 
67
69
  @property
68
70
  def name(self):
71
+ """The (file) name for this dependency, when taken"""
69
72
  return f"{self.target.identifier}.token"
70
73
 
71
74
  def status(self) -> DependencyStatus:
@@ -165,7 +168,7 @@ class CounterToken(Token, FileSystemEventHandler):
165
168
  - TIMESTAMP.token contains (1) the number of tokens (2) the job URI
166
169
  """
167
170
 
168
- """Maps paths to instances"""
171
+ """Maps token keys to CounterToken instances"""
169
172
  TOKENS: Dict[str, "CounterToken"] = {}
170
173
 
171
174
  @staticmethod
@@ -193,7 +196,7 @@ class CounterToken(Token, FileSystemEventHandler):
193
196
  """[summary]
194
197
 
195
198
  Arguments:
196
- path {Path} -- The file path of the token file
199
+ path {Path} -- The file path of the token directory
197
200
  count {int} -- Number of tokens (overrides previous definitions)
198
201
  force -- If the token has already been created, force to write the maximum
199
202
  number of tokens
@@ -298,6 +301,9 @@ class CounterToken(Token, FileSystemEventHandler):
298
301
  tokenfile = TokenFile(path)
299
302
  tokenfile.watch()
300
303
  self.cache[path.name] = tokenfile
304
+ except FileNotFoundError:
305
+ # We did not find the token file... just ignore
306
+ pass
301
307
  except Exception:
302
308
  logger.exception("Uncaught exception in on_modified handler")
303
309
  raise
@@ -336,13 +342,18 @@ class CounterToken(Token, FileSystemEventHandler):
336
342
  for dependency in dependents:
337
343
  dependency.check()
338
344
 
345
+ # A modified dependency not in cache
339
346
  elif path.name.endswith(".token") and path.name not in self.cache:
340
347
  with self.lock:
341
348
  if path.name not in self.cache:
342
349
  logger.debug("Token file not in cache %s", path.name)
343
- tokenfile = TokenFile(path)
344
- tokenfile.watch()
345
- self.cache[path.name] = tokenfile
350
+ try:
351
+ tokenfile = TokenFile(path)
352
+ tokenfile.watch()
353
+ self.cache[path.name] = tokenfile
354
+ except FileNotFoundError:
355
+ # Well, the file did not exist anymore...
356
+ pass
346
357
  except Exception:
347
358
  logger.exception("Uncaught exception in on_modified handler")
348
359
  raise
@@ -26,6 +26,13 @@ def isgenericalias(typehint):
26
26
  return isinstance(typehint, GenericAlias)
27
27
 
28
28
 
29
+ def get_union(typehint):
30
+ """Return the list of types of a union (or the type itself if it is not an union)"""
31
+ if isgenericalias(typehint) and typehint.__origin__ == typing.Union:
32
+ return typehint.__args__
33
+ return None
34
+
35
+
29
36
  def get_optional(typehint):
30
37
  if isgenericalias(typehint) and typehint.__origin__ == typing.Union:
31
38
  if len(typehint.__args__) == 2:
@@ -10,8 +10,12 @@ def asyncThreadcheck(name, func, *args, **kwargs) -> asyncio.Future:
10
10
 
11
11
  def dowait():
12
12
  logging.debug("Running %s", func)
13
- result = func(*args, **kwargs)
14
- logging.debug("Got result from %s", func)
13
+ try:
14
+ result = func(*args, **kwargs)
15
+ logging.debug("Got result from %s", func)
16
+ except Exception:
17
+ logging.exception("Got an error in the thread")
18
+ raise
15
19
  loop.call_soon_threadsafe(future.set_result, result)
16
20
 
17
21
  # Start thread
@@ -24,15 +24,19 @@ class ResourcePathWrapper(PathLike):
24
24
  parents = [s.name for s in reversed(self.path.parents)][1:]
25
25
  return ".".join(parents)
26
26
 
27
- @property
27
+ @cached_property
28
28
  def name(self):
29
29
  return self.path.name
30
30
 
31
31
  def is_file(self):
32
- return resources.is_resource(self.package, self.name)
32
+ return any(
33
+ traversable.name == self.name and traversable.is_file()
34
+ for traversable in resources.files(self.package).iterdir()
35
+ )
33
36
 
34
37
  def __fspath__(self):
35
- return resources.path(self.package, self.name).__fspath__()
38
+ """Return the file system path representation of the object"""
39
+ return resources.as_file(resources.files(self.package) / self.name)
36
40
 
37
41
  @contextmanager
38
42
  def open(self, *args, **kwargs):
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: experimaestro
3
- Version: 1.6.1
3
+ Version: 1.7.0rc0
4
4
  Summary: "Experimaestro is a computer science experiment manager"
5
5
  License: GPL-3
6
6
  Keywords: experiment manager
7
7
  Author: Benjamin Piwowarski
8
8
  Author-email: benjamin@piwowarski.fr
9
- Requires-Python: >=3.8,<4.0
9
+ Requires-Python: >=3.9,<4.0
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Science/Research
12
12
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -14,7 +14,6 @@ Classifier: License :: Other/Proprietary License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.8
18
17
  Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
@@ -35,7 +34,7 @@ Requires-Dist: huggingface-hub (>0.17)
35
34
  Requires-Dist: humanfriendly (>=10,<11)
36
35
  Requires-Dist: marshmallow (>=3.20,<4.0)
37
36
  Requires-Dist: omegaconf (>=2.3,<3.0)
38
- Requires-Dist: psutil (>=5.9.5,<6.0.0)
37
+ Requires-Dist: psutil (>=7)
39
38
  Requires-Dist: pyparsing (>=3.1,<4.0)
40
39
  Requires-Dist: pytools (>=2023.1.1,<2024.0.0)
41
40
  Requires-Dist: pyyaml (>=6.0.1,<7.0.0)