experimaestro 1.13.0__py3-none-any.whl → 1.14.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.

@@ -80,10 +80,12 @@ class Argument:
80
80
 
81
81
  self.generator = generator
82
82
  self.default = None
83
+ self.ignore_generated = False
83
84
 
84
85
  if default is not None:
85
86
  assert self.generator is None, "generator and default are exclusive options"
86
87
  if isinstance(default, field):
88
+ self.ignore_generated = default.ignore_generated
87
89
  if default.default is not None:
88
90
  self.default = default.default
89
91
  elif default.default_factory is not None:
@@ -184,13 +186,20 @@ DataPath = Annotated[Path, dataHint]
184
186
  class field:
185
187
  """Extra information for a given experimaestro field (param or meta)"""
186
188
 
187
- def __init__(self, *, default: Any = None, default_factory: Callable = None):
189
+ def __init__(
190
+ self,
191
+ *,
192
+ default: Any = None,
193
+ default_factory: Callable = None,
194
+ ignore_generated=False,
195
+ ):
188
196
  assert not (
189
197
  (default is not None) and (default_factory is not None)
190
198
  ), "default and default_factory are mutually exclusive options"
191
199
 
192
200
  self.default_factory = default_factory
193
201
  self.default = default
202
+ self.ignore_generated = ignore_generated
194
203
 
195
204
 
196
205
  class help(TypeAnnotation):
@@ -170,10 +170,33 @@ class ConfigInformation:
170
170
  self._sealed = False
171
171
  self._meta = None
172
172
 
173
- #: This flags is True when a value in this configuration,
174
- #: or any sub-configuration, is generated. This prevents problem
175
- #: when a configuration with generated values is re-used
176
- self._has_generated_value = False
173
+ # This contains the list of generated values (using context) in this
174
+ # configuration or any sub-configuration, is generated. This prevents
175
+ # problem when a configuration with generated values is re-used.
176
+ self._generated_values = []
177
+
178
+ def get_generated_paths(
179
+ self, path: list[str] = None, paths: list[str] = None
180
+ ) -> list[str]:
181
+ """Get the list of generated paths, useful to track down those
182
+
183
+ :param path: The current path
184
+ :param paths: The list of generated paths so far, defaults to None
185
+ :return: The full list of generated paths
186
+ """
187
+ paths = [] if paths is None else paths
188
+ path = [] if path is None else path
189
+
190
+ for key in self._generated_values:
191
+ value = self.values[key]
192
+ if isinstance(value, ConfigMixin) and value.__xpm__._generated_values:
193
+ path.append(key)
194
+ value.__xpm__.get_generated_paths(path, paths)
195
+ path.pop()
196
+ else:
197
+ paths.append(".".join(path + [key]))
198
+
199
+ return paths
177
200
 
178
201
  def set_meta(self, value: Optional[bool]):
179
202
  """Sets the meta flag"""
@@ -192,6 +215,31 @@ class ConfigInformation:
192
215
  # Not an argument, bypass
193
216
  return object.__getattribute__(self.pyobject, name)
194
217
 
218
+ @staticmethod
219
+ def is_generated_value(argument, value):
220
+ if argument.ignore_generated:
221
+ return False
222
+
223
+ if value is None:
224
+ return False
225
+
226
+ if isinstance(value, (int, str, float, bool, Path)):
227
+ return False
228
+
229
+ if isinstance(value, ConfigMixin):
230
+ return value.__xpm__._generated_values and value.__xpm__.task is None
231
+
232
+ if isinstance(value, list):
233
+ return any(ConfigInformation.is_generated_value(argument, x) for x in value)
234
+
235
+ if isinstance(value, dict):
236
+ return any(
237
+ ConfigInformation.is_generated_value(argument, x)
238
+ for x in value.values()
239
+ )
240
+
241
+ return False
242
+
195
243
  def set(self, k, v, bypass=False):
196
244
  from experimaestro.generators import Generator
197
245
 
@@ -208,18 +256,16 @@ class ConfigInformation:
208
256
  "Configuration (and not objects) should be used. Consider using .C(...)"
209
257
  )
210
258
 
211
- if (
212
- isinstance(v, ConfigMixin)
213
- and v.__xpm__._has_generated_value
214
- and v.__xpm__.task is None
215
- ):
216
- raise AttributeError(
217
- f"Cannot set {k} to a configuration with generated values"
218
- )
219
-
220
259
  try:
221
260
  argument = self.xpmtype.arguments.get(k, None)
222
261
  if argument:
262
+ if ConfigInformation.is_generated_value(argument, v):
263
+ raise AttributeError(
264
+ f"Cannot set {k} to a configuration with generated values. "
265
+ "Here is the list of paths to help you: "
266
+ f"""{', '.join(v.__xpm__.get_generated_paths([k]))}"""
267
+ )
268
+
223
269
  if not bypass and (
224
270
  (isinstance(argument.generator, Generator)) or argument.constant
225
271
  ):
@@ -345,6 +391,15 @@ class ConfigInformation:
345
391
  Arguments:
346
392
  - context: the generation context
347
393
  """
394
+ if generated_keys := [
395
+ k
396
+ for k, v in self.values.items()
397
+ if ConfigInformation.is_generated_value(self.xpmtype.arguments[k], v)
398
+ ]:
399
+ raise AttributeError(
400
+ "Cannot seal a configuration with generated values:"
401
+ f"""{",".join(generated_keys)} in {context.currentpath}"""
402
+ )
348
403
 
349
404
  class Sealer(ConfigWalk):
350
405
  def preprocess(self, config: ConfigMixin):
@@ -368,13 +423,15 @@ class ConfigInformation:
368
423
  if len(sig.parameters) == 0:
369
424
  value = argument.generator()
370
425
  elif len(sig.parameters) == 2:
426
+ # Only in that case do we need to flag this configuration
427
+ # as containing generated values
428
+ config.__xpm__._generated_values.append(k)
371
429
  value = argument.generator(self.context, config)
372
430
  else:
373
431
  assert (
374
432
  False
375
433
  ), "generator has either two parameters (context and config), or none"
376
434
  config.__xpm__.set(k, value, bypass=True)
377
- config.__xpm__._has_generated_value = True
378
435
  else:
379
436
  value = config.__xpm__.values.get(k)
380
437
  except Exception:
@@ -387,9 +444,9 @@ class ConfigInformation:
387
444
  if (
388
445
  (value is not None)
389
446
  and isinstance(value, ConfigMixin)
390
- and value.__xpm__._has_generated_value
447
+ and value.__xpm__._generated_values
391
448
  ):
392
- self._has_generated_value = True
449
+ config.__xpm__._generated_values.append(k)
393
450
 
394
451
  config.__xpm__._sealed = True
395
452
 
@@ -71,6 +71,7 @@ class ConfigWalk:
71
71
  return self.context.push(str(i))
72
72
 
73
73
  def map(self, k: str):
74
+ """Provides a path context when processing a tree"""
74
75
  return self.context.push(k)
75
76
 
76
77
  def stub(self, config):
@@ -123,7 +124,8 @@ class ConfigWalk:
123
124
  and self.recurse_task
124
125
  and x.__xpm__.task is not x
125
126
  ):
126
- self(x.__xpm__.task)
127
+ with self.map("__task__"):
128
+ self(x.__xpm__.task)
127
129
 
128
130
  processed = self.postprocess(stub, x, result)
129
131
  self.visited[xid] = processed
@@ -13,8 +13,35 @@ class Learner(Task):
13
13
  validation: Param[Validation]
14
14
  x: Param[int]
15
15
 
16
+ @staticmethod
17
+ def create(x: int, validation: Param[Validation]):
18
+ return Learner.C(x=x, validation=validation)
16
19
 
17
- def test_generators_reuse():
20
+
21
+ class LearnerList(Task):
22
+ validation: Param[list[Validation]]
23
+ x: Param[int]
24
+
25
+ @staticmethod
26
+ def create(x: int, validation: Param[Validation]):
27
+ return LearnerList.C(x=x, validation=[validation])
28
+
29
+
30
+ class LearnerDict(Task):
31
+ validation: Param[dict[str, Validation]]
32
+ x: Param[int]
33
+
34
+ @staticmethod
35
+ def create(x: int, validation: Param[Validation]):
36
+ return LearnerDict.C(x=x, validation={"key": validation})
37
+
38
+
39
+ class ModuleLoader(Task):
40
+ validation: Param[Validation] = field(ignore_generated=True)
41
+
42
+
43
+ @pytest.mark.parametrize("cls", [Learner, LearnerDict, LearnerList])
44
+ def test_generators_reuse_on_submit(cls):
18
45
  # We have one way to select the best model
19
46
  validation = Validation.C()
20
47
 
@@ -25,9 +52,42 @@ def test_generators_reuse():
25
52
  )
26
53
 
27
54
  # OK, the path is generated depending on Learner with x=1
28
- Learner.C(x=1, validation=validation).submit(workspace=workspace)
55
+ cls.create(1, validation).submit(workspace=workspace)
29
56
 
30
57
  with pytest.raises((AttributeError)):
31
58
  # Here we have a problem...
32
59
  # the path is still the previous one
33
- Learner.C(x=2, validation=validation).submit(workspace=workspace)
60
+ cls.create(2, validation).submit(workspace=workspace)
61
+
62
+
63
+ @pytest.mark.parametrize("cls", [Learner, LearnerDict, LearnerList])
64
+ def test_generators_delayed_submit(cls):
65
+ workspace = Workspace(
66
+ Settings(),
67
+ WorkspaceSettings("test_generators_simple", path=Path("/tmp")),
68
+ run_mode=RunMode.DRY_RUN,
69
+ )
70
+ validation = Validation.C()
71
+ task1 = cls.create(1, validation)
72
+ task2 = cls.create(2, validation)
73
+ task1.submit(workspace=workspace)
74
+ with pytest.raises((AttributeError)):
75
+ task2.submit(workspace=workspace)
76
+
77
+
78
+ @pytest.mark.parametrize("cls", [Learner, LearnerDict, LearnerList])
79
+ def test_generators_reuse_on_set(cls):
80
+ workspace = Workspace(
81
+ Settings(),
82
+ WorkspaceSettings("test_generators_simple", path=Path("/tmp")),
83
+ run_mode=RunMode.DRY_RUN,
84
+ )
85
+ validation = Validation.C()
86
+ cls.create(1, validation).submit(workspace=workspace)
87
+ with pytest.raises((AttributeError)):
88
+ # We should not be able to *create* a second task with the same validation,
89
+ # even without submitting it
90
+ cls.create(2, validation)
91
+
92
+ # This should run OK
93
+ ModuleLoader.C(validation=validation)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: experimaestro
3
- Version: 1.13.0
3
+ Version: 1.14.0
4
4
  Summary: "Experimaestro is a computer science experiment manager"
5
5
  License: GPL-3
6
6
  License-File: LICENSE
@@ -12,14 +12,14 @@ experimaestro/connectors/__init__.py,sha256=UKhDU3uv9jFH37oUb0JiejrekA85xtEirn79
12
12
  experimaestro/connectors/local.py,sha256=lCGIubqmUJZ1glLtLRXOgakTMfEaEmFtNkEcw9qV5vw,6143
13
13
  experimaestro/connectors/ssh.py,sha256=5giqvv1y0QQKF-GI0IFUzI_Z5H8Bj9EuL_Szpvk899Q,8600
14
14
  experimaestro/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- experimaestro/core/arguments.py,sha256=7hpkU1f8LJ7JL8kQaD514h9CFSfMotYLsVfMsMmdpWk,6487
15
+ experimaestro/core/arguments.py,sha256=jlK2_EILKLblLSomUDjlGmR-_xq3rAZPgAwbUjU4boc,6710
16
16
  experimaestro/core/callbacks.py,sha256=59JfeUgWcCCdIQ3pvh-xNnoRp9BX8f4iOAkgm16wBzE,1660
17
17
  experimaestro/core/context.py,sha256=1tLmX7WcgEKSbGw77vfziTzS8KNsoZJ02JBWMBCqqOk,2606
18
18
  experimaestro/core/identifier.py,sha256=JadBAdW2fOIgoTyMRtKI_RY9SXQP2B1vq1rUcV465Hs,10214
19
19
  experimaestro/core/objects/__init__.py,sha256=ucJY5e17QQ1Kc-GYXeL7g8GFj8rP0XB4g2vrl32uhxY,721
20
- experimaestro/core/objects/config.py,sha256=QDmD82NyWN9OQ_UVSV6WaPd1srKHVXeAmfcEW1Fw9Ps,58080
20
+ experimaestro/core/objects/config.py,sha256=ycZx6252pNmJadpSiu_1jd-LsiHw7kWWufkWH0vcOPc,60293
21
21
  experimaestro/core/objects/config_utils.py,sha256=ZLECGkeIWdzunm8vwWsQhvcSgV1e064BgXbLiZnxSEM,1288
22
- experimaestro/core/objects/config_walk.py,sha256=gyDMrVPbBMChn7r4em_gQXuqnxASO_JVauEbnJNO8II,4245
22
+ experimaestro/core/objects/config_walk.py,sha256=b8u6oohf1gXyva4Y_Cyyl_3BNivzI2y-I2B6MUPV2aU,4353
23
23
  experimaestro/core/objects.pyi,sha256=xvlsRj4u1xsJxbevJl5Ner_HwmxR8x1JlAeIVDJzuy0,6498
24
24
  experimaestro/core/serialization.py,sha256=CSPEwOzlDsgAz6V2og-TgyU0RXDtzt_nXaoXFZleDZE,5775
25
25
  experimaestro/core/serializers.py,sha256=R_CAMyjjfU1oi-eHU6VlEUixJpFayGqEPaYu7VsD9xA,1197
@@ -121,7 +121,7 @@ experimaestro/tests/test_dependencies.py,sha256=xfWrSkvjT45G4FSCL535m1huLT2ghmyW
121
121
  experimaestro/tests/test_experiment.py,sha256=QWF9aHewL9hepagrKKFyfikKn3iiZ_lRRXl1LLttta0,1687
122
122
  experimaestro/tests/test_findlauncher.py,sha256=KPy8ow--NXS1KFCIpxrmEJFRvjo-v-PwlVHVyoVKLPg,3134
123
123
  experimaestro/tests/test_forward.py,sha256=9y1zYm7hT_Lx5citxnK7n20cMZ2WJbsaEeY5irCZ9U4,735
124
- experimaestro/tests/test_generators.py,sha256=Cx0jDI8JA_tFsEfg9EkV353iKxBd6j_1560p_66_oNU,1040
124
+ experimaestro/tests/test_generators.py,sha256=R0UypTzxX0dPYvY4A_kozpLDHhGzQDNfLQyc0oRaSx8,2891
125
125
  experimaestro/tests/test_identifier.py,sha256=zgfpz5UumQftQTHhdw3nmr044Xd7Ntrh7e2hIAoS_PA,13862
126
126
  experimaestro/tests/test_instance.py,sha256=h-8UeoOlNsD-STWq5jbhmxw35CyiwJxtKAaUGmLPgB8,1521
127
127
  experimaestro/tests/test_objects.py,sha256=zycxjvWuJAbPR8-q2T3zuBY9xfmlhf1YvtOcrImHxnc,2431
@@ -152,8 +152,8 @@ experimaestro/utils/multiprocessing.py,sha256=am3DkHP_kmWbpynbck2c9QystCUtPBoSAC
152
152
  experimaestro/utils/resources.py,sha256=j-nvsTFwmgENMoVGOD2Ap-UD3WU85WkI0IgeSszMCX4,1328
153
153
  experimaestro/utils/settings.py,sha256=jpFMqF0DLL4_P1xGal0zVR5cOrdD8O0Y2IOYvnRgN3k,793
154
154
  experimaestro/xpmutils.py,sha256=S21eMbDYsHfvmZ1HmKpq5Pz5O-1HnCLYxKbyTBbASyQ,638
155
- experimaestro-1.13.0.dist-info/METADATA,sha256=UbZQxb19lKM9jI8HI8aZli0ZmOck1UflmQmN6VHvDEI,5705
156
- experimaestro-1.13.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
157
- experimaestro-1.13.0.dist-info/entry_points.txt,sha256=TppTNiz5qm5xm1fhAcdLKdCLMrlL-eQggtCrCI00D9c,446
158
- experimaestro-1.13.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
159
- experimaestro-1.13.0.dist-info/RECORD,,
155
+ experimaestro-1.14.0.dist-info/METADATA,sha256=qf7GOrTHf73pHUHBc2APqLSWvhCKbVhuPBOZmX53dpw,5705
156
+ experimaestro-1.14.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
157
+ experimaestro-1.14.0.dist-info/entry_points.txt,sha256=TppTNiz5qm5xm1fhAcdLKdCLMrlL-eQggtCrCI00D9c,446
158
+ experimaestro-1.14.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
159
+ experimaestro-1.14.0.dist-info/RECORD,,