experimaestro 2.0.0a3__tar.gz → 2.0.0a4__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.
Files changed (167) hide show
  1. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/PKG-INFO +3 -2
  2. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/pyproject.toml +5 -2
  3. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/connectors/__init__.py +2 -2
  4. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/objects/config.py +28 -9
  5. experimaestro-2.0.0a4/src/experimaestro/scheduler/__init__.py +18 -0
  6. experimaestro-2.0.0a4/src/experimaestro/scheduler/base.py +310 -0
  7. experimaestro-2.0.0a4/src/experimaestro/scheduler/experiment.py +387 -0
  8. experimaestro-2.0.0a4/src/experimaestro/scheduler/jobs.py +475 -0
  9. experimaestro-2.0.0a4/src/experimaestro/scheduler/signal_handler.py +32 -0
  10. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/scheduler/state.py +1 -1
  11. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/__init__.py +36 -5
  12. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_dependencies.py +1 -1
  13. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_generators.py +34 -9
  14. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/typingutils.py +11 -2
  15. experimaestro-2.0.0a3/src/experimaestro/scheduler/__init__.py +0 -1
  16. experimaestro-2.0.0a3/src/experimaestro/scheduler/base.py +0 -1129
  17. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/LICENSE +0 -0
  18. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/README.md +0 -0
  19. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/__init__.py +0 -0
  20. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/__main__.py +0 -0
  21. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/annotations.py +0 -0
  22. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/checkers.py +0 -0
  23. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/cli/__init__.py +0 -0
  24. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/cli/filter.py +0 -0
  25. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/cli/jobs.py +0 -0
  26. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/cli/progress.py +0 -0
  27. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/click.py +0 -0
  28. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/commandline.py +0 -0
  29. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/compat.py +0 -0
  30. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/connectors/local.py +0 -0
  31. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/connectors/ssh.py +0 -0
  32. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/__init__.py +0 -0
  33. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/arguments.py +0 -0
  34. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/callbacks.py +0 -0
  35. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/context.py +0 -0
  36. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/identifier.py +0 -0
  37. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/objects/__init__.py +0 -0
  38. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/objects/config_utils.py +0 -0
  39. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/objects/config_walk.py +0 -0
  40. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/objects.pyi +0 -0
  41. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/serialization.py +0 -0
  42. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/serializers.py +0 -0
  43. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/types.py +0 -0
  44. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/core/utils.py +0 -0
  45. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/exceptions.py +0 -0
  46. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/experiments/__init__.py +0 -0
  47. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/experiments/cli.py +0 -0
  48. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/experiments/configuration.py +0 -0
  49. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/generators.py +0 -0
  50. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/huggingface.py +0 -0
  51. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/ipc.py +0 -0
  52. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launcherfinder/__init__.py +0 -0
  53. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launcherfinder/base.py +0 -0
  54. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launcherfinder/parser.py +0 -0
  55. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launcherfinder/registry.py +0 -0
  56. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launcherfinder/specs.py +0 -0
  57. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launchers/__init__.py +0 -0
  58. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launchers/direct.py +0 -0
  59. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launchers/oar.py +0 -0
  60. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launchers/slurm/__init__.py +0 -0
  61. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/launchers/slurm/base.py +0 -0
  62. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/locking.py +0 -0
  63. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/mkdocs/__init__.py +0 -0
  64. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/mkdocs/annotations.py +0 -0
  65. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/mkdocs/base.py +0 -0
  66. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/mkdocs/metaloader.py +0 -0
  67. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/mkdocs/style.css +0 -0
  68. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/mypy.py +0 -0
  69. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/notifications.py +0 -0
  70. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/progress.py +0 -0
  71. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/py.typed +0 -0
  72. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/rpyc.py +0 -0
  73. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/run.py +0 -0
  74. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/scheduler/dependencies.py +0 -0
  75. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/scheduler/dynamic_outputs.py +0 -0
  76. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/scheduler/services.py +0 -0
  77. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/scheduler/workspace.py +0 -0
  78. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/scriptbuilder.py +0 -0
  79. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  80. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
  81. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  82. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
  83. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
  84. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  85. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  86. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  87. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  88. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  89. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  90. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
  91. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
  92. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
  93. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  94. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  95. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/favicon.ico +0 -0
  96. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/index.css +0 -0
  97. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/index.css.map +0 -0
  98. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/index.html +0 -0
  99. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/index.js +0 -0
  100. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/index.js.map +0 -0
  101. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/login.html +0 -0
  102. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/server/data/manifest.json +0 -0
  103. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/settings.py +0 -0
  104. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/sphinx/__init__.py +0 -0
  105. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/sphinx/static/experimaestro.css +0 -0
  106. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/taskglobals.py +0 -0
  107. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/__init__.py +0 -0
  108. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/conftest.py +0 -0
  109. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/connectors/bin/executable.py +0 -0
  110. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/connectors/test_local.py +0 -0
  111. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/connectors/utils.py +0 -0
  112. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/core/__init__.py +0 -0
  113. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/core/test_generics.py +0 -0
  114. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/definitions_types.py +0 -0
  115. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/__init__.py +0 -0
  116. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/bin/sacct +0 -0
  117. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/bin/sbatch +0 -0
  118. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/bin/srun +0 -0
  119. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/bin/test.py +0 -0
  120. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/common.py +0 -0
  121. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/config_slurm/__init__.py +0 -0
  122. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/config_slurm/launchers.py +0 -0
  123. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/test_local.py +0 -0
  124. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/launchers/test_slurm.py +0 -0
  125. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/restart.py +0 -0
  126. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/restart_main.py +0 -0
  127. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/scripts/notifyandwait.py +0 -0
  128. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/scripts/waitforfile.py +0 -0
  129. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/task_tokens.py +0 -0
  130. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/tasks/__init__.py +0 -0
  131. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/tasks/all.py +0 -0
  132. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/tasks/foreign.py +0 -0
  133. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_checkers.py +0 -0
  134. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_experiment.py +0 -0
  135. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_file_progress.py +0 -0
  136. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_file_progress_integration.py +0 -0
  137. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_findlauncher.py +0 -0
  138. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_forward.py +0 -0
  139. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_identifier.py +0 -0
  140. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_instance.py +0 -0
  141. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_objects.py +0 -0
  142. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_outputs.py +0 -0
  143. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_param.py +0 -0
  144. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_progress.py +0 -0
  145. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_serializers.py +0 -0
  146. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_snippets.py +0 -0
  147. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_ssh.py +0 -0
  148. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_tags.py +0 -0
  149. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_tasks.py +0 -0
  150. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_tokens.py +0 -0
  151. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_types.py +0 -0
  152. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/test_validation.py +0 -0
  153. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/token_reschedule.py +0 -0
  154. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tests/utils.py +0 -0
  155. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tokens.py +0 -0
  156. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tools/__init__.py +0 -0
  157. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tools/diff.py +0 -0
  158. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tools/documentation.py +0 -0
  159. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/tools/jobs.py +0 -0
  160. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/utils/__init__.py +0 -0
  161. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/utils/asyncio.py +0 -0
  162. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/utils/jobs.py +0 -0
  163. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/utils/jupyter.py +0 -0
  164. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/utils/multiprocessing.py +0 -0
  165. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/utils/resources.py +0 -0
  166. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/utils/settings.py +0 -0
  167. {experimaestro-2.0.0a3 → experimaestro-2.0.0a4}/src/experimaestro/xpmutils.py +0 -0
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: experimaestro
3
- Version: 2.0.0a3
3
+ Version: 2.0.0a4
4
4
  Summary: "Experimaestro is a computer science experiment manager"
5
5
  License: GPL-3
6
+ License-File: LICENSE
6
7
  Keywords: experiment manager
7
8
  Author: Benjamin Piwowarski
8
9
  Author-email: benjamin@piwowarski.fr
@@ -48,7 +48,7 @@ dependencies = [
48
48
  "typing-extensions >=4.2; python_version < \"3.12\"",
49
49
  "watchdog >=2"
50
50
  ]
51
- version = "2.0.0-a3"
51
+ version = "2.0.0-a4"
52
52
 
53
53
  [tool.poetry-dynamic-versioning]
54
54
  enable = false
@@ -85,6 +85,9 @@ build-backend = "poetry_dynamic_versioning.backend"
85
85
 
86
86
  [dependency-groups]
87
87
  dev = [
88
+ "markdown-include>=0.8.1",
89
+ "mkdocstrings[python]>=0.30.1",
90
+ "pymdown-extensions>=10.16.1",
88
91
  "pytest>=8.4.1",
89
92
  "pytest-timeout>=2.4.0",
90
93
  ]
@@ -146,7 +149,7 @@ warn_unused_ignores = true
146
149
 
147
150
  [tool.commitizen]
148
151
  name = "cz_conventional_commits"
149
- version = "2.0.0a3"
152
+ version = "2.0.0a4"
150
153
  changelog_start_rev = "v1.0.0"
151
154
  tag_format = "v$major.$minor.$patch$prerelease"
152
155
  # update_changelog_on_bump = true
@@ -16,7 +16,7 @@ from experimaestro.utils import logger
16
16
  from experimaestro.locking import Lock
17
17
  from experimaestro.tokens import Token
18
18
  from experimaestro.utils.asyncio import asyncThreadcheck
19
- import pkg_resources
19
+ from importlib.metadata import entry_points
20
20
 
21
21
 
22
22
  class RedirectType(enum.Enum):
@@ -101,7 +101,7 @@ class Process:
101
101
  """Get a handler"""
102
102
  if Process.HANDLERS is None:
103
103
  Process.HANDLERS = {}
104
- for ep in pkg_resources.iter_entry_points(group="experimaestro.process"):
104
+ for ep in entry_points(group="experimaestro.process"):
105
105
  logging.debug("Adding process handler for type %s", ep.name)
106
106
  handler = ep.load()
107
107
  Process.HANDLERS[ep.name] = handler
@@ -214,6 +214,31 @@ class ConfigInformation:
214
214
  # Not an argument, bypass
215
215
  return object.__getattribute__(self.pyobject, name)
216
216
 
217
+ @staticmethod
218
+ def is_generated_value(argument, value):
219
+ if argument.ignore_generated:
220
+ return False
221
+
222
+ if value is None:
223
+ return False
224
+
225
+ if isinstance(value, (int, str, float, bool, Path)):
226
+ return False
227
+
228
+ if isinstance(value, ConfigMixin):
229
+ return value.__xpm__._generated_values and value.__xpm__.task is None
230
+
231
+ if isinstance(value, list):
232
+ return any(ConfigInformation.is_generated_value(argument, x) for x in value)
233
+
234
+ if isinstance(value, dict):
235
+ return any(
236
+ ConfigInformation.is_generated_value(argument, x)
237
+ for x in value.values()
238
+ )
239
+
240
+ assert False, f"Cannot handle values of type {type(value)}"
241
+
217
242
  def set(self, k, v, bypass=False):
218
243
  from experimaestro.generators import Generator
219
244
 
@@ -233,12 +258,7 @@ class ConfigInformation:
233
258
  try:
234
259
  argument = self.xpmtype.arguments.get(k, None)
235
260
  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
- ):
261
+ if ConfigInformation.is_generated_value(argument, v):
242
262
  raise AttributeError(
243
263
  f"Cannot set {k} to a configuration with generated values. "
244
264
  "Here is the list of paths to help you: "
@@ -373,9 +393,7 @@ class ConfigInformation:
373
393
  if generated_keys := [
374
394
  k
375
395
  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
396
+ if ConfigInformation.is_generated_value(self.xpmtype.arguments[k], v)
379
397
  ]:
380
398
  raise AttributeError(
381
399
  "Cannot seal a configuration with generated values:"
@@ -926,6 +944,7 @@ class ConfigInformation:
926
944
  "workspace": str(context.workspace.path.absolute()),
927
945
  "tags": {key: value for key, value in self.tags().items()},
928
946
  "version": 2,
947
+ "experimaestro": experimaestro.__version__,
929
948
  "objects": self.__get_objects__([], context),
930
949
  },
931
950
  out,
@@ -0,0 +1,18 @@
1
+ from .base import Scheduler, Listener
2
+ from .workspace import Workspace, RunMode
3
+ from .experiment import experiment, FailedExperiment
4
+ from .jobs import Job, JobState, JobFailureStatus, JobDependency, JobContext
5
+
6
+ __all__ = [
7
+ "Scheduler",
8
+ "Listener",
9
+ "Workspace",
10
+ "RunMode",
11
+ "experiment",
12
+ "FailedExperiment",
13
+ "Job",
14
+ "JobState",
15
+ "JobFailureStatus",
16
+ "JobDependency",
17
+ "JobContext",
18
+ ]
@@ -0,0 +1,310 @@
1
+ import logging
2
+ import threading
3
+ import time
4
+ from typing import (
5
+ Optional,
6
+ Set,
7
+ )
8
+ import asyncio
9
+ from typing import Dict
10
+
11
+ from experimaestro.scheduler import experiment
12
+ from experimaestro.scheduler.jobs import Job, JobState
13
+ from experimaestro.scheduler.services import Service
14
+
15
+
16
+ from experimaestro.utils import logger
17
+ from experimaestro.utils.asyncio import asyncThreadcheck
18
+ import concurrent.futures
19
+
20
+
21
+ class Listener:
22
+ def job_submitted(self, job):
23
+ pass
24
+
25
+ def job_state(self, job):
26
+ pass
27
+
28
+ def service_add(self, service: Service):
29
+ """Notify when a new service is added"""
30
+ pass
31
+
32
+
33
+ class Scheduler(threading.Thread):
34
+ """A job scheduler
35
+
36
+ The scheduler is based on asyncio for easy concurrency handling
37
+ """
38
+
39
+ def __init__(self, xp: "experiment", name: str):
40
+ super().__init__(name=f"Scheduler ({name})", daemon=True)
41
+ self._ready = threading.Event()
42
+
43
+ # Name of the experiment
44
+ self.name = name
45
+ self.xp = xp
46
+
47
+ # Exit mode activated
48
+ self.exitmode = False
49
+
50
+ # List of all jobs
51
+ self.jobs: Dict[str, "Job"] = {}
52
+
53
+ # List of jobs
54
+ self.waitingjobs: Set[Job] = set()
55
+
56
+ # Listeners
57
+ self.listeners: Set[Listener] = set()
58
+
59
+ @staticmethod
60
+ def create(xp: "experiment", name: str):
61
+ instance = Scheduler(xp, name)
62
+ instance.start()
63
+ instance._ready.wait()
64
+ return instance
65
+
66
+ def run(self):
67
+ """Run the event loop forever"""
68
+ logger.debug("Starting event loop thread")
69
+ # Ported from SchedulerCentral
70
+ self.loop = asyncio.new_event_loop()
71
+ asyncio.set_event_loop(self.loop)
72
+ # Set loop-dependent variables
73
+ self.exitCondition = asyncio.Condition()
74
+ self.dependencyLock = asyncio.Lock()
75
+ self._ready.set()
76
+ self.loop.run_forever()
77
+
78
+ def start_scheduler(self):
79
+ """Start the scheduler event loop in a thread"""
80
+ if not self.is_alive():
81
+ self.start()
82
+ self._ready.wait()
83
+ else:
84
+ logger.warning("Scheduler already started")
85
+
86
+ def addlistener(self, listener: Listener):
87
+ self.listeners.add(listener)
88
+
89
+ def removelistener(self, listener: Listener):
90
+ self.listeners.remove(listener)
91
+
92
+ def getJobState(self, job: Job) -> "concurrent.futures.Future[JobState]":
93
+ # Check if the job belongs to this scheduler
94
+ if job.identifier not in self.jobs:
95
+ # If job is not in this scheduler, return its current state directly
96
+ future = concurrent.futures.Future()
97
+ future.set_result(job.state)
98
+ return future
99
+
100
+ return asyncio.run_coroutine_threadsafe(self.aio_getjobstate(job), self.loop)
101
+
102
+ async def aio_getjobstate(self, job: Job):
103
+ return job.state
104
+
105
+ def submit(self, job: Job) -> Optional[Job]:
106
+ # Wait for the future containing the submitted job
107
+ logger.debug("Registering the job %s within the scheduler", job)
108
+ otherFuture = asyncio.run_coroutine_threadsafe(
109
+ self.aio_registerJob(job), self.loop
110
+ )
111
+ other = otherFuture.result()
112
+ logger.debug("Job already submitted" if other else "First submission")
113
+ if other:
114
+ return other
115
+
116
+ job._future = asyncio.run_coroutine_threadsafe(self.aio_submit(job), self.loop)
117
+ return None
118
+
119
+ def prepare(self, job: Job):
120
+ """Prepares the job for running"""
121
+ logger.info("Preparing job %s", job.path)
122
+ job.prepare(overwrite=True)
123
+
124
+ async def aio_registerJob(self, job: Job):
125
+ """Register a job by adding it to the list, and checks
126
+ whether the job has already been submitted
127
+ """
128
+ logger.debug("Registering job %s", job)
129
+
130
+ if self.exitmode:
131
+ logger.warning("Exit mode: not submitting")
132
+
133
+ elif job.identifier in self.jobs:
134
+ other = self.jobs[job.identifier]
135
+ assert job.type == other.type
136
+ if other.state == JobState.ERROR:
137
+ logger.info("Re-submitting job")
138
+ else:
139
+ logger.warning("Job %s already submitted", job.identifier)
140
+ return other
141
+
142
+ else:
143
+ # Register this job
144
+ self.xp.unfinishedJobs += 1
145
+ self.jobs[job.identifier] = job
146
+
147
+ return None
148
+
149
+ def notify_job_submitted(self, job: Job):
150
+ """Notify the listeners that a job has been submitted"""
151
+ for listener in self.listeners:
152
+ try:
153
+ listener.job_submitted(job)
154
+ except Exception:
155
+ logger.exception("Got an error with listener %s", listener)
156
+
157
+ def notify_job_state(self, job: Job):
158
+ """Notify the listeners that a job has changed state"""
159
+ for listener in self.listeners:
160
+ try:
161
+ listener.job_state(job)
162
+ except Exception:
163
+ logger.exception("Got an error with listener %s", listener)
164
+
165
+ async def aio_submit(self, job: Job) -> JobState: # noqa: C901
166
+ """Main scheduler function: submit a job, run it (if needed), and returns
167
+ the status code
168
+ """
169
+ logger.info("Submitting job %s", job)
170
+ job._readyEvent = asyncio.Event()
171
+ job.submittime = time.time()
172
+ job.scheduler = self
173
+ self.waitingjobs.add(job)
174
+
175
+ # Check that we don't have a completed job in
176
+ # alternate directories
177
+ for jobspath in experiment.current().alt_jobspaths:
178
+ # FIXME: check if done
179
+ pass
180
+
181
+ # Creates a link into the experiment folder
182
+ path = experiment.current().jobspath / job.relpath
183
+ path.parent.mkdir(parents=True, exist_ok=True)
184
+ if path.is_symlink():
185
+ path.unlink()
186
+ path.symlink_to(job.path)
187
+
188
+ job.state = JobState.WAITING
189
+
190
+ self.notify_job_submitted(job)
191
+
192
+ # Add dependencies, and add to blocking resources
193
+ if job.dependencies:
194
+ job.unsatisfied = len(job.dependencies)
195
+
196
+ for dependency in job.dependencies:
197
+ dependency.target = job
198
+ dependency.loop = self.loop
199
+ dependency.origin.dependents.add(dependency)
200
+ dependency.check()
201
+ else:
202
+ job._readyEvent.set()
203
+ job.state = JobState.READY
204
+
205
+ if job.donepath.exists():
206
+ job.state = JobState.DONE
207
+
208
+ # Check if we have a running process
209
+ process = await job.aio_process()
210
+ if process is not None:
211
+ # Yep! First we notify the listeners
212
+ job.state = JobState.RUNNING
213
+ # Notify the listeners
214
+ self.notify_job_state(job)
215
+
216
+ # Adds to the listeners
217
+ if self.xp.server is not None:
218
+ job.add_notification_server(self.xp.server)
219
+
220
+ # And now, we wait...
221
+ logger.info("Got a process for job %s - waiting to complete", job)
222
+ code = await process.aio_code()
223
+ logger.info("Job %s completed with code %s", job, code)
224
+ job.state = JobState.DONE if code == 0 else JobState.ERROR
225
+
226
+ # Check if done
227
+ if job.donepath.exists():
228
+ job.state = JobState.DONE
229
+
230
+ # OK, not done; let's start the job for real
231
+ while not job.state.finished():
232
+ # Wait that the job is ready
233
+ await job._readyEvent.wait()
234
+ job._readyEvent.clear()
235
+
236
+ if job.state == JobState.READY:
237
+ try:
238
+ state = await self.aio_start(job)
239
+ except Exception:
240
+ logger.exception("Got an exception while starting the job")
241
+ raise
242
+
243
+ if state is None:
244
+ # State is None if this is not the main thread
245
+ return JobState.ERROR
246
+
247
+ job.state = state
248
+
249
+ self.notify_job_state(job)
250
+
251
+ # Job is finished
252
+ if job.state != JobState.DONE:
253
+ self.xp.failedJobs[job.identifier] = job
254
+
255
+ # Process all remaining tasks outputs
256
+ await asyncThreadcheck("End of job processing", job.done_handler)
257
+
258
+ # Decrement the number of unfinished jobs and notify
259
+ self.xp.unfinishedJobs -= 1
260
+ async with self.exitCondition:
261
+ logging.debug("Updated number of unfinished jobs")
262
+ self.exitCondition.notify_all()
263
+
264
+ job.endtime = time.time()
265
+ if job in self.waitingjobs:
266
+ self.waitingjobs.remove(job)
267
+
268
+ with job.dependents as dependents:
269
+ logger.info("Processing %d dependent jobs", len(dependents))
270
+ for dependency in dependents:
271
+ logger.debug("Checking dependency %s", dependency)
272
+ self.loop.call_soon(dependency.check)
273
+
274
+ return job.state
275
+
276
+ async def aio_start(self, job: Job) -> Optional[JobState]:
277
+ """Start a job (scheduler coordination layer)
278
+
279
+ This method serves as a coordination layer that delegates the actual
280
+ job starting logic to the job itself while handling scheduler-specific
281
+ concerns like state notifications and providing coordination context.
282
+
283
+ :param job: The job to start
284
+ :return: JobState.WAITING if dependencies could not be locked, JobState.DONE
285
+ if job completed successfully, JobState.ERROR if job failed during execution,
286
+ or None (should not occur in normal operation)
287
+ :raises Exception: Various exceptions during scheduler coordination
288
+ """
289
+
290
+ # Assert preconditions
291
+ assert job.launcher is not None
292
+
293
+ try:
294
+ # Call job's start method with scheduler context
295
+ state = await job.aio_start(
296
+ sched_dependency_lock=self.dependencyLock,
297
+ notification_server=self.xp.server if self.xp else None,
298
+ )
299
+
300
+ if state is None:
301
+ # Dependencies couldn't be locked, return WAITING state
302
+ return JobState.WAITING
303
+
304
+ # Notify scheduler listeners of job state after successful start
305
+ self.notify_job_state(job)
306
+ return state
307
+
308
+ except Exception:
309
+ logger.warning("Error in scheduler job coordination", exc_info=True)
310
+ return JobState.ERROR