experimaestro 1.4.3__tar.gz → 1.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of experimaestro might be problematic. Click here for more details.

Files changed (148) hide show
  1. {experimaestro-1.4.3 → experimaestro-1.5.1}/PKG-INFO +2 -3
  2. {experimaestro-1.4.3 → experimaestro-1.5.1}/pyproject.toml +4 -5
  3. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/connectors/ssh.py +31 -11
  4. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/objects.py +72 -27
  5. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/objects.pyi +11 -5
  6. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/types.py +49 -9
  7. experimaestro-1.5.1/src/experimaestro/exceptions.py +2 -0
  8. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/experiments/cli.py +48 -21
  9. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/experiments/configuration.py +18 -4
  10. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launcherfinder/parser.py +8 -1
  11. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launcherfinder/registry.py +11 -4
  12. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launcherfinder/specs.py +7 -0
  13. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launchers/slurm/base.py +30 -5
  14. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/scheduler/base.py +27 -8
  15. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/scriptbuilder.py +12 -8
  16. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/settings.py +8 -2
  17. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_findlauncher.py +1 -1
  18. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_objects.py +13 -6
  19. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_param.py +2 -2
  20. experimaestro-1.5.1/src/experimaestro/typingutils.py +110 -0
  21. experimaestro-1.4.3/src/experimaestro/typingutils.py +0 -65
  22. {experimaestro-1.4.3 → experimaestro-1.5.1}/LICENSE +0 -0
  23. {experimaestro-1.4.3 → experimaestro-1.5.1}/README.md +0 -0
  24. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/__init__.py +0 -0
  25. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/__main__.py +0 -0
  26. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/annotations.py +0 -0
  27. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/checkers.py +0 -0
  28. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/click.py +0 -0
  29. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/commandline.py +0 -0
  30. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/compat.py +0 -0
  31. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/connectors/__init__.py +0 -0
  32. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/connectors/local.py +0 -0
  33. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/__init__.py +0 -0
  34. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/arguments.py +0 -0
  35. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/context.py +0 -0
  36. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/serialization.py +0 -0
  37. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/serializers.py +0 -0
  38. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/core/utils.py +0 -0
  39. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/experiments/__init__.py +0 -0
  40. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/filter.py +0 -0
  41. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/generators.py +0 -0
  42. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/huggingface.py +0 -0
  43. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/ipc.py +0 -0
  44. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launcherfinder/__init__.py +0 -0
  45. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launcherfinder/base.py +0 -0
  46. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launchers/__init__.py +0 -0
  47. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launchers/direct.py +0 -0
  48. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launchers/oar.py +0 -0
  49. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launchers/slurm/__init__.py +0 -0
  50. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launchers/slurm/cli.py +0 -0
  51. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/launchers/slurm/configuration.py +0 -0
  52. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/locking.py +0 -0
  53. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/mkdocs/__init__.py +0 -0
  54. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/mkdocs/annotations.py +0 -0
  55. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/mkdocs/base.py +0 -0
  56. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/mkdocs/metaloader.py +0 -0
  57. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/mkdocs/style.css +0 -0
  58. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/mypy.py +0 -0
  59. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/notifications.py +0 -0
  60. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/py.typed +0 -0
  61. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/rpyc.py +0 -0
  62. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/run.py +0 -0
  63. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/scheduler/__init__.py +0 -0
  64. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/scheduler/dependencies.py +0 -0
  65. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/scheduler/environment.py +0 -0
  66. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/scheduler/services.py +0 -0
  67. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/scheduler/workspace.py +0 -0
  68. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/__init__.py +0 -0
  69. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/016b4a6cdced82ab3aa1.ttf +0 -0
  70. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
  71. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
  72. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
  73. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
  74. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
  75. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
  76. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/50701fbb8177c2dde530.ttf +0 -0
  77. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
  78. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
  79. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/878f31251d960bd6266f.woff2 +0 -0
  80. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/b041b1fa4fe241b23445.woff2 +0 -0
  81. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/b6879d41b0852f01ed5b.woff2 +0 -0
  82. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
  83. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/d75e3fd1eb12e9bd6655.ttf +0 -0
  84. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
  85. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/favicon.ico +0 -0
  86. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/index.css +0 -0
  87. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/index.css.map +0 -0
  88. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/index.html +0 -0
  89. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/index.js +0 -0
  90. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/index.js.map +0 -0
  91. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/login.html +0 -0
  92. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/server/data/manifest.json +0 -0
  93. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/sphinx/__init__.py +0 -0
  94. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/sphinx/static/experimaestro.css +0 -0
  95. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/taskglobals.py +0 -0
  96. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/__init__.py +0 -0
  97. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/conftest.py +0 -0
  98. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/connectors/bin/executable.py +0 -0
  99. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/connectors/test_local.py +0 -0
  100. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/connectors/utils.py +0 -0
  101. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/definitions_types.py +0 -0
  102. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/__init__.py +0 -0
  103. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/bin/sacct +0 -0
  104. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/bin/sbatch +0 -0
  105. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/bin/test.py +0 -0
  106. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/common.py +0 -0
  107. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/config_slurm/__init__.py +0 -0
  108. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/config_slurm/launchers.yaml +0 -0
  109. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/test_local.py +0 -0
  110. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/launchers/test_slurm.py +0 -0
  111. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/restart.py +0 -0
  112. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/restart_main.py +0 -0
  113. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/scripts/notifyandwait.py +0 -0
  114. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/scripts/waitforfile.py +0 -0
  115. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/task_tokens.py +0 -0
  116. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/tasks/__init__.py +0 -0
  117. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/tasks/all.py +0 -0
  118. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/tasks/foreign.py +0 -0
  119. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_checkers.py +0 -0
  120. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_dependencies.py +0 -0
  121. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_forward.py +0 -0
  122. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_identifier.py +0 -0
  123. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_instance.py +0 -0
  124. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_outputs.py +0 -0
  125. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_progress.py +0 -0
  126. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_serializers.py +0 -0
  127. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_snippets.py +0 -0
  128. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_ssh.py +0 -0
  129. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_tags.py +0 -0
  130. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_tasks.py +0 -0
  131. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_tokens.py +0 -0
  132. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_types.py +0 -0
  133. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/test_validation.py +0 -0
  134. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/token_reschedule.py +0 -0
  135. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tests/utils.py +0 -0
  136. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tokens.py +0 -0
  137. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tools/__init__.py +0 -0
  138. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tools/diff.py +0 -0
  139. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tools/documentation.py +0 -0
  140. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/tools/jobs.py +0 -0
  141. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/utils/__init__.py +0 -0
  142. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/utils/asyncio.py +0 -0
  143. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/utils/jobs.py +0 -0
  144. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/utils/jupyter.py +0 -0
  145. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/utils/resources.py +0 -0
  146. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/utils/settings.py +0 -0
  147. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/utils/yaml.py +0 -0
  148. {experimaestro-1.4.3 → experimaestro-1.5.1}/src/experimaestro/xpmutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: experimaestro
3
- Version: 1.4.3
3
+ Version: 1.5.1
4
4
  Summary: "Experimaestro is a computer science experiment manager"
5
5
  Home-page: https://github.com/experimaestro/experimaestro-python
6
6
  License: GPL-3
@@ -23,7 +23,6 @@ Classifier: Programming Language :: Python :: 3.12
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
24
  Requires-Dist: arpeggio (>=2,<3)
25
25
  Requires-Dist: attrs (>=23.1.0,<24.0.0)
26
- Requires-Dist: cached-property (>=1.5.2,<2.0.0) ; python_version < "3.9"
27
26
  Requires-Dist: click (>=8)
28
27
  Requires-Dist: decorator (>=5,<6)
29
28
  Requires-Dist: docstring-parser (>=0.15,<0.16)
@@ -46,7 +45,7 @@ Requires-Dist: rpyc (>=5,<6)
46
45
  Requires-Dist: sortedcontainers (>=2.4,<3.0)
47
46
  Requires-Dist: termcolor (>=2.3)
48
47
  Requires-Dist: tqdm (>=4.66.1,<5.0.0)
49
- Requires-Dist: typing-extensions (>=4.2) ; python_version < "3.11"
48
+ Requires-Dist: typing-extensions (>=4.2) ; python_version < "3.12"
50
49
  Requires-Dist: watchdog (>=2,<3)
51
50
  Project-URL: Documentation, https://experimaestro-python.readthedocs.io/
52
51
  Project-URL: Repository, https://github.com/experimaestro/experimaestro-python
@@ -19,7 +19,7 @@ include = [
19
19
  "src/experimaestro/sphinx/static/experimaestro.css",
20
20
  "src/experimaestro/mkdocs/style.css"
21
21
  ]
22
- version = "1.4.3"
22
+ version = "1.5.1"
23
23
  repository = "https://github.com/experimaestro/experimaestro-python"
24
24
  documentation = "https://experimaestro-python.readthedocs.io/"
25
25
 
@@ -38,8 +38,7 @@ build-backend = "poetry_dynamic_versioning.backend"
38
38
  python = "^3.8"
39
39
  click = ">=8"
40
40
  omegaconf = "^2.3"
41
- typing-extensions = {version = ">=4.2", markers = "python_version < \"3.11\""}
42
- cached-property = {markers = "python_version < \"3.9\"", version = "^1.5.2"}
41
+ typing-extensions = {version = ">=4.2", markers = "python_version < \"3.12\""}
43
42
  attrs = "^23.1.0"
44
43
  fasteners = "^0.19"
45
44
  pyyaml = "^6.0.1"
@@ -119,12 +118,12 @@ max-line-length = "88"
119
118
  extend-ignore = "E203"
120
119
 
121
120
  [tool.mypy]
122
- python_version = "3.8"
121
+ python_version = "3.9"
123
122
  warn_unused_ignores = true
124
123
 
125
124
  [tool.commitizen]
126
125
  name = "cz_conventional_commits"
127
- version = "1.4.3"
126
+ version = "1.5.1"
128
127
  changelog_start_rev = "0.15.0"
129
128
  tag_format = "v$version"
130
129
  update_changelog_on_bump = true
@@ -1,7 +1,8 @@
1
1
  from dataclasses import dataclass
2
- from pathlib import Path, PurePosixPath
2
+ from pathlib import Path, _posix_flavour
3
3
  import io
4
4
  import os
5
+ import re
5
6
  from experimaestro.launcherfinder import LauncherRegistry, YAMLDataClass
6
7
  from fabric import Connection
7
8
  from invoke import Promise
@@ -21,7 +22,7 @@ from experimaestro.tokens import Token
21
22
  # Might be wise to switch to https://github.com/marian-code/ssh-utilities
22
23
 
23
24
 
24
- class SshPath(Path, PurePosixPath):
25
+ class SshPath(Path):
25
26
  """SSH path
26
27
 
27
28
  Absolute:
@@ -31,16 +32,31 @@ class SshPath(Path, PurePosixPath):
31
32
  ssh://[user@]host[:port]/relative/path
32
33
  """
33
34
 
35
+ _flavour = _posix_flavour
36
+
37
+ def __new__(cls, *args, **kwargs):
38
+ return object.__new__(cls)
39
+
40
+ def __init__(self, url: str):
41
+ parsed = urlparse(url)
42
+ assert parsed.scheme == "ssh"
43
+ self._host = parsed.hostname
44
+
45
+ self._parts = re.split(r"/+", parsed.path)
46
+ if parsed.path.startswith("//"):
47
+ self._parts[0] = "/"
48
+ else:
49
+ self._parts = self._parts[1:]
50
+
34
51
  @property
35
52
  def hostpath(self):
36
- # path = "/" if self._parts[:0] + "/".join(self._parts[1:])
37
53
  if self.is_absolute():
38
- return "/" + self._flavour.join(self._parts[1:])
39
- return self._flavour.join(self._parts)
54
+ return "/" + "/".join(self._parts[1:])
55
+ return "/".join(self._parts)
40
56
 
41
57
  @property
42
58
  def host(self):
43
- return self._drv
59
+ return self._host
44
60
 
45
61
  def is_absolute(self):
46
62
  return self._parts and self._parts[0] == "/"
@@ -66,11 +82,15 @@ class SshPath(Path, PurePosixPath):
66
82
 
67
83
  def _make_child(self, args):
68
84
  drv, root, parts = self._parse_args(args)
69
- assert self._drv == drv or drv == "", f"{self._drv} and {drv}"
85
+ assert self._host == drv or drv == "", f"{self._host} and {drv}"
70
86
  drv, root, parts = self._flavour.join_parsed_parts(
71
- "", self._root, self._parts, "", root, parts
87
+ "", "", self._parts, "", root, parts
72
88
  )
73
- return self._from_parsed_parts(self._drv, root, parts)
89
+
90
+ child = object.__new__(SshPath)
91
+ child._parts = parts
92
+ child._host = self._host
93
+ return child
74
94
 
75
95
  def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None):
76
96
  # FIXME: should probably be wiser
@@ -105,10 +125,10 @@ class SshPath(Path, PurePosixPath):
105
125
  return obj
106
126
 
107
127
  def __repr__(self):
108
- return "SshPath(%s,%s)" % (self._drv, self._flavour.join(self._parts[1:]))
128
+ return "SshPath(%s,%s)" % (self._host, self._flavour.join(self._parts[1:]))
109
129
 
110
130
  def __str__(self):
111
- return "ssh://%s/%s" % (self._drv, self._flavour.join(self._parts[1:]))
131
+ return "ssh://%s/%s" % (self._host, self._flavour.join(self._parts[1:]))
112
132
 
113
133
 
114
134
  @dataclass
@@ -646,7 +646,7 @@ class ConfigInformation:
646
646
  "Cannot set non existing attribute %s in %s" % (k, self.xpmtype)
647
647
  )
648
648
  except Exception:
649
- logger.exception("Error while setting value %s" % k)
649
+ logger.error("Error while setting value %s in %s", k, self.xpmtype)
650
650
  raise
651
651
 
652
652
  def addtag(self, name, value):
@@ -1093,7 +1093,7 @@ class ConfigInformation:
1093
1093
  state_dict = {
1094
1094
  "id": id(self.pyobject),
1095
1095
  "module": self.xpmtype._module,
1096
- "type": self.xpmtype.objecttype.__qualname__,
1096
+ "type": self.xpmtype.basetype.__qualname__,
1097
1097
  "typename": self.xpmtype.name(),
1098
1098
  "identifier": self.identifier.state_dict(),
1099
1099
  }
@@ -1340,7 +1340,10 @@ class ConfigInformation:
1340
1340
  cls = getqualattr(mod, definition["type"])
1341
1341
 
1342
1342
  # Creates an object (or a config)
1343
- o = cls.__new__(cls, __xpmobject__=as_instance)
1343
+ if as_instance:
1344
+ o = cls.XPMValue.__new__(cls.XPMValue)
1345
+ else:
1346
+ o = cls.XPMConfig.__new__(cls.XPMConfig)
1344
1347
  assert definition["id"] not in objects, "Duplicate id %s" % definition["id"]
1345
1348
  objects[definition["id"]] = o
1346
1349
 
@@ -1377,6 +1380,7 @@ class ConfigInformation:
1377
1380
  o.__xpm_stdout__ = basepath / f"{name.lower()}.out"
1378
1381
  o.__xpm_stderr__ = basepath / f"{name.lower()}.err"
1379
1382
  else:
1383
+ o.__init__()
1380
1384
  xpminfo = o.__xpm__ # type: ConfigInformation
1381
1385
 
1382
1386
  meta = definition.get("meta", None)
@@ -1497,12 +1501,7 @@ class ConfigInformation:
1497
1501
 
1498
1502
  if o is None:
1499
1503
  # Creates an object (and not a config)
1500
- o = config.__xpmtype__.objecttype.__new__(
1501
- config.__xpmtype__.objecttype, __xpmobject__=True
1502
- )
1503
-
1504
- # And calls the parameter-less initialization
1505
- o.__init__()
1504
+ o = config.XPMValue()
1506
1505
 
1507
1506
  # Store in cache
1508
1507
  self.objects.add_stub(id(config), o)
@@ -1650,8 +1649,8 @@ class TypeConfig:
1650
1649
  [f"{key}={value}" for key, value in self.__xpm__.values.items()]
1651
1650
  )
1652
1651
  return (
1653
- f"{self.__xpmtype__.objecttype.__module__}."
1654
- f"{self.__xpmtype__.objecttype.__qualname__}({params})"
1652
+ f"{self.__xpmtype__.basetype.__module__}."
1653
+ f"{self.__xpmtype__.basetype.__qualname__}({params})"
1655
1654
  )
1656
1655
 
1657
1656
  def tag(self, name, value):
@@ -1770,6 +1769,11 @@ class TypeConfig:
1770
1769
  self.__xpm__.add_dependencies(*other.__xpm__.dependencies)
1771
1770
 
1772
1771
 
1772
+ class classproperty(property):
1773
+ def __get__(self, owner_self, owner_cls):
1774
+ return self.fget(owner_cls)
1775
+
1776
+
1773
1777
  class Config:
1774
1778
  """Base type for all objects in python interface"""
1775
1779
 
@@ -1781,6 +1785,42 @@ class Config:
1781
1785
  """The __xpm__ object contains all instance specific information about a
1782
1786
  configuration/task"""
1783
1787
 
1788
+ @classproperty
1789
+ def XPMConfig(cls):
1790
+ if issubclass(cls, TypeConfig):
1791
+ return cls
1792
+ return cls.__getxpmtype__().configtype
1793
+
1794
+ @classproperty
1795
+ def C(cls):
1796
+ return cls.XPMConfig
1797
+
1798
+ @classproperty
1799
+ def XPMValue(cls):
1800
+ """Returns the value object for this configuration"""
1801
+ if issubclass(cls, TypeConfig):
1802
+ return cls.__xpmtype__.objecttype
1803
+
1804
+ if value_cls := cls.__dict__.get("__XPMValue__", None):
1805
+ pass
1806
+ else:
1807
+ from .types import XPMValue
1808
+
1809
+ __objectbases__ = tuple(
1810
+ s.XPMValue
1811
+ for s in cls.__bases__
1812
+ if issubclass(s, Config) and (s is not Config)
1813
+ ) or (XPMValue,)
1814
+
1815
+ *tp_qual, tp_name = cls.__qualname__.split(".")
1816
+ value_cls = type(f"{tp_name}.XPMValue", (cls,) + __objectbases__, {})
1817
+ value_cls.__qualname__ = ".".join(tp_qual + [value_cls.__name__])
1818
+ value_cls.__module__ = cls.__module__
1819
+
1820
+ setattr(cls, "__XPMValue__", value_cls)
1821
+
1822
+ return value_cls
1823
+
1784
1824
  @classmethod
1785
1825
  def __getxpmtype__(cls) -> "ObjectType":
1786
1826
  """Get (and create if necessary) the Object type of this"""
@@ -1796,27 +1836,32 @@ class Config:
1796
1836
  raise
1797
1837
  return xpmtype
1798
1838
 
1799
- def __getnewargs_ex__(self):
1800
- # __new__ will be called with those arguments when unserializing
1801
- return ((), {"__xpmobject__": True})
1839
+ def __new__(cls: Type[T], *args, **kwargs) -> T:
1840
+ """Returns an instance of a TypeConfig (for compatibility, use XPMConfig
1841
+ or C if possible)"""
1802
1842
 
1803
- @classmethod
1804
- def c(cls: Type[T], **kwargs) -> T:
1805
- """Allows typing to process easily"""
1806
- return cls.__new__(cls, **kwargs)
1843
+ # If this is an XPMValue, just return a new instance
1844
+ from experimaestro.core.types import XPMValue
1807
1845
 
1808
- def __new__(cls: Type[T], *args, __xpmobject__=False, **kwargs) -> T:
1809
- """Returns an instance of a TypeConfig when called __xpmobject__ is False,
1810
- and otherwise the real object
1811
- """
1846
+ if issubclass(cls, XPMValue):
1847
+ return object.__new__(cls)
1812
1848
 
1813
- if __xpmobject__:
1814
- # __init__ is called directly
1849
+ # If this is the XPMConfig, just return a new instance
1850
+ # __init__ will be called
1851
+ if issubclass(cls, TypeConfig):
1815
1852
  return object.__new__(cls)
1816
1853
 
1817
- # We use the configuration type
1818
- o = object.__new__(cls.__getxpmtype__().configtype)
1819
- o.__init__(*args, **kwargs)
1854
+ # otherwise, we use the configuration type
1855
+ o: TypeConfig = object.__new__(cls.__getxpmtype__().configtype)
1856
+ try:
1857
+ o.__init__(*args, **kwargs)
1858
+ except Exception:
1859
+ caller = inspect.getframeinfo(inspect.stack()[1][0])
1860
+ logger.error(
1861
+ "Init error in %s:%s"
1862
+ % (str(Path(caller.filename).absolute()), caller.lineno)
1863
+ )
1864
+ raise
1820
1865
  return o
1821
1866
 
1822
1867
  def __validate__(self):
@@ -27,13 +27,16 @@ from typing import (
27
27
  Callable,
28
28
  ClassVar,
29
29
  Dict,
30
+ Generic,
30
31
  List,
31
32
  Optional,
32
33
  Set,
34
+ Type,
33
35
  TypeVar,
34
36
  Union,
35
37
  overload,
36
38
  )
39
+ from typing_extensions import Self
37
40
 
38
41
  TConfig = TypeVar("TConfig", bound="Config")
39
42
 
@@ -205,12 +208,15 @@ class TypeConfig:
205
208
  class Config:
206
209
  __xpmtype__: ClassVar[ObjectType]
207
210
  __xpm__: ConfigInformation
211
+ __use_xpmobject__: ClassVar[bool]
212
+
213
+ XPMValue: Type[Self]
214
+ XPMConfig: Union[Type[Self], Type[TypeConfig[Self]]]
215
+ C: Union[Type[Self], Type[TypeConfig[Self]]]
216
+
208
217
  @classmethod
209
218
  def __getxpmtype__(cls) -> ObjectType: ...
210
- def __getnewargs_ex__(self): ...
211
- @classmethod
212
- def c(cls, **kwargs) -> T: ...
213
- def __new__(cls, *args, __xpmobject__: bool = ..., **kwargs) -> T: ...
219
+ def __new__(cls, *args, **kwargs) -> Self: ...
214
220
  def __validate__(self) -> None: ...
215
221
  def __post_init__(self) -> None: ...
216
222
  def __json__(self): ...
@@ -240,6 +246,6 @@ class Task(LightweightTask):
240
246
  def copyconfig(config_or_output: TConfig, **kwargs) -> TConfig: ...
241
247
  def setmeta(config: TConfig, flag: bool) -> TConfig: ...
242
248
 
243
- class TypeConfig:
249
+ class TypeConfig(Generic[T]):
244
250
  def __validate__(self):
245
251
  pass
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  import inspect
3
3
  import sys
4
- from typing import Set, Union, Dict, Iterator, List
4
+ from typing import Set, Union, Dict, Iterator, List, get_args, get_origin
5
5
  from collections import ChainMap
6
6
  from pathlib import Path
7
7
  import typing
@@ -126,6 +126,10 @@ class Type:
126
126
  if t:
127
127
  return DictType(Type.fromType(t[0]), Type.fromType(t[1]))
128
128
 
129
+ # Takes care of generics
130
+ if get_origin(key):
131
+ return GenericType(key)
132
+
129
133
  raise Exception("No type found for %s", key)
130
134
 
131
135
 
@@ -179,6 +183,12 @@ class SubmitHook(ABC):
179
183
  return hash((self.__class__, self.spec()))
180
184
 
181
185
 
186
+ class XPMValue:
187
+ """Jut marks a XPMValue"""
188
+
189
+ pass
190
+
191
+
182
192
  class ObjectType(Type):
183
193
  submit_hooks: Set[SubmitHook]
184
194
  """Hooks associated with this configuration"""
@@ -211,6 +221,8 @@ class ObjectType(Type):
211
221
  # Get the identifier
212
222
  if identifier is None and hasattr(tp, "__xpmid__"):
213
223
  __xpmid__ = getattr(tp, "__xpmid__")
224
+ if isinstance(__xpmid__, Identifier):
225
+ identifier = __xpmid__
214
226
  if inspect.ismethod(__xpmid__):
215
227
  identifier = Identifier(__xpmid__())
216
228
  elif "__xpmid__" in tp.__dict__:
@@ -247,7 +259,7 @@ class ObjectType(Type):
247
259
  else:
248
260
  self.basetype = tp
249
261
 
250
- # Create the type-specific configuration class
262
+ # --- Create the type-specific configuration class (XPMConfig)
251
263
  __configbases__ = tuple(
252
264
  s.__getxpmtype__().configtype
253
265
  for s in tp.__bases__
@@ -256,23 +268,18 @@ class ObjectType(Type):
256
268
 
257
269
  *tp_qual, tp_name = self.basetype.__qualname__.split(".")
258
270
  self.configtype = type(
259
- f"{tp_name}_XPMConfig", __configbases__ + (self.basetype,), {}
271
+ f"{tp_name}.XPMConfig", __configbases__ + (self.basetype,), {}
260
272
  )
261
273
  self.configtype.__qualname__ = ".".join(tp_qual + [self.configtype.__name__])
262
274
  self.configtype.__module__ = tp.__module__
263
275
 
264
- # Create the type-specific object class
265
- # (now, the same as basetype - but in the future, remove references)
266
- self.objecttype = self.basetype # type: type
267
- self.basetype._ = self.configtype
268
-
269
276
  # Return type is used by tasks to change the output
270
277
  if hasattr(self.basetype, "task_outputs") or False:
271
278
  self.returntype = get_type_hints(
272
279
  getattr(self.basetype, "task_outputs")
273
280
  ).get("return", typing.Any)
274
281
  else:
275
- self.returntype = self.objecttype
282
+ self.returntype = self.basetype
276
283
 
277
284
  # Registers ourselves
278
285
  self.basetype.__xpmtype__ = self
@@ -284,6 +291,11 @@ class ObjectType(Type):
284
291
  self.annotations = []
285
292
  self._deprecated = False
286
293
 
294
+ @property
295
+ def objecttype(self):
296
+ """Returns the object type"""
297
+ return self.basetype.XPMValue
298
+
287
299
  def addAnnotation(self, annotation):
288
300
  assert not self.__initialized__
289
301
  self.annotations.append(annotation)
@@ -636,3 +648,31 @@ class DictType(Type):
636
648
 
637
649
  def __repr__(self):
638
650
  return str(self)
651
+
652
+
653
+ class GenericType(Type):
654
+ def __init__(self, type: typing.Type):
655
+ self.type = type
656
+ self.origin = get_origin(type)
657
+
658
+ self.args = get_args(type)
659
+
660
+ def name(self):
661
+ return str(self.type)
662
+
663
+ def __repr__(self):
664
+ return repr(self.type)
665
+
666
+ def validate(self, value):
667
+ # Now, let's check generics...
668
+ mros = typingutils.generic_mro(type(value))
669
+ matching = next(
670
+ (mro for mro in mros if (get_origin(mro) or mro) is self.origin), None
671
+ )
672
+ target = get_origin(self.type) or self.type
673
+ if matching is None:
674
+ raise ValueError(
675
+ f"{type(value)} is not of type {target} ({type(value).__mro__})"
676
+ )
677
+
678
+ return value
@@ -0,0 +1,2 @@
1
+ class HandledException(Exception):
2
+ pass
@@ -1,4 +1,5 @@
1
1
  import inspect
2
+ import itertools
2
3
  import json
3
4
  import logging
4
5
  import sys
@@ -10,7 +11,8 @@ import omegaconf
10
11
  import yaml
11
12
  from experimaestro import LauncherRegistry, RunMode, experiment
12
13
  from experimaestro.experiments.configuration import ConfigurationBase
13
- from experimaestro.settings import get_workspace
14
+ from experimaestro.exceptions import HandledException
15
+ from experimaestro.settings import get_settings, get_workspace
14
16
  from omegaconf import OmegaConf, SCMode
15
17
  from termcolor import cprint
16
18
 
@@ -63,7 +65,7 @@ def load(yaml_file: Path):
63
65
  if parent := _data.get("parent", None):
64
66
  data.extend(load(yaml_file.parent / parent))
65
67
 
66
- return data
68
+ return data[::-1]
67
69
 
68
70
 
69
71
  @click.option("--debug", is_flag=True, help="Print debug information")
@@ -110,10 +112,16 @@ def load(yaml_file: Path):
110
112
  help="Working directory - if None, uses the default XPM " "working directory",
111
113
  )
112
114
  @click.option("--conf", "-c", "extra_conf", type=str, multiple=True)
115
+ @click.option(
116
+ "--pre-yaml", type=str, multiple=True, help="Add YAML file after the main one"
117
+ )
118
+ @click.option(
119
+ "--post-yaml", type=str, multiple=True, help="Add YAML file before the main one"
120
+ )
113
121
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
114
122
  @click.argument("yaml_file", metavar="YAML file", type=str)
115
123
  @click.command()
116
- def experiments_cli(
124
+ def experiments_cli( # noqa: C901
117
125
  yaml_file: str,
118
126
  xp_file: str,
119
127
  host: str,
@@ -123,6 +131,8 @@ def experiments_cli(
123
131
  env: List[Tuple[str, str]],
124
132
  run_mode: RunMode,
125
133
  extra_conf: List[str],
134
+ pre_yaml: List[str],
135
+ post_yaml: List[str],
126
136
  args: List[str],
127
137
  show: bool,
128
138
  debug: bool,
@@ -133,13 +143,17 @@ def experiments_cli(
133
143
  logging.getLogger("xpm.hash").setLevel(logging.INFO)
134
144
 
135
145
  # --- Loads the YAML
136
- yamls = load(Path(yaml_file))
146
+ yamls = []
147
+ for y in pre_yaml:
148
+ yamls.extend(load(Path(y)))
149
+ yamls.extend(load(Path(yaml_file)))
150
+ for y in post_yaml:
151
+ yamls.extend(load(Path(y)))
137
152
 
138
153
  # --- Get the XP file
139
154
  if xp_file is None:
140
- for data in yamls:
141
- if xp_file := data.get("file"):
142
- del data["file"]
155
+ for data in yamls[::-1]:
156
+ if xp_file := data.get("file", None):
143
157
  break
144
158
 
145
159
  if xp_file is None:
@@ -201,31 +215,44 @@ def experiments_cli(
201
215
  cprint(f"Error in configuration:\n\n{e}", "red", file=sys.stderr)
202
216
  sys.exit(1)
203
217
 
204
- # Move to an object container
205
- configuration: schema = OmegaConf.to_container(
206
- configuration, structured_config_mode=SCMode.INSTANTIATE
207
- )
208
-
209
218
  if show:
210
219
  print(json.dumps(OmegaConf.to_container(configuration))) # noqa: T201
211
220
  sys.exit(0)
212
221
 
222
+ # Move to an object container
223
+ configuration = OmegaConf.to_container(
224
+ configuration, structured_config_mode=SCMode.INSTANTIATE
225
+ )
226
+
213
227
  # Get the working directory
214
- if workdir is None or not Path(workdir).is_dir():
215
- workdir = get_workspace(workdir).path.expanduser().resolve()
216
- logging.info("Using working directory %s", workdir)
228
+ settings = get_settings()
229
+ ws_env = {}
230
+ workdir = Path(workdir) if workdir else None
231
+ if (workdir is None) or (not workdir.is_dir()):
232
+ logging.info("Searching for workspace %s", workdir)
233
+ ws_settings = get_workspace(str(workdir))
234
+ workdir = ws_settings.path.expanduser()
235
+ ws_env = ws_settings.env
236
+
237
+ logging.info("Using working directory %s", str(workdir.resolve()))
217
238
 
218
239
  # --- Runs the experiment
219
240
  with experiment(
220
241
  workdir, configuration.id, host=host, port=port, run_mode=run_mode
221
242
  ) as xp:
222
243
  # Set up the environment
223
- for key, value in env:
244
+ # (1) global settings (2) workspace settings and (3) command line settings
245
+ for key, value in itertools.chain(settings.env.items(), ws_env.items(), env):
246
+ logging.info("Setting environment: %s=%s", key, value)
224
247
  xp.setenv(key, value)
225
248
 
226
- # Run the experiment
227
- helper.xp = xp
228
- helper.run(list(args), configuration)
249
+ try:
250
+ # Run the experiment
251
+ helper.xp = xp
252
+ helper.run(list(args), configuration)
253
+
254
+ # ... and wait
255
+ xp.wait()
229
256
 
230
- # ... and wait
231
- xp.wait()
257
+ except HandledException:
258
+ sys.exit(1)
@@ -20,14 +20,28 @@ def configuration(*args, **kwargs):
20
20
 
21
21
  @configuration()
22
22
  class ConfigurationBase:
23
+ """Base configuration for any experiment"""
24
+
23
25
  id: str = MISSING
24
- """ID of the experiment"""
26
+ """ID of the experiment
25
27
 
26
- description: str = ""
27
- """Description of the experiment"""
28
+ This ID is used by experimaestro when running as the experiment.
29
+ """
28
30
 
29
31
  file: str = "experiment"
30
- """qualified name (relative to the module) for the file containing a run function"""
32
+ """Relative path of the file containing a run function"""
31
33
 
32
34
  parent: Optional[str] = None
33
35
  """Relative path of a YAML file that should be merged"""
36
+
37
+ title: str = ""
38
+ """Short description of the experiment"""
39
+
40
+ subtitle: str = ""
41
+ """Allows to give some more details about the experiment"""
42
+
43
+ paper: str = ""
44
+ """Source paper for this experiment"""
45
+
46
+ description: str = ""
47
+ """Description of the experiment"""
@@ -54,8 +54,12 @@ def duration():
54
54
  return "duration", "=", RegExMatch(r"\d+"), RegExMatch(r"h(ours)?|d(ays)?")
55
55
 
56
56
 
57
+ def one_spec():
58
+ return OneOrMore(OrderedChoice([duration, cuda, cpu]), sep="&")
59
+
60
+
57
61
  def grammar():
58
- return OneOrMore(OrderedChoice([duration, cuda, cpu]), sep="&"), EndOfFile()
62
+ return OneOrMore(one_spec, sep="|"), EndOfFile()
59
63
 
60
64
 
61
65
  # ---- Visitor
@@ -63,6 +67,9 @@ def grammar():
63
67
 
64
68
  class Visitor(PTNodeVisitor):
65
69
  def visit_grammar(self, node, children):
70
+ return [child for child in children]
71
+
72
+ def visit_one_spec(self, node, children):
66
73
  return reduce(lambda x, el: x & el, children)
67
74
 
68
75
  def visit_duration(self, node, children):