experimaestro 1.11.1__py3-none-any.whl → 2.0.0a3__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 (39) hide show
  1. experimaestro/annotations.py +1 -1
  2. experimaestro/cli/__init__.py +10 -11
  3. experimaestro/cli/progress.py +269 -0
  4. experimaestro/core/arguments.py +20 -1
  5. experimaestro/core/identifier.py +11 -2
  6. experimaestro/core/objects/config.py +119 -97
  7. experimaestro/core/objects/config_walk.py +3 -1
  8. experimaestro/core/types.py +35 -57
  9. experimaestro/launcherfinder/registry.py +3 -3
  10. experimaestro/mkdocs/base.py +6 -8
  11. experimaestro/notifications.py +12 -3
  12. experimaestro/progress.py +406 -0
  13. experimaestro/settings.py +4 -2
  14. experimaestro/tests/launchers/common.py +2 -2
  15. experimaestro/tests/restart.py +1 -1
  16. experimaestro/tests/test_checkers.py +2 -2
  17. experimaestro/tests/test_dependencies.py +12 -12
  18. experimaestro/tests/test_experiment.py +3 -3
  19. experimaestro/tests/test_file_progress.py +425 -0
  20. experimaestro/tests/test_file_progress_integration.py +477 -0
  21. experimaestro/tests/test_generators.py +68 -0
  22. experimaestro/tests/test_identifier.py +90 -81
  23. experimaestro/tests/test_instance.py +16 -9
  24. experimaestro/tests/test_objects.py +9 -32
  25. experimaestro/tests/test_outputs.py +6 -6
  26. experimaestro/tests/test_param.py +14 -14
  27. experimaestro/tests/test_progress.py +4 -4
  28. experimaestro/tests/test_serializers.py +5 -5
  29. experimaestro/tests/test_tags.py +15 -15
  30. experimaestro/tests/test_tasks.py +40 -36
  31. experimaestro/tests/test_tokens.py +8 -6
  32. experimaestro/tests/test_types.py +10 -10
  33. experimaestro/tests/test_validation.py +19 -19
  34. experimaestro/tests/token_reschedule.py +1 -1
  35. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0a3.dist-info}/METADATA +1 -1
  36. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0a3.dist-info}/RECORD +39 -34
  37. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0a3.dist-info}/LICENSE +0 -0
  38. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0a3.dist-info}/WHEEL +0 -0
  39. {experimaestro-1.11.1.dist-info → experimaestro-2.0.0a3.dist-info}/entry_points.txt +0 -0
@@ -25,7 +25,6 @@ from typing import (
25
25
  Optional,
26
26
  Set,
27
27
  Tuple,
28
- Type,
29
28
  TypeVar,
30
29
  Union,
31
30
  overload,
@@ -122,11 +121,11 @@ class ConfigInformation:
122
121
  def __init__(self, pyobject: "ConfigMixin"):
123
122
  # The underlying pyobject and XPM type
124
123
  self.pyobject = pyobject
125
- self.xpmtype = pyobject.__xpmtype__ # type: ObjectType
124
+ self.xpmtype: "ObjectType" = pyobject.__xpmtype__
126
125
  self.values = {}
127
126
 
128
127
  # Meta-informations
129
- self._tags = {}
128
+ self._tags: dict[str, Any] = {}
130
129
  self._initinfo = ""
131
130
 
132
131
  self._taskoutput = None
@@ -142,7 +141,7 @@ class ConfigInformation:
142
141
  #: True when this configuration was loaded from disk
143
142
  self.loaded = False
144
143
 
145
- # Explicitely added dependencies
144
+ # Explicitly added dependencies
146
145
  self.dependencies = []
147
146
 
148
147
  # Concrete type variables resolutions
@@ -170,6 +169,34 @@ class ConfigInformation:
170
169
  self._sealed = False
171
170
  self._meta = None
172
171
 
172
+ # This contains the list of generated values (using context) in this
173
+ # configuration or any sub-configuration, is generated. This prevents
174
+ # problem when a configuration with generated values is re-used.
175
+ self._generated_values = []
176
+
177
+ def get_generated_paths(
178
+ self, path: list[str] = None, paths: list[str] = None
179
+ ) -> list[str]:
180
+ """Get the list of generated paths, useful to track down those
181
+
182
+ :param path: The current path
183
+ :param paths: The list of generated paths so far, defaults to None
184
+ :return: The full list of generated paths
185
+ """
186
+ paths = [] if paths is None else paths
187
+ path = [] if path is None else path
188
+
189
+ for key in self._generated_values:
190
+ value = self.values[key]
191
+ if isinstance(value, ConfigMixin) and value.__xpm__._generated_values:
192
+ path.append(key)
193
+ value.__xpm__.get_generated_paths(path, paths)
194
+ path.pop()
195
+ else:
196
+ paths.append(".".join(path + [key]))
197
+
198
+ return paths
199
+
173
200
  def set_meta(self, value: Optional[bool]):
174
201
  """Sets the meta flag"""
175
202
  assert not self._sealed, "Configuration is sealed"
@@ -198,9 +225,26 @@ class ConfigInformation:
198
225
  if self._sealed and not bypass:
199
226
  raise AttributeError(f"Object is read-only (trying to set {k})")
200
227
 
228
+ if not isinstance(v, ConfigMixin) and isinstance(v, Config):
229
+ raise AttributeError(
230
+ "Configuration (and not objects) should be used. Consider using .C(...)"
231
+ )
232
+
201
233
  try:
202
234
  argument = self.xpmtype.arguments.get(k, None)
203
235
  if argument:
236
+ if (
237
+ isinstance(v, ConfigMixin)
238
+ and v.__xpm__._generated_values
239
+ and v.__xpm__.task is None
240
+ and not argument.ignore_generated
241
+ ):
242
+ raise AttributeError(
243
+ f"Cannot set {k} to a configuration with generated values. "
244
+ "Here is the list of paths to help you: "
245
+ f"""{', '.join(v.__xpm__.get_generated_paths([k]))}"""
246
+ )
247
+
204
248
  if not bypass and (
205
249
  (isinstance(argument.generator, Generator)) or argument.constant
206
250
  ):
@@ -326,12 +370,23 @@ class ConfigInformation:
326
370
  Arguments:
327
371
  - context: the generation context
328
372
  """
373
+ if generated_keys := [
374
+ k
375
+ for k, v in self.values.items()
376
+ if isinstance(v, Config)
377
+ and v.__xpm__.task is None
378
+ and v.__xpm__._generated_values
379
+ ]:
380
+ raise AttributeError(
381
+ "Cannot seal a configuration with generated values:"
382
+ f"""{",".join(generated_keys)} in {context.currentpath}"""
383
+ )
329
384
 
330
385
  class Sealer(ConfigWalk):
331
- def preprocess(self, config: Config):
386
+ def preprocess(self, config: ConfigMixin):
332
387
  return not config.__xpm__._sealed, config
333
388
 
334
- def postprocess(self, stub, config: Config, values):
389
+ def postprocess(self, stub, config: ConfigMixin, values):
335
390
  # Generate values
336
391
  from experimaestro.generators import Generator
337
392
 
@@ -344,22 +399,42 @@ class ConfigInformation:
344
399
  continue
345
400
  value = argument.generator()
346
401
  else:
402
+ # Generate a value
347
403
  sig = inspect.signature(argument.generator)
348
404
  if len(sig.parameters) == 0:
349
405
  value = argument.generator()
350
406
  elif len(sig.parameters) == 2:
407
+ # Only in that case do we need to flag this configuration
408
+ # as containing generated values
409
+ if not argument.ignore_generated:
410
+ config.__xpm__._generated_values.append(k)
411
+ else:
412
+ logging.warning("Ignoring %s", k)
351
413
  value = argument.generator(self.context, config)
352
414
  else:
353
415
  assert (
354
416
  False
355
417
  ), "generator has either two parameters (context and config), or none"
356
418
  config.__xpm__.set(k, value, bypass=True)
419
+ else:
420
+ value = config.__xpm__.values.get(k)
357
421
  except Exception:
358
422
  logger.error(
359
423
  "While setting %s of %s", argument.name, config.__xpmtype__
360
424
  )
361
425
  raise
362
426
 
427
+ # Propagate the generated value flag
428
+ if (
429
+ value is not None
430
+ and isinstance(value, ConfigMixin)
431
+ and value.__xpm__._generated_values
432
+ ):
433
+ if not argument.ignore_generated:
434
+ config.__xpm__._generated_values.append(k)
435
+ else:
436
+ logging.warning("Ignoring %s", k)
437
+
363
438
  config.__xpm__._sealed = True
364
439
 
365
440
  Sealer(context, recurse_task=True)(self.pyobject)
@@ -657,13 +732,6 @@ class ConfigInformation:
657
732
 
658
733
  print(file=sys.stderr) # noqa: T201
659
734
 
660
- # Handle an output configuration # FIXME: remove
661
- def mark_output(config: "Config"):
662
- """Sets a dependency on the job"""
663
- assert not isinstance(config, Task), "Cannot set a dependency on a task"
664
- config.__xpm__.task = self.pyobject
665
- return config
666
-
667
735
  # Mark this configuration also
668
736
  self.task = self.pyobject
669
737
 
@@ -677,6 +745,9 @@ class ConfigInformation:
677
745
  def mark_output(self, config: "Config"):
678
746
  """Sets a dependency on the job"""
679
747
  assert not isinstance(config, Task), "Cannot set a dependency on a task"
748
+ assert isinstance(
749
+ config, ConfigMixin
750
+ ), "Only configurations can be marked as dependent on a task"
680
751
  config.__xpm__.task = self.pyobject
681
752
  return config
682
753
 
@@ -762,7 +833,7 @@ class ConfigInformation:
762
833
  state_dict = {
763
834
  "id": id(self.pyobject),
764
835
  "module": self.xpmtype._module,
765
- "type": self.xpmtype.basetype.__qualname__,
836
+ "type": self.xpmtype.value_type.__qualname__,
766
837
  "typename": self.xpmtype.name(),
767
838
  "identifier": self.identifier.state_dict(),
768
839
  }
@@ -1022,7 +1093,7 @@ class ConfigInformation:
1022
1093
 
1023
1094
  # Creates an object (or a config)
1024
1095
  if as_instance:
1025
- o = cls.XPMValue.__new__(cls.XPMValue)
1096
+ o = cls.__new__(cls)
1026
1097
  else:
1027
1098
  o = cls.XPMConfig.__new__(cls.XPMConfig)
1028
1099
  assert definition["id"] not in objects, "Duplicate id %s" % definition["id"]
@@ -1183,7 +1254,7 @@ class ConfigInformation:
1183
1254
 
1184
1255
  if o is None:
1185
1256
  # Creates an object (and not a config)
1186
- o = config.XPMValue()
1257
+ o = config.__xpmtype__.value_type()
1187
1258
 
1188
1259
  # Store in cache
1189
1260
  self.objects.add_stub(id(config), o)
@@ -1242,6 +1313,9 @@ def clone(v):
1242
1313
  if isinstance(v, Enum):
1243
1314
  return v
1244
1315
 
1316
+ if isinstance(v, tuple):
1317
+ return tuple(clone(x) for x in v)
1318
+
1245
1319
  if isinstance(v, Config):
1246
1320
  # Create a new instance
1247
1321
  kwargs = {
@@ -1260,6 +1334,11 @@ class ConfigMixin:
1260
1334
  """Class for configuration objects"""
1261
1335
 
1262
1336
  __xpmtype__: ObjectType
1337
+ """The associated XPM type"""
1338
+
1339
+ __xpm__: ConfigInformation
1340
+ """The __xpm__ object contains all instance specific information about a
1341
+ configuration/task"""
1263
1342
 
1264
1343
  def __init__(self, **kwargs):
1265
1344
  """Initialize the configuration with the given parameters"""
@@ -1310,8 +1389,8 @@ class ConfigMixin:
1310
1389
  [f"{key}={value}" for key, value in self.__xpm__.values.items()]
1311
1390
  )
1312
1391
  return (
1313
- f"{self.__xpmtype__.basetype.__module__}."
1314
- f"{self.__xpmtype__.basetype.__qualname__}({params})"
1392
+ f"{self.__xpmtype__.value_type.__module__}."
1393
+ f"{self.__xpmtype__.value_type.__qualname__}({params})"
1315
1394
  )
1316
1395
 
1317
1396
  def tag(self, name, value):
@@ -1340,9 +1419,20 @@ class ConfigMixin:
1340
1419
  return self
1341
1420
 
1342
1421
  def instance(
1343
- self, context: ConfigWalkContext = None, *, objects: ObjectStore = None
1422
+ self,
1423
+ context: ConfigWalkContext = None,
1424
+ *,
1425
+ objects: ObjectStore = None,
1426
+ keep: bool = True,
1344
1427
  ) -> T:
1345
- """Return an instance with the current values"""
1428
+ """Return an instance with the current values
1429
+
1430
+ :param context: The context when computing the instance
1431
+ :param objects: The previously built objects (so that we avoid
1432
+ re-creating instances of past configurations)
1433
+ :param keep: register a configuration in the __config__ field of the
1434
+ instance
1435
+ """
1346
1436
  if context is None:
1347
1437
  from experimaestro.xpmutils import EmptyContext
1348
1438
 
@@ -1351,7 +1441,11 @@ class ConfigMixin:
1351
1441
  assert isinstance(
1352
1442
  context, ConfigWalkContext
1353
1443
  ), f"{context.__class__} is not an instance of ConfigWalkContext"
1354
- return self.__xpm__.fromConfig(context, objects=objects) # type: ignore
1444
+
1445
+ instance = self.__xpm__.fromConfig(context, objects=objects) # type: ignore
1446
+ if keep:
1447
+ object.__setattr__(instance, "__config__", self)
1448
+ return instance
1355
1449
 
1356
1450
  def submit(
1357
1451
  self,
@@ -1397,6 +1491,9 @@ class ConfigMixin:
1397
1491
  return clone(self)
1398
1492
 
1399
1493
  def add_pretasks(self, *tasks: "LightweightTask"):
1494
+ assert all(
1495
+ [isinstance(task, ConfigMixin) for task in tasks]
1496
+ ), "One of the parameters is not a configuration object"
1400
1497
  assert all(
1401
1498
  [isinstance(task, LightweightTask) for task in tasks]
1402
1499
  ), "One of the pre-tasks are not lightweight tasks"
@@ -1441,52 +1538,17 @@ class Config:
1441
1538
  """The object type holds all the information about a specific subclass
1442
1539
  experimaestro metadata"""
1443
1540
 
1444
- __xpm__: ConfigInformation
1445
- """The __xpm__ object contains all instance specific information about a
1446
- configuration/task"""
1447
-
1448
1541
  @classproperty
1449
1542
  def XPMConfig(cls):
1450
1543
  if issubclass(cls, ConfigMixin):
1451
1544
  return cls
1452
- return cls.__getxpmtype__().configtype
1453
-
1454
- @classproperty
1455
- def XPMValue(cls):
1456
- """Returns the value object for this configuration"""
1457
- if issubclass(cls, ConfigMixin):
1458
- return cls.__xpmtype__.objecttype
1459
-
1460
- if value_cls := cls.__dict__.get("__XPMValue__", None):
1461
- pass
1462
- else:
1463
- from ..types import XPMValue
1464
-
1465
- __objectbases__ = tuple(
1466
- s.XPMValue
1467
- for s in cls.__bases__
1468
- if issubclass(s, Config) and (s is not Config)
1469
- ) or (XPMValue,)
1470
-
1471
- *tp_qual, tp_name = cls.__qualname__.split(".")
1472
- value_cls = type(f"{tp_name}.XPMValue", (cls,) + __objectbases__, {})
1473
- value_cls.__qualname__ = ".".join(tp_qual + [value_cls.__name__])
1474
- value_cls.__module__ = cls.__module__
1475
-
1476
- setattr(cls, "__XPMValue__", value_cls)
1477
-
1478
- return value_cls
1545
+ return cls.__getxpmtype__().config_type
1479
1546
 
1480
1547
  @classproperty
1481
1548
  def C(cls):
1482
1549
  """Alias for XPMConfig"""
1483
1550
  return cls.XPMConfig
1484
1551
 
1485
- @classproperty
1486
- def V(cls):
1487
- """Alias for XPMValue"""
1488
- return cls.XPMValue
1489
-
1490
1552
  @classmethod
1491
1553
  def __getxpmtype__(cls) -> "ObjectType":
1492
1554
  """Get (and create if necessary) the Object type associated
@@ -1503,46 +1565,6 @@ class Config:
1503
1565
  raise
1504
1566
  return xpmtype
1505
1567
 
1506
- def __new__(cls: Type[T], *args, **kwargs) -> T:
1507
- """Returns an instance of a ConfigMixin (for compatibility, use XPMConfig
1508
- or C if possible)
1509
-
1510
- :deprecated: Use Config.C or Config.XPMConfig to construct a new
1511
- configuration, and Config.V (or Config.XPMValue) for a new value
1512
- """
1513
- # If this is an XPMValue, just return a new instance
1514
- from experimaestro.core.types import XPMValue
1515
-
1516
- if issubclass(cls, XPMValue):
1517
- return object.__new__(cls)
1518
-
1519
- # If this is the XPMConfig, just return a new instance
1520
- # __init__ will be called
1521
- if issubclass(cls, ConfigMixin):
1522
- return object.__new__(cls)
1523
-
1524
- # Log a deprecation warning for this way of creating a configuration
1525
- caller = inspect.getframeinfo(inspect.stack()[1][0])
1526
- logger.warning(
1527
- "Creating a configuration using Config.__new__ is deprecated, and will be removed in a future version. "
1528
- "Use Config.C or Config.XPMConfig to create a new configuration. "
1529
- "Issue created at %s:%s",
1530
- str(Path(caller.filename).absolute()),
1531
- caller.lineno,
1532
- )
1533
-
1534
- # otherwise, we use the configuration type
1535
- o: ConfigMixin = object.__new__(cls.__getxpmtype__().configtype)
1536
- try:
1537
- o.__init__(*args, **kwargs)
1538
- except Exception:
1539
- logger.error(
1540
- "Init error in %s:%s"
1541
- % (str(Path(caller.filename).absolute()), caller.lineno)
1542
- )
1543
- raise
1544
- return o
1545
-
1546
1568
  def __validate__(self):
1547
1569
  """Validate the values"""
1548
1570
  pass
@@ -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
@@ -203,18 +203,14 @@ class ObjectType(Type):
203
203
  """ObjectType contains class-level information about
204
204
  experimaestro configurations and tasks
205
205
 
206
- :param objecttype: The python Type of the associated object
207
- :param configtype: The python Type of the configuration object that uses
208
- property for arguments
206
+ :param value_type: The Python type of the associated object
207
+ :param config_type: The Python type of the configuration object
209
208
  """
210
209
 
211
- # Those entries should not be copied in the __dict__
212
- FORBIDDEN_KEYS = set(("__dict__", "__weakref__"))
213
-
214
210
  def __init__(
215
211
  self,
216
212
  tp: type,
217
- identifier: Union[str, Identifier] = None,
213
+ identifier: Union[str, Identifier, None] = None,
218
214
  ):
219
215
  """Creates a type"""
220
216
  from .objects import Config, ConfigMixin
@@ -225,7 +221,7 @@ class ObjectType(Type):
225
221
  self._title = None
226
222
  self.submit_hooks = set()
227
223
 
228
- # Get the identifier
224
+ # --- Get the identifier
229
225
  if identifier is None and hasattr(tp, "__xpmid__"):
230
226
  __xpmid__ = getattr(tp, "__xpmid__")
231
227
  if isinstance(__xpmid__, Identifier):
@@ -250,59 +246,41 @@ class ObjectType(Type):
250
246
  # --- Creates the config type and not config type
251
247
 
252
248
  self.originaltype = tp
253
- if not issubclass(tp, Config):
254
- # Adds Config as a base class if not present
255
- __bases__ = () if tp.__bases__ == (object,) else tp.__bases__
256
- __dict__ = dict(tp.__dict__)
257
-
258
- __dict__ = {
259
- key: value
260
- for key, value in tp.__dict__.items()
261
- if key not in ObjectType.FORBIDDEN_KEYS
262
- }
263
- self.basetype = type(tp.__name__, (Config,) + __bases__, __dict__)
264
- self.basetype.__module__ = tp.__module__
265
- self.basetype.__qualname__ = tp.__qualname__
266
- else:
267
- self.basetype = tp
249
+ assert issubclass(tp, Config)
250
+ self.value_type = tp
268
251
 
269
252
  # --- Create the type-specific configuration class (XPMConfig)
270
253
  __configbases__ = tuple(
271
- s.__getxpmtype__().configtype
254
+ s.__getxpmtype__().config_type
272
255
  for s in tp.__bases__
273
256
  if issubclass(s, Config) and (s is not Config)
274
257
  ) or (ConfigMixin,)
275
258
 
276
- *tp_qual, tp_name = self.basetype.__qualname__.split(".")
277
- self.configtype = type(
278
- f"{tp_name}.XPMConfig", __configbases__ + (self.basetype,), {}
259
+ *tp_qual, tp_name = self.value_type.__qualname__.split(".")
260
+ self.config_type = type(
261
+ f"{tp_name}.XPMConfig", __configbases__ + (self.value_type,), {}
279
262
  )
280
- self.configtype.__qualname__ = ".".join(tp_qual + [self.configtype.__name__])
281
- self.configtype.__module__ = tp.__module__
263
+ self.config_type.__qualname__ = ".".join(tp_qual + [self.config_type.__name__])
264
+ self.config_type.__module__ = tp.__module__
282
265
 
283
- # Return type is used by tasks to change the output
284
- if hasattr(self.basetype, "task_outputs") or False:
266
+ # --- Get the return type
267
+ if hasattr(self.value_type, "task_outputs") or False:
285
268
  self.returntype = get_type_hints(
286
- getattr(self.basetype, "task_outputs")
269
+ getattr(self.value_type, "task_outputs")
287
270
  ).get("return", typing.Any)
288
271
  else:
289
- self.returntype = self.basetype
272
+ self.returntype = self.value_type
290
273
 
291
- # Registers ourselves
292
- self.basetype.__xpmtype__ = self
293
- self.configtype.__xpmtype__ = self
274
+ # --- Registers ourselves
275
+ self.value_type.__xpmtype__ = self
276
+ self.config_type.__xpmtype__ = self
294
277
 
295
- # Other initializations
278
+ # --- Other initializations
296
279
  self.__initialized__ = False
297
280
  self._runtype = None
298
281
  self.annotations = []
299
282
  self._deprecated = False
300
283
 
301
- @property
302
- def objecttype(self):
303
- """Returns the object type"""
304
- return self.basetype.XPMValue
305
-
306
284
  def addAnnotation(self, annotation):
307
285
  assert not self.__initialized__
308
286
  self.annotations.append(annotation)
@@ -357,15 +335,15 @@ class ObjectType(Type):
357
335
  # Add task
358
336
  if self.taskcommandfactory is not None:
359
337
  self.task = self.taskcommandfactory(self)
360
- elif issubclass(self.basetype, Task):
338
+ elif issubclass(self.value_type, Task):
361
339
  self.task = self.getpythontaskcommand()
362
340
 
363
341
  # Add arguments from type hints
364
342
  from .arguments import TypeAnnotation
365
343
 
366
- if hasattr(self.basetype, "__annotations__"):
367
- typekeys = set(self.basetype.__dict__.get("__annotations__", {}).keys())
368
- hints = get_type_hints(self.basetype, include_extras=True)
344
+ if hasattr(self.value_type, "__annotations__"):
345
+ typekeys = set(self.value_type.__dict__.get("__annotations__", {}).keys())
346
+ hints = get_type_hints(self.value_type, include_extras=True)
369
347
  for key, typehint in hints.items():
370
348
  # Filter out hints from parent classes
371
349
  if key in typekeys:
@@ -378,19 +356,19 @@ class ObjectType(Type):
378
356
  try:
379
357
  self.addArgument(
380
358
  options.create(
381
- key, self.objecttype, typehint.__args__[0]
359
+ key, self.value_type, typehint.__args__[0]
382
360
  )
383
361
  )
384
362
  except Exception:
385
363
  logger.error(
386
364
  "while adding argument %s of %s",
387
365
  key,
388
- self.objecttype,
366
+ self.value_type,
389
367
  )
390
368
  raise
391
369
 
392
370
  def name(self):
393
- return f"{self.basetype.__module__}.{self.basetype.__qualname__}"
371
+ return f"{self.value_type.__module__}.{self.value_type.__qualname__}"
394
372
 
395
373
  def __parsedoc__(self):
396
374
  """Parse the documentation"""
@@ -400,7 +378,7 @@ class ObjectType(Type):
400
378
  self.__initialize__()
401
379
 
402
380
  # Get description from documentation
403
- __doc__ = self.basetype.__dict__.get("__doc__", None)
381
+ __doc__ = self.value_type.__dict__.get("__doc__", None)
404
382
  if __doc__:
405
383
  parseddoc = parse(__doc__)
406
384
  self._title = parseddoc.short_description
@@ -430,7 +408,7 @@ class ObjectType(Type):
430
408
  argname = None
431
409
 
432
410
  def deprecate(self):
433
- if len(self.basetype.__bases__) != 1:
411
+ if len(self.value_type.__bases__) != 1:
434
412
  raise RuntimeError(
435
413
  "Deprecated configurations must have "
436
414
  "only one parent (the new configuration)"
@@ -439,7 +417,7 @@ class ObjectType(Type):
439
417
 
440
418
  # Uses the parent identifier (and saves the deprecated one for path updates)
441
419
  self._deprecated_identifier = self.identifier
442
- parent = self.basetype.__bases__[0].__getxpmtype__()
420
+ parent = self.value_type.__bases__[0].__getxpmtype__()
443
421
  self.identifier = parent.identifier
444
422
  self._deprecated = True
445
423
 
@@ -454,7 +432,7 @@ class ObjectType(Type):
454
432
  return self._description
455
433
 
456
434
  @property
457
- def title(self) -> Dict[str, Argument]:
435
+ def title(self) -> str:
458
436
  self.__parsedoc__()
459
437
  return self._title or str(self.identifier)
460
438
 
@@ -469,7 +447,7 @@ class ObjectType(Type):
469
447
 
470
448
  # The the attribute for the config type
471
449
  setattr(
472
- self.configtype,
450
+ self.config_type,
473
451
  argument.name,
474
452
  property(
475
453
  lambda _self: _self.__xpm__.get(argument.name),
@@ -488,7 +466,7 @@ class ObjectType(Type):
488
466
  def parents(self) -> Iterator["ObjectType"]:
489
467
  from .objects import Config, Task
490
468
 
491
- for tp in self.basetype.__bases__:
469
+ for tp in self.value_type.__bases__:
492
470
  if issubclass(tp, Config) and tp not in [Config, Task]:
493
471
  yield tp.__xpmtype__
494
472
 
@@ -504,7 +482,7 @@ class ObjectType(Type):
504
482
  if not isinstance(value, Config):
505
483
  raise ValueError(f"{value} is not an experimaestro type or task")
506
484
 
507
- types = self.basetype
485
+ types = self.value_type
508
486
 
509
487
  if not isinstance(value, types):
510
488
  raise ValueError(
@@ -519,7 +497,7 @@ class ObjectType(Type):
519
497
 
520
498
  def fullyqualifiedname(self) -> str:
521
499
  """Returns the fully qualified (Python) name"""
522
- return f"{self.basetype.__module__}.{self.basetype.__qualname__}"
500
+ return f"{self.value_type.__module__}.{self.value_type.__qualname__}"
523
501
 
524
502
 
525
503
  class TypeProxy:
@@ -6,7 +6,7 @@ from typing import ClassVar, Dict, Optional, Set, Type, Union
6
6
  from pathlib import Path
7
7
  import typing
8
8
  from omegaconf import DictConfig, OmegaConf, SCMode
9
- import pkg_resources
9
+ from importlib.metadata import entry_points
10
10
  from experimaestro.utils import logger
11
11
  from .base import ConnectorConfiguration, TokenConfiguration
12
12
  from .specs import HostRequirement, RequirementUnion
@@ -75,10 +75,10 @@ class LauncherRegistry:
75
75
  self.find_launcher_fn = None
76
76
 
77
77
  # Use entry points for connectors and launchers
78
- for entry_point in pkg_resources.iter_entry_points("experimaestro.connectors"):
78
+ for entry_point in entry_points(group="experimaestro.connectors"):
79
79
  entry_point.load().init_registry(self)
80
80
 
81
- for entry_point in pkg_resources.iter_entry_points("experimaestro.tokens"):
81
+ for entry_point in entry_points(group="experimaestro.tokens"):
82
82
  entry_point.load().init_registry(self)
83
83
 
84
84
  # Register the find launcher function if it exists