experimaestro 1.11.1__py3-none-any.whl → 2.0.0b4__py3-none-any.whl
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/__init__.py +10 -11
- experimaestro/annotations.py +167 -206
- experimaestro/cli/__init__.py +140 -16
- experimaestro/cli/filter.py +42 -74
- experimaestro/cli/jobs.py +157 -106
- experimaestro/cli/progress.py +269 -0
- experimaestro/cli/refactor.py +249 -0
- experimaestro/click.py +0 -1
- experimaestro/commandline.py +19 -3
- experimaestro/connectors/__init__.py +22 -3
- experimaestro/connectors/local.py +12 -0
- experimaestro/core/arguments.py +192 -37
- experimaestro/core/identifier.py +127 -12
- experimaestro/core/objects/__init__.py +6 -0
- experimaestro/core/objects/config.py +702 -285
- experimaestro/core/objects/config_walk.py +24 -6
- experimaestro/core/serialization.py +91 -34
- experimaestro/core/serializers.py +1 -8
- experimaestro/core/subparameters.py +164 -0
- experimaestro/core/types.py +198 -83
- experimaestro/exceptions.py +26 -0
- experimaestro/experiments/cli.py +107 -25
- experimaestro/generators.py +50 -9
- experimaestro/huggingface.py +3 -1
- experimaestro/launcherfinder/parser.py +29 -0
- experimaestro/launcherfinder/registry.py +3 -3
- experimaestro/launchers/__init__.py +26 -1
- experimaestro/launchers/direct.py +12 -0
- experimaestro/launchers/slurm/base.py +154 -2
- experimaestro/mkdocs/base.py +6 -8
- experimaestro/mkdocs/metaloader.py +0 -1
- experimaestro/mypy.py +452 -7
- experimaestro/notifications.py +75 -16
- experimaestro/progress.py +404 -0
- experimaestro/rpyc.py +0 -1
- experimaestro/run.py +19 -6
- experimaestro/scheduler/__init__.py +18 -1
- experimaestro/scheduler/base.py +504 -959
- experimaestro/scheduler/dependencies.py +43 -28
- experimaestro/scheduler/dynamic_outputs.py +259 -130
- experimaestro/scheduler/experiment.py +582 -0
- experimaestro/scheduler/interfaces.py +474 -0
- experimaestro/scheduler/jobs.py +485 -0
- experimaestro/scheduler/services.py +186 -12
- experimaestro/scheduler/signal_handler.py +32 -0
- experimaestro/scheduler/state.py +1 -1
- experimaestro/scheduler/state_db.py +388 -0
- experimaestro/scheduler/state_provider.py +2345 -0
- experimaestro/scheduler/state_sync.py +834 -0
- experimaestro/scheduler/workspace.py +52 -10
- experimaestro/scriptbuilder.py +7 -0
- experimaestro/server/__init__.py +153 -32
- experimaestro/server/data/index.css +0 -125
- experimaestro/server/data/index.css.map +1 -1
- experimaestro/server/data/index.js +194 -58
- experimaestro/server/data/index.js.map +1 -1
- experimaestro/settings.py +47 -6
- experimaestro/sphinx/__init__.py +3 -3
- experimaestro/taskglobals.py +20 -0
- experimaestro/tests/conftest.py +80 -0
- experimaestro/tests/core/test_generics.py +2 -2
- experimaestro/tests/identifier_stability.json +45 -0
- experimaestro/tests/launchers/bin/sacct +6 -2
- experimaestro/tests/launchers/bin/sbatch +4 -2
- experimaestro/tests/launchers/common.py +2 -2
- experimaestro/tests/launchers/test_slurm.py +80 -0
- experimaestro/tests/restart.py +1 -1
- experimaestro/tests/tasks/all.py +7 -0
- experimaestro/tests/tasks/test_dynamic.py +231 -0
- experimaestro/tests/test_checkers.py +2 -2
- experimaestro/tests/test_cli_jobs.py +615 -0
- experimaestro/tests/test_dependencies.py +11 -17
- experimaestro/tests/test_deprecated.py +630 -0
- experimaestro/tests/test_environment.py +200 -0
- experimaestro/tests/test_experiment.py +3 -3
- experimaestro/tests/test_file_progress.py +425 -0
- experimaestro/tests/test_file_progress_integration.py +477 -0
- experimaestro/tests/test_forward.py +3 -3
- experimaestro/tests/test_generators.py +93 -0
- experimaestro/tests/test_identifier.py +520 -169
- experimaestro/tests/test_identifier_stability.py +458 -0
- experimaestro/tests/test_instance.py +16 -21
- experimaestro/tests/test_multitoken.py +442 -0
- experimaestro/tests/test_mypy.py +433 -0
- experimaestro/tests/test_objects.py +314 -30
- experimaestro/tests/test_outputs.py +8 -8
- experimaestro/tests/test_param.py +22 -26
- experimaestro/tests/test_partial_paths.py +231 -0
- experimaestro/tests/test_progress.py +2 -50
- experimaestro/tests/test_resumable_task.py +480 -0
- experimaestro/tests/test_serializers.py +141 -60
- experimaestro/tests/test_state_db.py +434 -0
- experimaestro/tests/test_subparameters.py +160 -0
- experimaestro/tests/test_tags.py +151 -15
- experimaestro/tests/test_tasks.py +137 -160
- experimaestro/tests/test_token_locking.py +252 -0
- experimaestro/tests/test_tokens.py +25 -19
- experimaestro/tests/test_types.py +133 -11
- experimaestro/tests/test_validation.py +19 -19
- experimaestro/tests/test_workspace_triggers.py +158 -0
- experimaestro/tests/token_reschedule.py +5 -3
- experimaestro/tests/utils.py +2 -2
- experimaestro/tokens.py +154 -57
- experimaestro/tools/diff.py +8 -1
- experimaestro/tui/__init__.py +8 -0
- experimaestro/tui/app.py +2303 -0
- experimaestro/tui/app.tcss +353 -0
- experimaestro/tui/log_viewer.py +228 -0
- experimaestro/typingutils.py +11 -2
- experimaestro/utils/__init__.py +23 -0
- experimaestro/utils/environment.py +148 -0
- experimaestro/utils/git.py +129 -0
- experimaestro/utils/resources.py +1 -1
- experimaestro/version.py +34 -0
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info}/METADATA +70 -39
- experimaestro-2.0.0b4.dist-info/RECORD +181 -0
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info}/WHEEL +1 -1
- experimaestro-2.0.0b4.dist-info/entry_points.txt +16 -0
- experimaestro/compat.py +0 -6
- experimaestro/core/objects.pyi +0 -225
- experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
- experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
- experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
- experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
- experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
- experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
- experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
- experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
- experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
- experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
- experimaestro-1.11.1.dist-info/RECORD +0 -158
- experimaestro-1.11.1.dist-info/entry_points.txt +0 -17
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info/licenses}/LICENSE +0 -0
experimaestro/__init__.py
CHANGED
|
@@ -4,15 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
# Annotations
|
|
6
6
|
from .annotations import (
|
|
7
|
-
config,
|
|
8
|
-
task,
|
|
9
|
-
param,
|
|
10
|
-
ConstantParam,
|
|
11
|
-
constant,
|
|
12
|
-
option,
|
|
13
|
-
pathoption,
|
|
14
7
|
cache,
|
|
15
|
-
Identifier,
|
|
16
8
|
Array,
|
|
17
9
|
TagDict,
|
|
18
10
|
tag,
|
|
@@ -21,12 +13,11 @@ from .annotations import (
|
|
|
21
13
|
STDOUT,
|
|
22
14
|
STDERR,
|
|
23
15
|
deprecate,
|
|
24
|
-
# deprecated
|
|
25
|
-
argument,
|
|
26
16
|
initializer,
|
|
27
17
|
# Method
|
|
28
18
|
config_only,
|
|
29
19
|
)
|
|
20
|
+
from .core.types import Identifier
|
|
30
21
|
from .core.serialization import (
|
|
31
22
|
load,
|
|
32
23
|
save,
|
|
@@ -47,15 +38,22 @@ from .core.arguments import (
|
|
|
47
38
|
field,
|
|
48
39
|
# Annotations helpers
|
|
49
40
|
help,
|
|
50
|
-
default,
|
|
51
41
|
)
|
|
52
42
|
from .generators import pathgenerator, PathGenerator
|
|
43
|
+
from .core.subparameters import (
|
|
44
|
+
subparameters,
|
|
45
|
+
param_group,
|
|
46
|
+
ParameterGroup,
|
|
47
|
+
Subparameters,
|
|
48
|
+
)
|
|
53
49
|
from .core.objects import (
|
|
54
50
|
Config,
|
|
51
|
+
InstanceConfig,
|
|
55
52
|
copyconfig,
|
|
56
53
|
setmeta,
|
|
57
54
|
DependentMarker,
|
|
58
55
|
Task,
|
|
56
|
+
ResumableTask,
|
|
59
57
|
LightweightTask,
|
|
60
58
|
ObjectStore,
|
|
61
59
|
)
|
|
@@ -64,6 +62,7 @@ from .core.serializers import SerializationLWTask, PathSerializationLWTask
|
|
|
64
62
|
from .core.types import Any, SubmitHook
|
|
65
63
|
from .launchers import Launcher
|
|
66
64
|
from .scheduler import Scheduler, experiment, FailedExperiment
|
|
65
|
+
from .exceptions import GracefulTimeout
|
|
67
66
|
from .scheduler.workspace import Workspace, RunMode
|
|
68
67
|
from .scheduler.state import get_experiment
|
|
69
68
|
from .notifications import progress, tqdm
|
experimaestro/annotations.py
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
# Import Python modules
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
from
|
|
5
|
-
from typing import Callable, Type as TypingType, Optional, TypeVar, Union
|
|
4
|
+
from typing import Callable, Type as TypingType, TypeVar, Union
|
|
6
5
|
from sortedcontainers import SortedDict
|
|
7
6
|
import experimaestro.core.objects as objects
|
|
8
7
|
import experimaestro.core.types as types
|
|
9
|
-
from experimaestro.generators import PathGenerator
|
|
10
8
|
|
|
11
|
-
from .core.
|
|
12
|
-
from .core.
|
|
13
|
-
from .core.types import Any, Identifier, TypeProxy, Type, ObjectType
|
|
9
|
+
from .core.objects import Config, Task
|
|
10
|
+
from .core.types import Any, TypeProxy, Type
|
|
14
11
|
from .utils import logger
|
|
15
|
-
from .checkers import Checker
|
|
16
12
|
|
|
17
13
|
# --- Annotations to define tasks and types
|
|
18
14
|
|
|
@@ -24,56 +20,6 @@ def configmethod(method):
|
|
|
24
20
|
return method
|
|
25
21
|
|
|
26
22
|
|
|
27
|
-
class config:
|
|
28
|
-
"""Annotations for experimaestro types"""
|
|
29
|
-
|
|
30
|
-
def __init__(self, identifier=None, description=None):
|
|
31
|
-
"""[summary]
|
|
32
|
-
|
|
33
|
-
Keyword Arguments:
|
|
34
|
-
identifier {Identifier, str} -- Unique identifier of the type,
|
|
35
|
-
generate by default (None)
|
|
36
|
-
|
|
37
|
-
description {str} -- (deprecated, use
|
|
38
|
-
comments) Description of the config/task, use comments with
|
|
39
|
-
(default) None
|
|
40
|
-
|
|
41
|
-
register {bool} -- False if the type should not be
|
|
42
|
-
registered (debug only)
|
|
43
|
-
|
|
44
|
-
The identifier, if not specified, will be set to `X.CLASSNAME`(by order
|
|
45
|
-
of priority), where X is:
|
|
46
|
-
- the parent identifier
|
|
47
|
-
- the module qualified name
|
|
48
|
-
"""
|
|
49
|
-
super().__init__()
|
|
50
|
-
self.identifier = identifier
|
|
51
|
-
if isinstance(self.identifier, str):
|
|
52
|
-
self.identifier = Identifier(self.identifier)
|
|
53
|
-
|
|
54
|
-
self.description = description
|
|
55
|
-
|
|
56
|
-
def __call__(self, tp: T) -> T:
|
|
57
|
-
"""Annotate the class
|
|
58
|
-
|
|
59
|
-
Depending on whether we are running or configuring,
|
|
60
|
-
the behavior is different:
|
|
61
|
-
|
|
62
|
-
- when configuring, we return a proxy class
|
|
63
|
-
- when running, we return the same class
|
|
64
|
-
|
|
65
|
-
Arguments:
|
|
66
|
-
tp {type} -- The type
|
|
67
|
-
|
|
68
|
-
Keyword Arguments:
|
|
69
|
-
basetype {type} -- The base type of the class (Task or Config)
|
|
70
|
-
"""
|
|
71
|
-
assert inspect.isclass(tp), f"{tp} is not a class"
|
|
72
|
-
|
|
73
|
-
# Adds to xpminfo for on demand creation of information
|
|
74
|
-
return ObjectType(tp, identifier=self.identifier).basetype
|
|
75
|
-
|
|
76
|
-
|
|
77
23
|
class Array(TypeProxy):
|
|
78
24
|
"""Array of object"""
|
|
79
25
|
|
|
@@ -94,121 +40,12 @@ class Choice(TypeProxy):
|
|
|
94
40
|
return types.StringType
|
|
95
41
|
|
|
96
42
|
|
|
97
|
-
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
def __init__(self, identifier=None, pythonpath=None, description=None):
|
|
101
|
-
super().__init__(identifier, description)
|
|
102
|
-
self.pythonpath = pythonpath
|
|
103
|
-
|
|
104
|
-
def __call__(self, tp) -> type:
|
|
105
|
-
# Register the type
|
|
106
|
-
tp = super().__call__(tp)
|
|
107
|
-
|
|
108
|
-
def factory(xpmtype):
|
|
109
|
-
return xpmtype.getpythontaskcommand(self.pythonpath)
|
|
110
|
-
|
|
111
|
-
tp.__getxpmtype__().taskcommandfactory = factory
|
|
112
|
-
return tp
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# --- argument related annotations
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
class param:
|
|
119
|
-
"""Defines an argument for an experimaestro type"""
|
|
120
|
-
|
|
121
|
-
def __init__(
|
|
122
|
-
self,
|
|
123
|
-
name,
|
|
124
|
-
type=None,
|
|
125
|
-
default=None,
|
|
126
|
-
required: bool = None,
|
|
127
|
-
ignored: Optional[bool] = None,
|
|
128
|
-
help: Optional[str] = None,
|
|
129
|
-
checker: Optional[Checker] = None,
|
|
130
|
-
constant: bool = False,
|
|
131
|
-
):
|
|
132
|
-
# Determine if required
|
|
133
|
-
self.name = name
|
|
134
|
-
self.type = Type.fromType(type) if type else None
|
|
135
|
-
self.help = help
|
|
136
|
-
self.ignored = ignored
|
|
137
|
-
self.required = required
|
|
138
|
-
self.checker = checker
|
|
139
|
-
self.constant = constant
|
|
140
|
-
|
|
141
|
-
self.generator = None
|
|
142
|
-
self.default = None
|
|
143
|
-
|
|
144
|
-
# Set default or generator
|
|
145
|
-
if isinstance(default, field):
|
|
146
|
-
if default.default is not None:
|
|
147
|
-
self.default = default
|
|
148
|
-
elif default.default_factory is not None:
|
|
149
|
-
self.generator = default.default_factory
|
|
150
|
-
else:
|
|
151
|
-
self.default = default
|
|
152
|
-
|
|
153
|
-
def __call__(self, tp):
|
|
154
|
-
# Don't annotate in task mode
|
|
155
|
-
tp.__getxpmtype__().addAnnotation(self)
|
|
156
|
-
return tp
|
|
157
|
-
|
|
158
|
-
def process(self, xpmtype):
|
|
159
|
-
# Get type from default if needed
|
|
160
|
-
if self.type is None:
|
|
161
|
-
if self.default is not None:
|
|
162
|
-
self.type = Type.fromType(type(self.default))
|
|
163
|
-
|
|
164
|
-
# Type = any if no type
|
|
165
|
-
if self.type is None:
|
|
166
|
-
self.type = types.Any
|
|
167
|
-
|
|
168
|
-
argument = CoreArgument(
|
|
169
|
-
self.name,
|
|
170
|
-
self.type,
|
|
171
|
-
help=self.help,
|
|
172
|
-
required=self.required,
|
|
173
|
-
ignored=self.ignored,
|
|
174
|
-
generator=self.generator,
|
|
175
|
-
default=self.default,
|
|
176
|
-
checker=self.checker,
|
|
177
|
-
constant=self.constant,
|
|
178
|
-
)
|
|
179
|
-
xpmtype.addArgument(argument)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# Just a rebind (back-compatibility)
|
|
183
|
-
argument = param
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class option(param):
|
|
187
|
-
"""An argument which is ignored
|
|
188
|
-
|
|
189
|
-
See argument
|
|
190
|
-
"""
|
|
191
|
-
|
|
192
|
-
def __init__(self, *args, **kwargs):
|
|
193
|
-
kwargs["ignored"] = True
|
|
194
|
-
super().__init__(*args, **kwargs)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
class pathoption(param):
|
|
198
|
-
"""Defines a an argument that will be a relative path (automatically
|
|
199
|
-
set by experimaestro)"""
|
|
200
|
-
|
|
201
|
-
def __init__(self, name: str, path=None, help=""):
|
|
202
|
-
"""
|
|
203
|
-
:param name: The name of argument (in python)
|
|
204
|
-
:param path: The relative path or a function
|
|
205
|
-
"""
|
|
206
|
-
super().__init__(name, type=Path, help=help)
|
|
43
|
+
def config_only(method):
|
|
44
|
+
"""Marks a configuration-only method"""
|
|
45
|
+
assert inspect.ismethod(method)
|
|
207
46
|
|
|
208
|
-
if path is None:
|
|
209
|
-
path = name
|
|
210
47
|
|
|
211
|
-
|
|
48
|
+
# --- Path generators for task stdout/stderr
|
|
212
49
|
|
|
213
50
|
|
|
214
51
|
def STDERR(jobcontext, config):
|
|
@@ -219,28 +56,31 @@ def STDOUT(jobcontext, config):
|
|
|
219
56
|
return "%s.out" % jobcontext.name
|
|
220
57
|
|
|
221
58
|
|
|
222
|
-
|
|
223
|
-
"""
|
|
224
|
-
A constant argument (useful for versionning tasks)
|
|
225
|
-
"""
|
|
226
|
-
|
|
227
|
-
def __init__(self, name: str, value, type=None, help=""):
|
|
228
|
-
super().__init__(name, default=value, constant=True, type=type, help=help)
|
|
229
|
-
|
|
59
|
+
# --- Cache
|
|
230
60
|
|
|
231
|
-
ConstantParam = constant
|
|
232
61
|
|
|
62
|
+
def cache(name: str):
|
|
63
|
+
"""Decorator for caching method results to disk.
|
|
233
64
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
assert inspect.ismethod(method)
|
|
65
|
+
The cache is stored in the workspace's config directory, keyed by the
|
|
66
|
+
configuration's identifier.
|
|
237
67
|
|
|
68
|
+
Example::
|
|
238
69
|
|
|
239
|
-
|
|
70
|
+
class MyConfig(Config):
|
|
71
|
+
data_path: Param[Path]
|
|
240
72
|
|
|
73
|
+
@cache("processed.pkl")
|
|
74
|
+
def process(self, cache_path: Path):
|
|
75
|
+
if cache_path.exists():
|
|
76
|
+
return pickle.load(cache_path.open("rb"))
|
|
77
|
+
result = expensive_computation(self.data_path)
|
|
78
|
+
pickle.dump(result, cache_path.open("wb"))
|
|
79
|
+
return result
|
|
241
80
|
|
|
242
|
-
|
|
243
|
-
|
|
81
|
+
:param name: Filename for the cache file
|
|
82
|
+
:return: A decorator that wraps the method with caching logic
|
|
83
|
+
"""
|
|
244
84
|
|
|
245
85
|
def annotate(method):
|
|
246
86
|
return objects.cache(method, name)
|
|
@@ -252,7 +92,22 @@ def cache(name: str):
|
|
|
252
92
|
|
|
253
93
|
|
|
254
94
|
def tag(value):
|
|
255
|
-
"""Tag a value
|
|
95
|
+
"""Tag a parameter value for tracking in experiments.
|
|
96
|
+
|
|
97
|
+
Tagged values appear in experiment logs and can be used for filtering
|
|
98
|
+
and organizing results. Tags are included in the task's ``__tags__``
|
|
99
|
+
dictionary.
|
|
100
|
+
|
|
101
|
+
Example::
|
|
102
|
+
|
|
103
|
+
task = MyTask.C(
|
|
104
|
+
learning_rate=tag(0.001), # Will appear in task tags
|
|
105
|
+
batch_size=32,
|
|
106
|
+
).submit()
|
|
107
|
+
|
|
108
|
+
:param value: The value to tag (str, int, float, or bool)
|
|
109
|
+
:return: A tagged value wrapper that preserves the original value
|
|
110
|
+
"""
|
|
256
111
|
return objects.TaggedValue(value)
|
|
257
112
|
|
|
258
113
|
|
|
@@ -267,7 +122,19 @@ class TagDict(SortedDict):
|
|
|
267
122
|
|
|
268
123
|
|
|
269
124
|
def tags(value) -> TagDict:
|
|
270
|
-
"""Return the tags associated with a
|
|
125
|
+
"""Return the tags associated with a configuration.
|
|
126
|
+
|
|
127
|
+
Returns a dictionary of all tagged parameter values from this configuration
|
|
128
|
+
and its nested configurations.
|
|
129
|
+
|
|
130
|
+
Example::
|
|
131
|
+
|
|
132
|
+
config = MyTask.C(learning_rate=tag(0.001), epochs=tag(100))
|
|
133
|
+
task_tags = tags(config) # {"learning_rate": 0.001, "epochs": 100}
|
|
134
|
+
|
|
135
|
+
:param value: A configuration object
|
|
136
|
+
:return: A TagDict with tag names as keys and tagged values as values
|
|
137
|
+
"""
|
|
271
138
|
return TagDict(value.__xpm__.tags())
|
|
272
139
|
|
|
273
140
|
|
|
@@ -277,8 +144,20 @@ def _normalizepathcomponent(v: Any):
|
|
|
277
144
|
return v
|
|
278
145
|
|
|
279
146
|
|
|
280
|
-
def tagspath(value: Config):
|
|
281
|
-
"""
|
|
147
|
+
def tagspath(value: Config) -> str:
|
|
148
|
+
"""Generate a unique path string from a configuration's tags.
|
|
149
|
+
|
|
150
|
+
Useful for creating tag-based directory structures. Tags are sorted
|
|
151
|
+
alphabetically and joined with underscores.
|
|
152
|
+
|
|
153
|
+
Example::
|
|
154
|
+
|
|
155
|
+
config = MyTask.C(learning_rate=tag(0.001), epochs=tag(100))
|
|
156
|
+
path = tagspath(config) # "epochs=100_learning_rate=0.001"
|
|
157
|
+
|
|
158
|
+
:param value: A configuration object
|
|
159
|
+
:return: A string with sorted tags in ``key=value`` format, joined by ``_``
|
|
160
|
+
"""
|
|
282
161
|
return "_".join(
|
|
283
162
|
f"""{_normalizepathcomponent(key)}={_normalizepathcomponent(value)}"""
|
|
284
163
|
for key, value in tags(value).items()
|
|
@@ -288,33 +167,101 @@ def tagspath(value: Config):
|
|
|
288
167
|
# --- Deprecated
|
|
289
168
|
|
|
290
169
|
|
|
291
|
-
def deprecate(
|
|
292
|
-
|
|
293
|
-
|
|
170
|
+
def deprecate(
|
|
171
|
+
config_or_target: Union[TypingType[Config], Callable, None] = None,
|
|
172
|
+
*,
|
|
173
|
+
replace: bool = False,
|
|
174
|
+
):
|
|
175
|
+
"""Deprecate a configuration/task class or a parameter.
|
|
176
|
+
|
|
177
|
+
Deprecated configurations maintain backwards compatibility while allowing
|
|
178
|
+
migration to new structures. The identifier is computed from the converted
|
|
179
|
+
configuration, ensuring consistency.
|
|
180
|
+
|
|
181
|
+
**Usage patterns:**
|
|
294
182
|
|
|
295
|
-
|
|
183
|
+
1. Simple deprecation (class inherits from new class)::
|
|
296
184
|
|
|
297
185
|
@deprecate
|
|
298
186
|
class OldConfig(NewConfig):
|
|
299
187
|
pass
|
|
300
188
|
|
|
301
|
-
|
|
302
|
-
|
|
189
|
+
2. Deprecation with conversion::
|
|
190
|
+
|
|
191
|
+
@deprecate(NewConfig)
|
|
192
|
+
class OldConfig(Config):
|
|
193
|
+
value: Param[int]
|
|
194
|
+
|
|
195
|
+
def __convert__(self):
|
|
196
|
+
return NewConfig.C(values=[self.value])
|
|
197
|
+
|
|
198
|
+
3. Immediate replacement::
|
|
199
|
+
|
|
200
|
+
@deprecate(NewConfig, replace=True)
|
|
201
|
+
class OldConfig(Config):
|
|
202
|
+
value: Param[int]
|
|
203
|
+
|
|
204
|
+
def __convert__(self):
|
|
205
|
+
return NewConfig.C(values=[self.value])
|
|
206
|
+
|
|
207
|
+
4. Deprecate a parameter::
|
|
208
|
+
|
|
209
|
+
class MyConfig(Config):
|
|
210
|
+
new_param: Param[list[int]]
|
|
211
|
+
|
|
303
212
|
@deprecate
|
|
304
|
-
def
|
|
305
|
-
|
|
306
|
-
pass
|
|
307
|
-
"""
|
|
308
|
-
if inspect.isclass(config):
|
|
309
|
-
config.__getxpmtype__().deprecate()
|
|
310
|
-
return config
|
|
213
|
+
def old_param(self, value: int):
|
|
214
|
+
self.new_param = [value]
|
|
311
215
|
|
|
312
|
-
|
|
216
|
+
:param config_or_target: Target class for conversion, or the deprecated
|
|
217
|
+
class/method when used as a simple decorator
|
|
218
|
+
:param replace: If True, creating the deprecated class immediately returns
|
|
219
|
+
the converted instance
|
|
220
|
+
"""
|
|
221
|
+
# Case 1: @deprecate on a function (deprecated attribute)
|
|
222
|
+
if inspect.isfunction(config_or_target):
|
|
313
223
|
from experimaestro.core.types import DeprecatedAttribute
|
|
314
224
|
|
|
315
|
-
return DeprecatedAttribute(
|
|
225
|
+
return DeprecatedAttribute(config_or_target)
|
|
226
|
+
|
|
227
|
+
# Case 2: @deprecate (no parens) on a class - legacy pattern
|
|
228
|
+
# The class inherits from its target (NewConfig), not directly from Config
|
|
229
|
+
if config_or_target is not None and inspect.isclass(config_or_target):
|
|
230
|
+
# Check if this looks like a deprecated class (legacy pattern)
|
|
231
|
+
# Legacy pattern: @deprecate class OldConfig(NewConfig) where NewConfig is a Config subclass
|
|
232
|
+
# The deprecated class inherits from exactly one Config subclass (the target)
|
|
233
|
+
# We exclude Config and Task as base classes since those indicate the new pattern
|
|
234
|
+
base_classes_for_new_pattern = (Config, Task)
|
|
235
|
+
if (
|
|
236
|
+
not replace
|
|
237
|
+
and len(config_or_target.__bases__) == 1
|
|
238
|
+
and config_or_target.__bases__[0] not in base_classes_for_new_pattern
|
|
239
|
+
and issubclass(config_or_target.__bases__[0], Config)
|
|
240
|
+
):
|
|
241
|
+
# This is the legacy pattern: @deprecate on a class
|
|
242
|
+
deprecated_class = config_or_target
|
|
243
|
+
deprecated_class.__getxpmtype__().deprecate()
|
|
244
|
+
return deprecated_class
|
|
245
|
+
|
|
246
|
+
# Otherwise, this is the new pattern: @deprecate(TargetConfig)
|
|
247
|
+
target = config_or_target
|
|
316
248
|
|
|
317
|
-
|
|
249
|
+
def decorator(deprecated_class: TypingType[Config]):
|
|
250
|
+
deprecated_class.__getxpmtype__().deprecate(target=target, replace=replace)
|
|
251
|
+
return deprecated_class
|
|
252
|
+
|
|
253
|
+
return decorator
|
|
254
|
+
|
|
255
|
+
# Case 3: @deprecate() with parentheses but no arguments (legacy, uses parent class)
|
|
256
|
+
if config_or_target is None:
|
|
257
|
+
|
|
258
|
+
def decorator(deprecated_class: TypingType[Config]):
|
|
259
|
+
deprecated_class.__getxpmtype__().deprecate()
|
|
260
|
+
return deprecated_class
|
|
261
|
+
|
|
262
|
+
return decorator
|
|
263
|
+
|
|
264
|
+
raise NotImplementedError("Cannot deprecate %s" % config_or_target)
|
|
318
265
|
|
|
319
266
|
|
|
320
267
|
def deprecateClass(klass):
|
|
@@ -337,7 +284,21 @@ def deprecateClass(klass):
|
|
|
337
284
|
|
|
338
285
|
|
|
339
286
|
def initializer(method):
|
|
340
|
-
"""
|
|
287
|
+
"""Decorator for methods that should only execute once.
|
|
288
|
+
|
|
289
|
+
After the first call, subsequent calls return the cached result.
|
|
290
|
+
This is useful for lazy initialization of expensive resources.
|
|
291
|
+
|
|
292
|
+
Example::
|
|
293
|
+
|
|
294
|
+
class MyConfig(Config):
|
|
295
|
+
@initializer
|
|
296
|
+
def model(self):
|
|
297
|
+
return load_expensive_model()
|
|
298
|
+
|
|
299
|
+
:param method: The method to wrap
|
|
300
|
+
:return: A wrapper that caches the result after first execution
|
|
301
|
+
"""
|
|
341
302
|
|
|
342
303
|
def wrapper(self, *args, **kwargs):
|
|
343
304
|
value = method(self, *args, **kwargs)
|