experimaestro 1.11.1__tar.gz → 2.0.0rc0__tar.gz

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 (162) hide show
  1. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/PKG-INFO +1 -1
  2. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/pyproject.toml +8 -2
  3. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/annotations.py +1 -1
  4. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/cli/__init__.py +10 -11
  5. experimaestro-2.0.0rc0/src/experimaestro/cli/progress.py +269 -0
  6. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/identifier.py +11 -2
  7. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/config.py +64 -94
  8. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/types.py +35 -57
  9. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/registry.py +3 -3
  10. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/base.py +6 -8
  11. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/notifications.py +12 -3
  12. experimaestro-2.0.0rc0/src/experimaestro/progress.py +406 -0
  13. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/settings.py +4 -2
  14. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/common.py +2 -2
  15. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/restart.py +1 -1
  16. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_checkers.py +2 -2
  17. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_dependencies.py +12 -12
  18. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_experiment.py +3 -3
  19. experimaestro-2.0.0rc0/src/experimaestro/tests/test_file_progress.py +425 -0
  20. experimaestro-2.0.0rc0/src/experimaestro/tests/test_file_progress_integration.py +477 -0
  21. experimaestro-2.0.0rc0/src/experimaestro/tests/test_generators.py +61 -0
  22. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_identifier.py +90 -81
  23. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_instance.py +9 -9
  24. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_objects.py +9 -32
  25. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_outputs.py +6 -6
  26. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_param.py +14 -14
  27. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_progress.py +4 -4
  28. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_serializers.py +5 -5
  29. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_tags.py +15 -15
  30. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_tasks.py +40 -36
  31. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_tokens.py +8 -6
  32. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_types.py +10 -10
  33. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_validation.py +19 -19
  34. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/token_reschedule.py +1 -1
  35. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/LICENSE +0 -0
  36. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/README.md +0 -0
  37. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/__init__.py +0 -0
  38. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/__main__.py +0 -0
  39. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/checkers.py +0 -0
  40. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/cli/filter.py +0 -0
  41. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/cli/jobs.py +0 -0
  42. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/click.py +0 -0
  43. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/commandline.py +0 -0
  44. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/compat.py +0 -0
  45. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/connectors/__init__.py +0 -0
  46. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/connectors/local.py +0 -0
  47. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/connectors/ssh.py +0 -0
  48. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/__init__.py +0 -0
  49. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/arguments.py +0 -0
  50. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/callbacks.py +0 -0
  51. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/context.py +0 -0
  52. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/__init__.py +0 -0
  53. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/config_utils.py +0 -0
  54. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/config_walk.py +0 -0
  55. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects.pyi +0 -0
  56. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/serialization.py +0 -0
  57. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/serializers.py +0 -0
  58. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/utils.py +0 -0
  59. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/exceptions.py +0 -0
  60. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/experiments/__init__.py +0 -0
  61. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/experiments/cli.py +0 -0
  62. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/experiments/configuration.py +0 -0
  63. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/generators.py +0 -0
  64. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/huggingface.py +0 -0
  65. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/ipc.py +0 -0
  66. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/__init__.py +0 -0
  67. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/base.py +0 -0
  68. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/parser.py +0 -0
  69. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/specs.py +0 -0
  70. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/__init__.py +0 -0
  71. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/direct.py +0 -0
  72. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/oar.py +0 -0
  73. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/slurm/__init__.py +0 -0
  74. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/slurm/base.py +0 -0
  75. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/locking.py +0 -0
  76. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/__init__.py +0 -0
  77. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/annotations.py +0 -0
  78. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/metaloader.py +0 -0
  79. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/style.css +0 -0
  80. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/mypy.py +0 -0
  81. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/py.typed +0 -0
  82. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/rpyc.py +0 -0
  83. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/run.py +0 -0
  84. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/__init__.py +0 -0
  85. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/base.py +0 -0
  86. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/dependencies.py +0 -0
  87. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/dynamic_outputs.py +0 -0
  88. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/services.py +0 -0
  89. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/state.py +0 -0
  90. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/workspace.py +0 -0
  91. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/scriptbuilder.py +0 -0
  92. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/__init__.py +0 -0
  93. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  94. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  95. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  96. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  97. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  98. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  99. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  100. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  101. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  102. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  103. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  104. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  105. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  106. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  107. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  108. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  109. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/favicon.ico +0 -0
  110. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.css +0 -0
  111. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.css.map +0 -0
  112. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.html +0 -0
  113. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.js +0 -0
  114. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.js.map +0 -0
  115. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/login.html +0 -0
  116. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/manifest.json +0 -0
  117. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/sphinx/__init__.py +0 -0
  118. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/sphinx/static/experimaestro.css +0 -0
  119. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/taskglobals.py +0 -0
  120. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/__init__.py +0 -0
  121. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/conftest.py +0 -0
  122. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/connectors/bin/executable.py +0 -0
  123. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/connectors/test_local.py +0 -0
  124. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/connectors/utils.py +0 -0
  125. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/core/__init__.py +0 -0
  126. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/core/test_generics.py +0 -0
  127. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/definitions_types.py +0 -0
  128. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/__init__.py +0 -0
  129. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/sacct +0 -0
  130. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/sbatch +0 -0
  131. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/srun +0 -0
  132. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/test.py +0 -0
  133. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/config_slurm/__init__.py +0 -0
  134. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/config_slurm/launchers.py +0 -0
  135. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/test_local.py +0 -0
  136. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/test_slurm.py +0 -0
  137. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/restart_main.py +0 -0
  138. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/scripts/notifyandwait.py +0 -0
  139. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/scripts/waitforfile.py +0 -0
  140. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/task_tokens.py +0 -0
  141. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/tasks/__init__.py +0 -0
  142. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/tasks/all.py +0 -0
  143. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/tasks/foreign.py +0 -0
  144. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_findlauncher.py +0 -0
  145. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_forward.py +0 -0
  146. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_snippets.py +0 -0
  147. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_ssh.py +0 -0
  148. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/utils.py +0 -0
  149. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tokens.py +0 -0
  150. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/__init__.py +0 -0
  151. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/diff.py +0 -0
  152. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/documentation.py +0 -0
  153. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/jobs.py +0 -0
  154. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/typingutils.py +0 -0
  155. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/__init__.py +0 -0
  156. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/asyncio.py +0 -0
  157. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/jobs.py +0 -0
  158. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/jupyter.py +0 -0
  159. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/multiprocessing.py +0 -0
  160. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/resources.py +0 -0
  161. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/settings.py +0 -0
  162. {experimaestro-1.11.1 → experimaestro-2.0.0rc0}/src/experimaestro/xpmutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: experimaestro
3
- Version: 1.11.1
3
+ Version: 2.0.0rc0
4
4
  Summary: "Experimaestro is a computer science experiment manager"
5
5
  License: GPL-3
6
6
  Keywords: experiment manager
@@ -48,7 +48,7 @@ dependencies = [
48
48
  "typing-extensions >=4.2; python_version < \"3.12\"",
49
49
  "watchdog >=2"
50
50
  ]
51
- version = "1.11.1"
51
+ version = "2.0.0-rc0"
52
52
 
53
53
  [tool.poetry-dynamic-versioning]
54
54
  enable = false
@@ -83,6 +83,12 @@ poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] }
83
83
  requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
84
84
  build-backend = "poetry_dynamic_versioning.backend"
85
85
 
86
+ [dependency-groups]
87
+ dev = [
88
+ "pytest>=8.4.1",
89
+ "pytest-timeout>=2.4.0",
90
+ ]
91
+
86
92
  [tool.poetry.group.ssh]
87
93
  optional = true
88
94
 
@@ -140,7 +146,7 @@ warn_unused_ignores = true
140
146
 
141
147
  [tool.commitizen]
142
148
  name = "cz_conventional_commits"
143
- version = "1.11.1"
149
+ version = "2.0.0a0"
144
150
  changelog_start_rev = "v1.0.0"
145
151
  tag_format = "v$major.$minor.$patch$prerelease"
146
152
  # update_changelog_on_bump = true
@@ -71,7 +71,7 @@ class config:
71
71
  assert inspect.isclass(tp), f"{tp} is not a class"
72
72
 
73
73
  # Adds to xpminfo for on demand creation of information
74
- return ObjectType(tp, identifier=self.identifier).basetype
74
+ return ObjectType(tp, identifier=self.identifier).valuetype
75
75
 
76
76
 
77
77
  class Array(TypeProxy):
@@ -20,16 +20,6 @@ from experimaestro.settings import find_workspace
20
20
  logging.basicConfig(level=logging.INFO)
21
21
 
22
22
 
23
- def pass_cfg(f):
24
- """Pass configuration information"""
25
-
26
- @click.pass_context
27
- def new_func(ctx, *args, **kwargs):
28
- return ctx.invoke(f, ctx.obj, *args, **kwargs)
29
-
30
- return update_wrapper(new_func, f)
31
-
32
-
33
23
  def check_xp_path(ctx, self, path: Path):
34
24
  if not (path / ".__experimaestro__").is_file():
35
25
  cprint(f"{path} is not an experimaestro working directory", "red")
@@ -142,7 +132,6 @@ def diff(path: Path):
142
132
  """Show the reason of the identifier change for a job"""
143
133
  from experimaestro.tools.jobs import load_job
144
134
  from experimaestro import Config
145
- from experimaestro.core.objects import ConfigWalkContext
146
135
 
147
136
  _, job = load_job(path / "params.json", discard_id=False)
148
137
  _, new_job = load_job(path / "params.json")
@@ -290,6 +279,16 @@ cli.add_command(Launchers("launchers", help="Launcher specific commands"))
290
279
  cli.add_command(Launchers("connectors", help="Connector specific commands"))
291
280
  cli.add_command(Launchers("tokens", help="Token specific commands"))
292
281
 
282
+ # Import and add progress commands
283
+ from .progress import progress as progress_cli
284
+
285
+ cli.add_command(progress_cli)
286
+
287
+ # Import and add jobs commands
288
+ from .jobs import jobs as jobs_cli
289
+
290
+ cli.add_command(jobs_cli)
291
+
293
292
 
294
293
  @cli.group()
295
294
  @click.option("--workdir", type=Path, default=None)
@@ -0,0 +1,269 @@
1
+ """Simplified CLI commands for managing and viewing progress files"""
2
+
3
+ import time
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Optional, Dict
7
+
8
+ import click
9
+ from termcolor import colored
10
+
11
+ try:
12
+ from tqdm import tqdm
13
+
14
+ TQDM_AVAILABLE = True
15
+ except ImportError:
16
+ TQDM_AVAILABLE = False
17
+
18
+ from experimaestro.progress import ProgressEntry, ProgressFileReader
19
+ from experimaestro.settings import find_workspace
20
+ from . import cli
21
+
22
+
23
+ @click.option("--workspace", default="", help="Experimaestro workspace")
24
+ @click.option("--workdir", type=Path, default=None)
25
+ @cli.group()
26
+ @click.pass_context
27
+ def progress(
28
+ ctx,
29
+ workdir: Optional[Path],
30
+ workspace: Optional[str],
31
+ ):
32
+ """Progress tracking commands"""
33
+ ctx.obj.workspace = find_workspace(workdir=workdir, workspace=workspace)
34
+
35
+
36
+ def format_timestamp(timestamp: float) -> str:
37
+ """Format timestamp for display"""
38
+ dt = datetime.fromtimestamp(timestamp)
39
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
40
+
41
+
42
+ @click.argument("jobid", type=str)
43
+ @progress.command()
44
+ @click.pass_context
45
+ def show(ctx, jobid: str):
46
+ """Show current progress state (default command)
47
+
48
+ JOBID format: task_name/task_hash
49
+ """
50
+ try:
51
+ task_name, task_hash = jobid.split("/")
52
+ except ValueError:
53
+ raise click.ClickException("JOBID must be in format task_name/task_hash")
54
+
55
+ workspace = ctx.obj.workspace
56
+ task_path = workspace.path / "jobs" / task_name / task_hash
57
+
58
+ if not task_path.exists():
59
+ raise click.ClickException(f"Job directory not found: {task_path}")
60
+
61
+ reader = ProgressFileReader(task_path)
62
+ current_progress = reader.get_current_progress()
63
+
64
+ if not current_progress:
65
+ click.echo("No progress information available")
66
+ return
67
+
68
+ # Filter out EOJ markers
69
+ current_progress = {k: v for k, v in current_progress.items() if k != -1}
70
+
71
+ if not current_progress:
72
+ click.echo("No progress information available")
73
+ return
74
+
75
+ click.echo(f"Progress for job {jobid}")
76
+ click.echo("=" * 80)
77
+
78
+ # Show simple text-based progress for each level
79
+ for level in sorted(current_progress.keys()):
80
+ entry = current_progress[level]
81
+ indent = " " * level
82
+ progress_pct = f"{entry.progress * 100:5.1f}%"
83
+ desc = entry.desc or f"Level {level}"
84
+ timestamp = format_timestamp(entry.timestamp)
85
+
86
+ color = "green" if entry.progress >= 1.0 else "yellow"
87
+ click.echo(colored(f"{indent}L{level}: {progress_pct} - {desc}", color))
88
+ click.echo(colored(f"{indent} Last updated: {timestamp}", "cyan"))
89
+
90
+
91
+ def create_progress_bar(
92
+ level: int,
93
+ desc: str,
94
+ progress: float = 0.0,
95
+ ) -> tqdm:
96
+ """Create a properly aligned progress bar like dashboard style"""
97
+ if level > 0:
98
+ indent = " " * (level - 1) + "└─ "
99
+ else:
100
+ indent = ""
101
+ label = f"{indent}L{level}"
102
+
103
+ colors = ["blue", "yellow", "magenta", "cyan", "white"]
104
+ bar_color = colors[level % len(colors)]
105
+
106
+ unit = desc[:50] if desc else f"Level {level}"
107
+ ncols = 100
108
+ wbar = 50
109
+
110
+ return tqdm(
111
+ total=100,
112
+ desc=label,
113
+ position=level,
114
+ leave=True,
115
+ bar_format=f"{{desc}}: {{percentage:3.0f}}%|{{bar:{wbar - len(indent)}}}| {{unit}}", # noqa: F541
116
+ ncols=ncols, # Adjust width based on level
117
+ unit=unit,
118
+ colour=bar_color,
119
+ initial=progress * 100,
120
+ )
121
+
122
+
123
+ def _update_progress_display(
124
+ reader: ProgressFileReader, progress_bars: Dict[int, tqdm]
125
+ ) -> bool:
126
+ """Update the tqdm progress bars in dashboard style"""
127
+ current_state: Dict[int, ProgressEntry] = {
128
+ k: v for k, v in reader.get_current_state().items() if k != -1
129
+ }
130
+
131
+ if not current_state:
132
+ click.echo("No progress information available yet...")
133
+ return False
134
+
135
+ # Update existing bars and create new ones
136
+ for _level, entry in current_state.items():
137
+ progress_val = entry.progress * 100
138
+ desc = entry.desc or f"Level {entry.level}"
139
+
140
+ if entry.level not in progress_bars:
141
+ progress_bars[entry.level] = create_progress_bar(
142
+ entry.level, desc, progress_val
143
+ )
144
+
145
+ bar = progress_bars[entry.level]
146
+ bar.unit = desc[:50]
147
+ bar.n = progress_val
148
+
149
+ bar.refresh()
150
+
151
+ # Remove bars for levels that no longer exist
152
+ levels_to_remove = set(progress_bars.keys()) - set(current_state.keys())
153
+ for level in levels_to_remove:
154
+ progress_bars[level].close()
155
+ del progress_bars[level]
156
+
157
+ return True
158
+
159
+
160
+ @click.argument("jobid", type=str)
161
+ @click.option("--refresh-rate", "-r", default=0.5, help="Refresh rate in seconds")
162
+ @progress.command()
163
+ @click.pass_context
164
+ def live(ctx, jobid: str, refresh_rate: float):
165
+ """Show live progress with tqdm-style bars
166
+
167
+ JOBID format: task_name/task_hash
168
+ """
169
+ if not TQDM_AVAILABLE:
170
+ click.echo("tqdm is not available. Install with: pip install tqdm")
171
+ click.echo("Falling back to basic display...")
172
+ ctx.invoke(show, jobid=jobid)
173
+ return
174
+
175
+ try:
176
+ task_name, task_hash = jobid.split("/")
177
+ except ValueError:
178
+ raise click.ClickException("JOBID must be in format task_name/task_hash")
179
+
180
+ workspace = ctx.obj.workspace
181
+ task_path = workspace.path / "jobs" / task_name / task_hash
182
+
183
+ if not task_path.exists():
184
+ raise click.ClickException(f"Job directory not found: {task_path}")
185
+
186
+ reader = ProgressFileReader(task_path)
187
+ progress_bars: Dict[int, tqdm] = {}
188
+
189
+ def cleanup_bars():
190
+ """Clean up all progress bars"""
191
+ for bar in progress_bars.values():
192
+ bar.close()
193
+ progress_bars.clear()
194
+
195
+ click.echo(f"Live progress for job {jobid}")
196
+ click.echo("Press Ctrl+C to stop")
197
+ click.echo("=" * 80)
198
+
199
+ try:
200
+ if not _update_progress_display(reader, progress_bars):
201
+ click.echo("No progress information available yet...")
202
+
203
+ while True:
204
+ time.sleep(refresh_rate)
205
+
206
+ if not _update_progress_display(reader, progress_bars):
207
+ # Check if job is complete
208
+ if reader.is_done():
209
+ click.echo("\nJob completed!")
210
+ break
211
+
212
+ # Check if all progress bars are at 100%
213
+ if progress_bars and all(bar.n >= 100 for bar in progress_bars.values()):
214
+ cleanup_bars()
215
+ click.echo("\nAll progress completed!")
216
+ break
217
+
218
+ except KeyboardInterrupt:
219
+ click.echo("\nStopped monitoring progress")
220
+ finally:
221
+ cleanup_bars()
222
+
223
+
224
+ @progress.command(name="list")
225
+ @click.pass_context
226
+ def list_jobs(ctx):
227
+ """List all jobs with progress information"""
228
+ ws = ctx.obj.workspace
229
+ jobs_path = ws.path / "jobs"
230
+
231
+ if not jobs_path.exists():
232
+ click.echo("No jobs directory found")
233
+ return
234
+
235
+ for task_dir in jobs_path.iterdir():
236
+ if not task_dir.is_dir():
237
+ continue
238
+
239
+ for job_dir in task_dir.iterdir():
240
+ if not job_dir.is_dir():
241
+ continue
242
+
243
+ progress_dir = job_dir / ".experimaestro"
244
+ if not progress_dir.exists():
245
+ continue
246
+
247
+ # Check if there are progress files
248
+ progress_files = list(progress_dir.glob("progress-*.jsonl"))
249
+ if not progress_files:
250
+ continue
251
+
252
+ job_id = f"{task_dir.name}/{job_dir.name}"
253
+ reader = ProgressFileReader(job_dir)
254
+ current_state = reader.get_current_state()
255
+
256
+ # if current_progress:
257
+ if current_state:
258
+ # Get overall progress (level 0)
259
+ level_0 = current_state.get(0)
260
+ if level_0:
261
+ color = "green" if level_0.progress >= 1.0 else "yellow"
262
+ desc = f"{level_0.desc}" if level_0.desc else ""
263
+ progress_pct = f"{level_0.progress * 100:5.1f}%"
264
+ click.echo(colored(f"{job_id:50} - {progress_pct} - {desc}", color))
265
+
266
+ else:
267
+ click.echo(f"{job_id:50} No level 0 progress")
268
+ else:
269
+ click.echo(f"{job_id:50} No progress data")
@@ -170,7 +170,7 @@ class IdentifierComputer:
170
170
  self._hashupdate(IdentifierComputer.ENUM_ID)
171
171
  k = value.__class__
172
172
  self._hashupdate(
173
- f"{k.__module__}.{k.__qualname__ }:{value.name}".encode("utf-8"),
173
+ f"{k.__module__}.{k.__qualname__}:{value.name}".encode("utf-8"),
174
174
  )
175
175
  elif isinstance(value, dict):
176
176
  self._hashupdate(IdentifierComputer.DICT_ID)
@@ -235,7 +235,16 @@ class IdentifierComputer:
235
235
  # Skip if the argument is not a constant, and
236
236
  # - optional argument: both value and default are None
237
237
  # - the argument value is equal to the default value
238
- argvalue = getattr(value, argument.name, None)
238
+ try:
239
+ argvalue = getattr(value, argument.name, None)
240
+ except KeyError:
241
+ logging.warning(
242
+ "Parameter %s has not been set in %s created at %s",
243
+ argument.name,
244
+ value,
245
+ value.__xpm__._initinfo,
246
+ )
247
+ raise
239
248
  if not argument.constant and (
240
249
  (
241
250
  not argument.required
@@ -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,11 @@ class ConfigInformation:
170
169
  self._sealed = False
171
170
  self._meta = None
172
171
 
172
+ #: This flags is True when a value in this configuration,
173
+ #: or any sub-configuration, is generated. This prevents problem
174
+ #: when a configuration with generated values is re-used
175
+ self._has_generated_value = False
176
+
173
177
  def set_meta(self, value: Optional[bool]):
174
178
  """Sets the meta flag"""
175
179
  assert not self._sealed, "Configuration is sealed"
@@ -198,6 +202,20 @@ class ConfigInformation:
198
202
  if self._sealed and not bypass:
199
203
  raise AttributeError(f"Object is read-only (trying to set {k})")
200
204
 
205
+ if not isinstance(v, ConfigMixin) and isinstance(v, Config):
206
+ raise AttributeError(
207
+ "Configuration (and not objects) should be used. Consider using .C(...)"
208
+ )
209
+
210
+ if (
211
+ isinstance(v, ConfigMixin)
212
+ and v.__xpm__._has_generated_value
213
+ and v.__xpm__.task is None
214
+ ):
215
+ raise AttributeError(
216
+ f"Cannot set {k} to a configuration with generated values"
217
+ )
218
+
201
219
  try:
202
220
  argument = self.xpmtype.arguments.get(k, None)
203
221
  if argument:
@@ -326,12 +344,20 @@ class ConfigInformation:
326
344
  Arguments:
327
345
  - context: the generation context
328
346
  """
347
+ subconfigs = [
348
+ v.__xpm__
349
+ for v in self.values.values()
350
+ if isinstance(v, Config) and v.__xpm__.task is None
351
+ ]
352
+
353
+ if any(v._has_generated_value for v in subconfigs):
354
+ raise AttributeError("Cannot seal a configuration with generated values")
329
355
 
330
356
  class Sealer(ConfigWalk):
331
- def preprocess(self, config: Config):
357
+ def preprocess(self, config: ConfigMixin):
332
358
  return not config.__xpm__._sealed, config
333
359
 
334
- def postprocess(self, stub, config: Config, values):
360
+ def postprocess(self, stub, config: ConfigMixin, values):
335
361
  # Generate values
336
362
  from experimaestro.generators import Generator
337
363
 
@@ -344,6 +370,7 @@ class ConfigInformation:
344
370
  continue
345
371
  value = argument.generator()
346
372
  else:
373
+ # Generate a value
347
374
  sig = inspect.signature(argument.generator)
348
375
  if len(sig.parameters) == 0:
349
376
  value = argument.generator()
@@ -354,12 +381,23 @@ class ConfigInformation:
354
381
  False
355
382
  ), "generator has either two parameters (context and config), or none"
356
383
  config.__xpm__.set(k, value, bypass=True)
384
+ config.__xpm__._has_generated_value = True
385
+ else:
386
+ value = config.__xpm__.values.get(k)
357
387
  except Exception:
358
388
  logger.error(
359
389
  "While setting %s of %s", argument.name, config.__xpmtype__
360
390
  )
361
391
  raise
362
392
 
393
+ # Propagate the generated value flag
394
+ if (
395
+ (value is not None)
396
+ and isinstance(value, ConfigMixin)
397
+ and value.__xpm__._has_generated_value
398
+ ):
399
+ self._has_generated_value = True
400
+
363
401
  config.__xpm__._sealed = True
364
402
 
365
403
  Sealer(context, recurse_task=True)(self.pyobject)
@@ -657,13 +695,6 @@ class ConfigInformation:
657
695
 
658
696
  print(file=sys.stderr) # noqa: T201
659
697
 
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
698
  # Mark this configuration also
668
699
  self.task = self.pyobject
669
700
 
@@ -677,6 +708,9 @@ class ConfigInformation:
677
708
  def mark_output(self, config: "Config"):
678
709
  """Sets a dependency on the job"""
679
710
  assert not isinstance(config, Task), "Cannot set a dependency on a task"
711
+ assert isinstance(
712
+ config, ConfigMixin
713
+ ), "Only configurations can be marked as dependent on a task"
680
714
  config.__xpm__.task = self.pyobject
681
715
  return config
682
716
 
@@ -762,7 +796,7 @@ class ConfigInformation:
762
796
  state_dict = {
763
797
  "id": id(self.pyobject),
764
798
  "module": self.xpmtype._module,
765
- "type": self.xpmtype.basetype.__qualname__,
799
+ "type": self.xpmtype.value_type.__qualname__,
766
800
  "typename": self.xpmtype.name(),
767
801
  "identifier": self.identifier.state_dict(),
768
802
  }
@@ -1022,7 +1056,7 @@ class ConfigInformation:
1022
1056
 
1023
1057
  # Creates an object (or a config)
1024
1058
  if as_instance:
1025
- o = cls.XPMValue.__new__(cls.XPMValue)
1059
+ o = cls.__new__(cls)
1026
1060
  else:
1027
1061
  o = cls.XPMConfig.__new__(cls.XPMConfig)
1028
1062
  assert definition["id"] not in objects, "Duplicate id %s" % definition["id"]
@@ -1183,7 +1217,7 @@ class ConfigInformation:
1183
1217
 
1184
1218
  if o is None:
1185
1219
  # Creates an object (and not a config)
1186
- o = config.XPMValue()
1220
+ o = config.__xpmtype__.value_type()
1187
1221
 
1188
1222
  # Store in cache
1189
1223
  self.objects.add_stub(id(config), o)
@@ -1242,6 +1276,9 @@ def clone(v):
1242
1276
  if isinstance(v, Enum):
1243
1277
  return v
1244
1278
 
1279
+ if isinstance(v, tuple):
1280
+ return tuple(clone(x) for x in v)
1281
+
1245
1282
  if isinstance(v, Config):
1246
1283
  # Create a new instance
1247
1284
  kwargs = {
@@ -1260,6 +1297,11 @@ class ConfigMixin:
1260
1297
  """Class for configuration objects"""
1261
1298
 
1262
1299
  __xpmtype__: ObjectType
1300
+ """The associated XPM type"""
1301
+
1302
+ __xpm__: ConfigInformation
1303
+ """The __xpm__ object contains all instance specific information about a
1304
+ configuration/task"""
1263
1305
 
1264
1306
  def __init__(self, **kwargs):
1265
1307
  """Initialize the configuration with the given parameters"""
@@ -1310,8 +1352,8 @@ class ConfigMixin:
1310
1352
  [f"{key}={value}" for key, value in self.__xpm__.values.items()]
1311
1353
  )
1312
1354
  return (
1313
- f"{self.__xpmtype__.basetype.__module__}."
1314
- f"{self.__xpmtype__.basetype.__qualname__}({params})"
1355
+ f"{self.__xpmtype__.value_type.__module__}."
1356
+ f"{self.__xpmtype__.value_type.__qualname__}({params})"
1315
1357
  )
1316
1358
 
1317
1359
  def tag(self, name, value):
@@ -1397,6 +1439,9 @@ class ConfigMixin:
1397
1439
  return clone(self)
1398
1440
 
1399
1441
  def add_pretasks(self, *tasks: "LightweightTask"):
1442
+ assert all(
1443
+ [isinstance(task, ConfigMixin) for task in tasks]
1444
+ ), "One of the parameters is not a configuration object"
1400
1445
  assert all(
1401
1446
  [isinstance(task, LightweightTask) for task in tasks]
1402
1447
  ), "One of the pre-tasks are not lightweight tasks"
@@ -1441,52 +1486,17 @@ class Config:
1441
1486
  """The object type holds all the information about a specific subclass
1442
1487
  experimaestro metadata"""
1443
1488
 
1444
- __xpm__: ConfigInformation
1445
- """The __xpm__ object contains all instance specific information about a
1446
- configuration/task"""
1447
-
1448
1489
  @classproperty
1449
1490
  def XPMConfig(cls):
1450
1491
  if issubclass(cls, ConfigMixin):
1451
1492
  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
1493
+ return cls.__getxpmtype__().config_type
1479
1494
 
1480
1495
  @classproperty
1481
1496
  def C(cls):
1482
1497
  """Alias for XPMConfig"""
1483
1498
  return cls.XPMConfig
1484
1499
 
1485
- @classproperty
1486
- def V(cls):
1487
- """Alias for XPMValue"""
1488
- return cls.XPMValue
1489
-
1490
1500
  @classmethod
1491
1501
  def __getxpmtype__(cls) -> "ObjectType":
1492
1502
  """Get (and create if necessary) the Object type associated
@@ -1503,46 +1513,6 @@ class Config:
1503
1513
  raise
1504
1514
  return xpmtype
1505
1515
 
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
1516
  def __validate__(self):
1547
1517
  """Validate the values"""
1548
1518
  pass