experimaestro 2.0.0a3__tar.gz → 2.0.0a5__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.
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/PKG-INFO +3 -2
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/pyproject.toml +5 -2
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/connectors/__init__.py +2 -2
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/objects/config.py +28 -9
- experimaestro-2.0.0a5/src/experimaestro/scheduler/__init__.py +18 -0
- experimaestro-2.0.0a5/src/experimaestro/scheduler/base.py +310 -0
- experimaestro-2.0.0a5/src/experimaestro/scheduler/experiment.py +387 -0
- experimaestro-2.0.0a5/src/experimaestro/scheduler/jobs.py +475 -0
- experimaestro-2.0.0a5/src/experimaestro/scheduler/signal_handler.py +32 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/scheduler/state.py +1 -1
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/__init__.py +36 -5
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_dependencies.py +1 -1
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_generators.py +34 -9
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/typingutils.py +11 -2
- experimaestro-2.0.0a3/src/experimaestro/scheduler/__init__.py +0 -1
- experimaestro-2.0.0a3/src/experimaestro/scheduler/base.py +0 -1129
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/LICENSE +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/README.md +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/__main__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/annotations.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/checkers.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/cli/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/cli/filter.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/cli/jobs.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/cli/progress.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/click.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/commandline.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/compat.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/connectors/local.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/connectors/ssh.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/arguments.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/callbacks.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/context.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/identifier.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/objects/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/objects/config_utils.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/objects/config_walk.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/objects.pyi +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/serialization.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/serializers.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/types.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/core/utils.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/exceptions.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/experiments/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/experiments/cli.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/experiments/configuration.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/generators.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/huggingface.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/ipc.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launcherfinder/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launcherfinder/base.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launcherfinder/parser.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launcherfinder/registry.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launcherfinder/specs.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launchers/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launchers/direct.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launchers/oar.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launchers/slurm/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/launchers/slurm/base.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/locking.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/mkdocs/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/mkdocs/annotations.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/mkdocs/base.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/mkdocs/metaloader.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/mkdocs/style.css +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/mypy.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/notifications.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/progress.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/py.typed +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/rpyc.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/run.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/scheduler/dependencies.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/scheduler/dynamic_outputs.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/scheduler/services.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/scheduler/workspace.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/scriptbuilder.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/favicon.ico +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/index.css +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/index.css.map +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/index.html +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/index.js +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/index.js.map +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/login.html +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/server/data/manifest.json +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/settings.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/sphinx/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/sphinx/static/experimaestro.css +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/taskglobals.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/conftest.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/connectors/bin/executable.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/connectors/test_local.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/connectors/utils.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/core/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/core/test_generics.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/definitions_types.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/bin/sacct +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/bin/sbatch +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/bin/srun +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/bin/test.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/common.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/config_slurm/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/config_slurm/launchers.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/test_local.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/launchers/test_slurm.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/restart.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/restart_main.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/scripts/notifyandwait.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/scripts/waitforfile.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/task_tokens.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/tasks/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/tasks/all.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/tasks/foreign.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_checkers.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_experiment.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_file_progress.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_file_progress_integration.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_findlauncher.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_forward.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_identifier.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_instance.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_objects.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_outputs.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_param.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_progress.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_serializers.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_snippets.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_ssh.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_tags.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_tasks.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_tokens.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_types.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/test_validation.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/token_reschedule.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tests/utils.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tokens.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tools/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tools/diff.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tools/documentation.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/tools/jobs.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/utils/__init__.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/utils/asyncio.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/utils/jobs.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/utils/jupyter.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/utils/multiprocessing.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/utils/resources.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/utils/settings.py +0 -0
- {experimaestro-2.0.0a3 → experimaestro-2.0.0a5}/src/experimaestro/xpmutils.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: experimaestro
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a5
|
|
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-
|
|
51
|
+
version = "2.0.0-a5"
|
|
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.
|
|
152
|
+
version = "2.0.0a5"
|
|
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
|
|
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
|
|
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, Enum, 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
|
+
return False
|
|
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
|
|
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
|