experimaestro 1.10.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.
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/PKG-INFO +1 -1
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/pyproject.toml +8 -2
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/annotations.py +1 -1
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/cli/__init__.py +10 -11
- experimaestro-2.0.0rc0/src/experimaestro/cli/progress.py +269 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/identifier.py +11 -2
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/config.py +64 -94
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/types.py +35 -57
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/registry.py +3 -3
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/specs.py +8 -1
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/base.py +6 -8
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/notifications.py +12 -3
- experimaestro-2.0.0rc0/src/experimaestro/progress.py +406 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/run.py +2 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/settings.py +4 -2
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/common.py +2 -2
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/restart.py +1 -1
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_checkers.py +2 -2
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_dependencies.py +12 -12
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_experiment.py +3 -3
- experimaestro-2.0.0rc0/src/experimaestro/tests/test_file_progress.py +425 -0
- experimaestro-2.0.0rc0/src/experimaestro/tests/test_file_progress_integration.py +477 -0
- experimaestro-2.0.0rc0/src/experimaestro/tests/test_generators.py +61 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_identifier.py +90 -81
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_instance.py +9 -9
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_objects.py +9 -32
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_outputs.py +6 -6
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_param.py +14 -14
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_progress.py +4 -4
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_serializers.py +5 -5
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_tags.py +15 -15
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_tasks.py +40 -36
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_tokens.py +8 -6
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_types.py +10 -10
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_validation.py +19 -19
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/token_reschedule.py +1 -1
- experimaestro-2.0.0rc0/src/experimaestro/utils/multiprocessing.py +44 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/LICENSE +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/README.md +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/__main__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/checkers.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/cli/filter.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/cli/jobs.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/click.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/commandline.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/compat.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/connectors/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/connectors/local.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/connectors/ssh.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/arguments.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/callbacks.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/context.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/config_utils.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects/config_walk.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/objects.pyi +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/serialization.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/serializers.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/core/utils.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/exceptions.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/experiments/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/experiments/cli.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/experiments/configuration.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/generators.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/huggingface.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/ipc.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/base.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launcherfinder/parser.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/direct.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/oar.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/slurm/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/launchers/slurm/base.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/locking.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/annotations.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/metaloader.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/mkdocs/style.css +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/mypy.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/py.typed +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/rpyc.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/base.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/dependencies.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/dynamic_outputs.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/services.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/state.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scheduler/workspace.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/scriptbuilder.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/1815e00441357e01619e.ttf +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/2463b90d9a316e4e5294.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/2582b0e4bcf85eceead0.ttf +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/89999bdf5d835c012025.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/914997e1bdfc990d0897.ttf +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/c210719e60948b211a12.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/favicon.ico +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.css +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.css.map +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.html +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.js +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/index.js.map +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/login.html +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/server/data/manifest.json +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/sphinx/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/sphinx/static/experimaestro.css +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/taskglobals.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/conftest.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/connectors/bin/executable.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/connectors/test_local.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/connectors/utils.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/core/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/core/test_generics.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/definitions_types.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/sacct +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/sbatch +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/srun +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/bin/test.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/config_slurm/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/config_slurm/launchers.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/test_local.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/launchers/test_slurm.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/restart_main.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/scripts/notifyandwait.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/scripts/waitforfile.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/task_tokens.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/tasks/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/tasks/all.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/tasks/foreign.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_findlauncher.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_forward.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_snippets.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/test_ssh.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tests/utils.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tokens.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/diff.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/documentation.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/tools/jobs.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/typingutils.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/__init__.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/asyncio.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/jobs.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/jupyter.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/resources.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/utils/settings.py +0 -0
- {experimaestro-1.10.1 → experimaestro-2.0.0rc0}/src/experimaestro/xpmutils.py +0 -0
|
@@ -48,7 +48,7 @@ dependencies = [
|
|
|
48
48
|
"typing-extensions >=4.2; python_version < \"3.12\"",
|
|
49
49
|
"watchdog >=2"
|
|
50
50
|
]
|
|
51
|
-
version = "
|
|
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 = "
|
|
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).
|
|
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__
|
|
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
|
-
|
|
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__
|
|
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
|
-
#
|
|
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:
|
|
357
|
+
def preprocess(self, config: ConfigMixin):
|
|
332
358
|
return not config.__xpm__._sealed, config
|
|
333
359
|
|
|
334
|
-
def postprocess(self, stub, config:
|
|
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.
|
|
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.
|
|
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.
|
|
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__.
|
|
1314
|
-
f"{self.__xpmtype__.
|
|
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__().
|
|
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
|