hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a199__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.
- hpcflow/__pyinstaller/hook-hpcflow.py +9 -6
- hpcflow/_version.py +1 -1
- hpcflow/app.py +1 -0
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/environments.yaml +1 -1
- hpcflow/sdk/__init__.py +26 -15
- hpcflow/sdk/app.py +2192 -768
- hpcflow/sdk/cli.py +506 -296
- hpcflow/sdk/cli_common.py +105 -7
- hpcflow/sdk/config/__init__.py +1 -1
- hpcflow/sdk/config/callbacks.py +115 -43
- hpcflow/sdk/config/cli.py +126 -103
- hpcflow/sdk/config/config.py +674 -318
- hpcflow/sdk/config/config_file.py +131 -95
- hpcflow/sdk/config/errors.py +125 -84
- hpcflow/sdk/config/types.py +148 -0
- hpcflow/sdk/core/__init__.py +25 -1
- hpcflow/sdk/core/actions.py +1771 -1059
- hpcflow/sdk/core/app_aware.py +24 -0
- hpcflow/sdk/core/cache.py +139 -79
- hpcflow/sdk/core/command_files.py +263 -287
- hpcflow/sdk/core/commands.py +145 -112
- hpcflow/sdk/core/element.py +828 -535
- hpcflow/sdk/core/enums.py +192 -0
- hpcflow/sdk/core/environment.py +74 -93
- hpcflow/sdk/core/errors.py +455 -52
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/json_like.py +540 -272
- hpcflow/sdk/core/loop.py +751 -347
- hpcflow/sdk/core/loop_cache.py +164 -47
- hpcflow/sdk/core/object_list.py +370 -207
- hpcflow/sdk/core/parameters.py +1100 -627
- hpcflow/sdk/core/rule.py +59 -41
- hpcflow/sdk/core/run_dir_files.py +21 -37
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +1649 -1339
- hpcflow/sdk/core/task_schema.py +308 -196
- hpcflow/sdk/core/test_utils.py +191 -114
- hpcflow/sdk/core/types.py +440 -0
- hpcflow/sdk/core/utils.py +485 -309
- hpcflow/sdk/core/validation.py +82 -9
- hpcflow/sdk/core/workflow.py +2544 -1178
- hpcflow/sdk/core/zarr_io.py +98 -137
- hpcflow/sdk/data/workflow_spec_schema.yaml +2 -0
- hpcflow/sdk/demo/cli.py +53 -33
- hpcflow/sdk/helper/cli.py +18 -15
- hpcflow/sdk/helper/helper.py +75 -63
- hpcflow/sdk/helper/watcher.py +61 -28
- hpcflow/sdk/log.py +122 -71
- hpcflow/sdk/persistence/__init__.py +8 -31
- hpcflow/sdk/persistence/base.py +1360 -606
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +38 -0
- hpcflow/sdk/persistence/json.py +568 -188
- hpcflow/sdk/persistence/pending.py +382 -179
- hpcflow/sdk/persistence/store_resource.py +39 -23
- hpcflow/sdk/persistence/types.py +318 -0
- hpcflow/sdk/persistence/utils.py +14 -11
- hpcflow/sdk/persistence/zarr.py +1337 -433
- hpcflow/sdk/runtime.py +44 -41
- hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
- hpcflow/sdk/submission/jobscript.py +1651 -692
- hpcflow/sdk/submission/schedulers/__init__.py +167 -39
- hpcflow/sdk/submission/schedulers/direct.py +121 -81
- hpcflow/sdk/submission/schedulers/sge.py +170 -129
- hpcflow/sdk/submission/schedulers/slurm.py +291 -268
- hpcflow/sdk/submission/schedulers/utils.py +12 -2
- hpcflow/sdk/submission/shells/__init__.py +14 -15
- hpcflow/sdk/submission/shells/base.py +150 -29
- hpcflow/sdk/submission/shells/bash.py +283 -173
- hpcflow/sdk/submission/shells/os_version.py +31 -30
- hpcflow/sdk/submission/shells/powershell.py +228 -170
- hpcflow/sdk/submission/submission.py +1014 -335
- hpcflow/sdk/submission/types.py +140 -0
- hpcflow/sdk/typing.py +182 -12
- hpcflow/sdk/utils/arrays.py +71 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +12 -0
- hpcflow/sdk/utils/strings.py +33 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +27 -6
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +34 -35
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +866 -85
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +12 -4
- hpcflow/tests/unit/test_action.py +262 -75
- hpcflow/tests/unit/test_action_rule.py +9 -4
- hpcflow/tests/unit/test_app.py +33 -6
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +134 -1
- hpcflow/tests/unit/test_command.py +71 -54
- hpcflow/tests/unit/test_config.py +142 -16
- hpcflow/tests/unit/test_config_file.py +21 -18
- hpcflow/tests/unit/test_element.py +58 -62
- hpcflow/tests/unit/test_element_iteration.py +50 -1
- hpcflow/tests/unit/test_element_set.py +29 -19
- hpcflow/tests/unit/test_group.py +4 -2
- hpcflow/tests/unit/test_input_source.py +116 -93
- hpcflow/tests/unit/test_input_value.py +29 -24
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_json_like.py +44 -35
- hpcflow/tests/unit/test_loop.py +1396 -84
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +229 -0
- hpcflow/tests/unit/test_object_list.py +17 -12
- hpcflow/tests/unit/test_parameter.py +29 -7
- hpcflow/tests/unit/test_persistence.py +237 -42
- hpcflow/tests/unit/test_resources.py +20 -18
- hpcflow/tests/unit/test_run.py +117 -6
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_runtime.py +2 -1
- hpcflow/tests/unit/test_schema_input.py +23 -15
- hpcflow/tests/unit/test_shell.py +23 -2
- hpcflow/tests/unit/test_slurm.py +8 -7
- hpcflow/tests/unit/test_submission.py +38 -89
- hpcflow/tests/unit/test_task.py +352 -247
- hpcflow/tests/unit/test_task_schema.py +33 -20
- hpcflow/tests/unit/test_utils.py +9 -11
- hpcflow/tests/unit/test_value_sequence.py +15 -12
- hpcflow/tests/unit/test_workflow.py +114 -83
- hpcflow/tests/unit/test_workflow_template.py +0 -1
- hpcflow/tests/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +334 -1
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +160 -15
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6587 -3
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/METADATA +8 -4
- hpcflow_new2-0.2.0a199.dist-info/RECORD +221 -0
- hpcflow/sdk/core/parallel.py +0 -21
- hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a199.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/config/config.py
CHANGED
@@ -14,42 +14,45 @@ import os
|
|
14
14
|
import socket
|
15
15
|
import uuid
|
16
16
|
from dataclasses import dataclass, field
|
17
|
-
from hashlib import new
|
18
17
|
from pathlib import Path
|
19
|
-
from typing import
|
20
|
-
import fsspec
|
18
|
+
from typing import cast, overload, TYPE_CHECKING
|
19
|
+
import fsspec # type: ignore
|
21
20
|
|
22
21
|
from rich.console import Console, Group
|
23
22
|
from rich.table import Table
|
24
23
|
from rich.pretty import Pretty
|
25
24
|
from rich.panel import Panel
|
26
25
|
from rich import print as rich_print
|
27
|
-
from fsspec.registry import known_implementations as fsspec_protocols
|
28
|
-
from fsspec.implementations.local import LocalFileSystem
|
29
|
-
from platformdirs import user_data_dir
|
30
|
-
from valida.schema import Schema
|
26
|
+
from fsspec.registry import known_implementations as fsspec_protocols # type: ignore
|
27
|
+
from fsspec.implementations.local import LocalFileSystem # type: ignore
|
31
28
|
from hpcflow.sdk.core.utils import get_in_container, read_YAML_file, set_in_container
|
32
29
|
|
33
|
-
from hpcflow.sdk.core.validation import get_schema
|
30
|
+
from hpcflow.sdk.core.validation import get_schema, Schema
|
34
31
|
from hpcflow.sdk.submission.shells import DEFAULT_SHELL_NAMES
|
35
32
|
from hpcflow.sdk.typing import PathLike
|
36
33
|
|
37
|
-
from .callbacks import (
|
34
|
+
from hpcflow.sdk.config.callbacks import (
|
38
35
|
callback_bool,
|
39
36
|
callback_lowercase,
|
40
37
|
callback_scheduler_set_up,
|
41
38
|
callback_supported_schedulers,
|
42
39
|
callback_supported_shells,
|
43
40
|
callback_update_log_console_level,
|
41
|
+
callback_unset_log_console_level,
|
44
42
|
callback_vars,
|
45
43
|
callback_file_paths,
|
46
44
|
exists_in_schedulers,
|
47
45
|
set_callback_file_paths,
|
48
46
|
check_load_data_files,
|
49
47
|
set_scheduler_invocation_match,
|
48
|
+
callback_update_log_file_path,
|
49
|
+
callback_update_log_file_level,
|
50
|
+
callback_unset_log_file_level,
|
51
|
+
callback_unset_log_file_path,
|
52
|
+
callback_log_file_path,
|
50
53
|
)
|
51
|
-
from .config_file import ConfigFile
|
52
|
-
from .errors import (
|
54
|
+
from hpcflow.sdk.config.config_file import ConfigFile
|
55
|
+
from hpcflow.sdk.config.errors import (
|
53
56
|
ConfigChangeInvalidJSONError,
|
54
57
|
ConfigChangePopIndexError,
|
55
58
|
ConfigChangeTypeInvalidError,
|
@@ -57,16 +60,35 @@ from .errors import (
|
|
57
60
|
ConfigItemAlreadyUnsetError,
|
58
61
|
ConfigItemCallbackError,
|
59
62
|
ConfigNonConfigurableError,
|
63
|
+
ConfigReadOnlyError,
|
60
64
|
ConfigUnknownItemError,
|
61
65
|
ConfigUnknownOverrideError,
|
62
66
|
ConfigValidationError,
|
63
67
|
)
|
64
68
|
|
69
|
+
if TYPE_CHECKING:
|
70
|
+
from collections.abc import Callable, Iterator, Mapping, Sequence
|
71
|
+
from typing import Any, Literal
|
72
|
+
from .types import (
|
73
|
+
ConfigDescriptor,
|
74
|
+
ConfigMetadata,
|
75
|
+
DefaultConfiguration,
|
76
|
+
SchedulerConfigDescriptor,
|
77
|
+
ShellConfigDescriptor,
|
78
|
+
GetterCallback,
|
79
|
+
SetterCallback,
|
80
|
+
UnsetterCallback,
|
81
|
+
T,
|
82
|
+
)
|
83
|
+
from ..app import BaseApp
|
84
|
+
from ..core.types import AbstractFileSystem
|
85
|
+
|
86
|
+
|
65
87
|
logger = logging.getLogger(__name__)
|
66
88
|
|
67
89
|
_DEFAULT_SHELL = DEFAULT_SHELL_NAMES[os.name]
|
68
90
|
#: The default configuration descriptor.
|
69
|
-
DEFAULT_CONFIG = {
|
91
|
+
DEFAULT_CONFIG: DefaultConfiguration = {
|
70
92
|
"invocation": {"environment_setup": None, "match": {}},
|
71
93
|
"config": {
|
72
94
|
"machine": socket.gethostname(),
|
@@ -88,29 +110,29 @@ class ConfigOptions:
|
|
88
110
|
"""Application-level options for configuration"""
|
89
111
|
|
90
112
|
#: The default directory.
|
91
|
-
default_directory:
|
113
|
+
default_directory: Path | str
|
92
114
|
#: The environment variable containing the directory name.
|
93
115
|
directory_env_var: str
|
94
116
|
#: The default configuration.
|
95
|
-
default_config:
|
117
|
+
default_config: DefaultConfiguration = field(
|
96
118
|
default_factory=lambda: deepcopy(DEFAULT_CONFIG)
|
97
119
|
)
|
98
120
|
#: Any extra schemas to apply.
|
99
|
-
extra_schemas:
|
121
|
+
extra_schemas: Sequence[Schema] = field(default_factory=list)
|
100
122
|
#: Default directory of known configurations.
|
101
|
-
default_known_configs_dir:
|
123
|
+
default_known_configs_dir: str | None = None
|
124
|
+
_schemas: Sequence[Schema] = field(init=False)
|
125
|
+
_configurable_keys: Sequence[str] = field(init=False)
|
102
126
|
|
103
|
-
def __post_init__(self):
|
104
|
-
|
105
|
-
self._schemas = cfg_schemas
|
106
|
-
self._configurable_keys = cfg_keys
|
127
|
+
def __post_init__(self) -> None:
|
128
|
+
self._schemas, self._configurable_keys = self.init_schemas()
|
107
129
|
|
108
|
-
def init_schemas(self):
|
130
|
+
def init_schemas(self) -> tuple[Sequence[Schema], Sequence[str]]:
|
109
131
|
"""
|
110
132
|
Get allowed configurable keys from config schemas.
|
111
133
|
"""
|
112
|
-
cfg_schemas = [get_schema("config_schema.yaml")
|
113
|
-
cfg_keys = []
|
134
|
+
cfg_schemas = [get_schema("config_schema.yaml"), *self.extra_schemas]
|
135
|
+
cfg_keys: list[str] = []
|
114
136
|
for cfg_schema in cfg_schemas:
|
115
137
|
for rule in cfg_schema.rules:
|
116
138
|
if not rule.path and rule.condition.callable.name == "allowed_keys":
|
@@ -118,7 +140,13 @@ class ConfigOptions:
|
|
118
140
|
|
119
141
|
return (cfg_schemas, cfg_keys)
|
120
142
|
|
121
|
-
def validate(
|
143
|
+
def validate(
|
144
|
+
self,
|
145
|
+
data: T,
|
146
|
+
logger: logging.Logger,
|
147
|
+
metadata: ConfigMetadata | None = None,
|
148
|
+
raise_with_metadata: bool = True,
|
149
|
+
) -> T:
|
122
150
|
"""Validate configuration items of the loaded invocation."""
|
123
151
|
|
124
152
|
logger.debug("Validating configuration...")
|
@@ -140,7 +168,8 @@ class ConfigOptions:
|
|
140
168
|
|
141
169
|
|
142
170
|
class Config:
|
143
|
-
"""
|
171
|
+
"""
|
172
|
+
Application configuration as defined in one or more config files.
|
144
173
|
|
145
174
|
This class supports indexing into the collection of properties via Python dot notation.
|
146
175
|
|
@@ -178,76 +207,25 @@ class Config:
|
|
178
207
|
|
179
208
|
Attributes
|
180
209
|
----------
|
181
|
-
|
182
|
-
The directory containing the configuration file.
|
183
|
-
config_file_name:
|
184
|
-
The name of the configuration file.
|
185
|
-
config_file_path:
|
186
|
-
The full path to the configuration file.
|
187
|
-
config_file_contents:
|
188
|
-
The cached contents of the configuration file.
|
189
|
-
config_key:
|
190
|
-
The primary key to select the configuration within the configuration file.
|
191
|
-
config_schemas:
|
192
|
-
The schemas that apply to the configuration file.
|
193
|
-
host_user_id:
|
194
|
-
User ID as understood by the script.
|
195
|
-
host_user_id_file_path:
|
196
|
-
Where user ID information is stored.
|
197
|
-
invoking_user_id:
|
198
|
-
User ID that created the workflow.
|
199
|
-
machine:
|
200
|
-
Machine to submit to.
|
201
|
-
Mapped to a field in the configuration file.
|
202
|
-
user_name:
|
210
|
+
user_name: str
|
203
211
|
User to submit as.
|
204
212
|
Mapped to a field in the configuration file.
|
205
|
-
user_orcid:
|
213
|
+
user_orcid: str
|
206
214
|
User's ORCID.
|
207
215
|
Mapped to a field in the configuration file.
|
208
|
-
user_affiliation:
|
216
|
+
user_affiliation: str
|
209
217
|
User's institutional affiliation.
|
210
218
|
Mapped to a field in the configuration file.
|
211
|
-
linux_release_file:
|
219
|
+
linux_release_file: str
|
212
220
|
Where to get the description of the Linux release version data.
|
213
221
|
Mapped to a field in the configuration file.
|
214
|
-
|
215
|
-
Where to log to.
|
216
|
-
Mapped to a field in the configuration file.
|
217
|
-
log_file_level:
|
222
|
+
log_file_level: str
|
218
223
|
At what level to do logging to the file.
|
219
224
|
Mapped to a field in the configuration file.
|
220
|
-
log_console_level:
|
225
|
+
log_console_level: str
|
221
226
|
At what level to do logging to the console. Usually coarser than to a file.
|
222
227
|
Mapped to a field in the configuration file.
|
223
|
-
|
224
|
-
Where to get task schemas.
|
225
|
-
Mapped to a field in the configuration file.
|
226
|
-
parameter_sources:
|
227
|
-
Where to get parameter descriptors.
|
228
|
-
Mapped to a field in the configuration file.
|
229
|
-
command_file_sources:
|
230
|
-
Where to get command files.
|
231
|
-
Mapped to a field in the configuration file.
|
232
|
-
environment_sources:
|
233
|
-
Where to get execution environment descriptors.
|
234
|
-
Mapped to a field in the configuration file.
|
235
|
-
default_scheduler:
|
236
|
-
The name of the default scheduler.
|
237
|
-
Mapped to a field in the configuration file.
|
238
|
-
default_shell:
|
239
|
-
The name of the default shell.
|
240
|
-
Mapped to a field in the configuration file.
|
241
|
-
schedulers:
|
242
|
-
Settings for supported scheduler(s).
|
243
|
-
Mapped to a field in the configuration file.
|
244
|
-
shells:
|
245
|
-
Settings for supported shell(s).
|
246
|
-
Mapped to a field in the configuration file.
|
247
|
-
demo_data_dir:
|
248
|
-
Location of demo data.
|
249
|
-
Mapped to a field in the configuration file.
|
250
|
-
demo_data_manifest_file:
|
228
|
+
demo_data_manifest_file: str
|
251
229
|
Where the manifest describing the demo data is.
|
252
230
|
Mapped to a field in the configuration file.
|
253
231
|
"""
|
@@ -258,10 +236,10 @@ class Config:
|
|
258
236
|
config_file: ConfigFile,
|
259
237
|
options: ConfigOptions,
|
260
238
|
logger: logging.Logger,
|
261
|
-
config_key:
|
262
|
-
uid=None,
|
263
|
-
callbacks=None,
|
264
|
-
variables=None,
|
239
|
+
config_key: str | None,
|
240
|
+
uid: str | None = None,
|
241
|
+
callbacks: dict[str, tuple[GetterCallback, ...]] | None = None,
|
242
|
+
variables: dict[str, str] | None = None,
|
265
243
|
**overrides,
|
266
244
|
):
|
267
245
|
self._app = app
|
@@ -281,12 +259,12 @@ class Config:
|
|
281
259
|
)
|
282
260
|
|
283
261
|
# Callbacks are run on get:
|
284
|
-
self._get_callbacks = {
|
262
|
+
self._get_callbacks: dict[str, tuple[GetterCallback, ...]] = {
|
285
263
|
"task_schema_sources": (callback_file_paths,),
|
286
264
|
"environment_sources": (callback_file_paths,),
|
287
265
|
"parameter_sources": (callback_file_paths,),
|
288
266
|
"command_file_sources": (callback_file_paths,),
|
289
|
-
"log_file_path": (callback_vars,
|
267
|
+
"log_file_path": (callback_vars, callback_log_file_path),
|
290
268
|
"telemetry": (callback_bool,),
|
291
269
|
"schedulers": (callback_lowercase, callback_supported_schedulers),
|
292
270
|
"shells": (callback_lowercase,),
|
@@ -297,7 +275,7 @@ class Config:
|
|
297
275
|
}
|
298
276
|
|
299
277
|
# Set callbacks are run on set:
|
300
|
-
self._set_callbacks = {
|
278
|
+
self._set_callbacks: dict[str, tuple[SetterCallback, ...]] = {
|
301
279
|
"task_schema_sources": (set_callback_file_paths, check_load_data_files),
|
302
280
|
"environment_sources": (set_callback_file_paths, check_load_data_files),
|
303
281
|
"parameter_sources": (set_callback_file_paths, check_load_data_files),
|
@@ -305,22 +283,28 @@ class Config:
|
|
305
283
|
"default_scheduler": (exists_in_schedulers, set_scheduler_invocation_match),
|
306
284
|
"default_shell": (callback_supported_shells,),
|
307
285
|
"schedulers": (callback_supported_schedulers, callback_scheduler_set_up),
|
308
|
-
"log_file_path": (
|
286
|
+
"log_file_path": (callback_update_log_file_path,),
|
287
|
+
"log_file_level": (callback_update_log_file_level,),
|
309
288
|
"log_console_level": (callback_update_log_console_level,),
|
310
289
|
"demo_data_manifest_file": (set_callback_file_paths,),
|
311
290
|
}
|
312
291
|
|
292
|
+
self._unset_callbacks: dict[str, tuple[UnsetterCallback, ...]] = {
|
293
|
+
"log_console_level": (callback_unset_log_console_level,),
|
294
|
+
"log_file_level": (callback_unset_log_file_level,),
|
295
|
+
"log_file_path": (callback_unset_log_file_path,),
|
296
|
+
}
|
297
|
+
|
313
298
|
self._configurable_keys = self._options._configurable_keys
|
314
|
-
self._modified_keys = {}
|
315
|
-
self._unset_keys =
|
299
|
+
self._modified_keys: ConfigDescriptor = {}
|
300
|
+
self._unset_keys: set[str] = set()
|
316
301
|
|
317
|
-
for name in overrides:
|
318
|
-
|
319
|
-
raise ConfigUnknownOverrideError(name=name)
|
302
|
+
if any((unknown := name) not in self._configurable_keys for name in overrides):
|
303
|
+
raise ConfigUnknownOverrideError(name=unknown)
|
320
304
|
|
321
305
|
host_uid, host_uid_file_path = self._get_user_id()
|
322
306
|
|
323
|
-
metadata = {
|
307
|
+
metadata: ConfigMetadata = {
|
324
308
|
"config_directory": self._file.directory,
|
325
309
|
"config_file_name": self._file.path.name,
|
326
310
|
"config_file_path": self._file.path,
|
@@ -333,22 +317,222 @@ class Config:
|
|
333
317
|
}
|
334
318
|
self._meta_data = metadata
|
335
319
|
|
320
|
+
# used within context manager `cached_config`:
|
321
|
+
self._use_cache = False
|
322
|
+
self._config_cache: dict[tuple[str, bool, bool, bool], Any] = {}
|
323
|
+
|
324
|
+
# note: this must go at the end, after all instance attributes have been set!
|
336
325
|
self._options.validate(
|
337
326
|
data=self.get_all(include_overrides=True),
|
338
327
|
logger=self._logger,
|
339
328
|
metadata=metadata,
|
340
329
|
)
|
341
330
|
|
342
|
-
def __dir__(self):
|
343
|
-
|
331
|
+
def __dir__(self) -> Iterator[str]:
|
332
|
+
yield from super().__dir__()
|
333
|
+
yield from self._all_keys
|
344
334
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
335
|
+
@property
|
336
|
+
def config_directory(self) -> Path:
|
337
|
+
"""
|
338
|
+
The directory containing the configuration file.
|
339
|
+
"""
|
340
|
+
return self._get("config_directory")
|
341
|
+
|
342
|
+
@property
|
343
|
+
def config_file_name(self) -> str:
|
344
|
+
"""
|
345
|
+
The name of the configuration file.
|
346
|
+
"""
|
347
|
+
return self._get("config_file_name")
|
348
|
+
|
349
|
+
@property
|
350
|
+
def config_file_path(self) -> Path:
|
351
|
+
"""
|
352
|
+
The full path to the configuration file.
|
353
|
+
"""
|
354
|
+
return self._get("config_file_path")
|
355
|
+
|
356
|
+
@property
|
357
|
+
def config_file_contents(self) -> str:
|
358
|
+
"""
|
359
|
+
The cached contents of the configuration file.
|
360
|
+
"""
|
361
|
+
return self._get("config_file_contents")
|
362
|
+
|
363
|
+
@property
|
364
|
+
def config_key(self) -> str:
|
365
|
+
"""
|
366
|
+
The primary key to select the configuration within the configuration file.
|
367
|
+
"""
|
368
|
+
return self._get("config_key")
|
369
|
+
|
370
|
+
@property
|
371
|
+
def config_schemas(self) -> Sequence[Schema]:
|
372
|
+
"""
|
373
|
+
The schemas that apply to the configuration file.
|
374
|
+
"""
|
375
|
+
return self._get("config_schemas")
|
376
|
+
|
377
|
+
@property
|
378
|
+
def invoking_user_id(self) -> str:
|
379
|
+
"""
|
380
|
+
User ID that created the workflow.
|
381
|
+
"""
|
382
|
+
return self._get("invoking_user_id")
|
383
|
+
|
384
|
+
@property
|
385
|
+
def host_user_id(self) -> str:
|
386
|
+
"""
|
387
|
+
User ID as understood by the script.
|
388
|
+
"""
|
389
|
+
return self._get("host_user_id")
|
390
|
+
|
391
|
+
@property
|
392
|
+
def host_user_id_file_path(self) -> Path:
|
393
|
+
"""
|
394
|
+
Where user ID information is stored.
|
395
|
+
"""
|
396
|
+
return self._get("host_user_id_file_path")
|
397
|
+
|
398
|
+
@property
|
399
|
+
def machine(self) -> str:
|
400
|
+
"""
|
401
|
+
Machine to submit to.
|
402
|
+
Mapped to a field in the configuration file.
|
403
|
+
"""
|
404
|
+
return self._get("machine")
|
405
|
+
|
406
|
+
@machine.setter
|
407
|
+
def machine(self, value: str):
|
408
|
+
self._set("machine", value)
|
409
|
+
|
410
|
+
@property
|
411
|
+
def log_file_path(self) -> str:
|
412
|
+
"""
|
413
|
+
Where to log to.
|
414
|
+
Mapped to a field in the configuration file.
|
415
|
+
"""
|
416
|
+
return self._get("log_file_path")
|
417
|
+
|
418
|
+
@log_file_path.setter
|
419
|
+
def log_file_path(self, value: str):
|
420
|
+
self._set("log_file_path", value)
|
421
|
+
|
422
|
+
@property
|
423
|
+
def environment_sources(self) -> Sequence[Path]:
|
424
|
+
"""
|
425
|
+
Where to get execution environment descriptors.
|
426
|
+
Mapped to a field in the configuration file.
|
427
|
+
"""
|
428
|
+
return self._get("environment_sources")
|
429
|
+
|
430
|
+
@environment_sources.setter
|
431
|
+
def environment_sources(self, value: Sequence[Path]):
|
432
|
+
self._set("environment_sources", value)
|
433
|
+
|
434
|
+
@property
|
435
|
+
def task_schema_sources(self) -> Sequence[str]:
|
436
|
+
"""
|
437
|
+
Where to get task schemas.
|
438
|
+
Mapped to a field in the configuration file.
|
439
|
+
"""
|
440
|
+
return self._get("task_schema_sources")
|
441
|
+
|
442
|
+
@task_schema_sources.setter
|
443
|
+
def task_schema_sources(self, value: Sequence[str]):
|
444
|
+
self._set("task_schema_sources", value)
|
445
|
+
|
446
|
+
@property
|
447
|
+
def command_file_sources(self) -> Sequence[str]:
|
448
|
+
"""
|
449
|
+
Where to get command files.
|
450
|
+
Mapped to a field in the configuration file.
|
451
|
+
"""
|
452
|
+
return self._get("command_file_sources")
|
453
|
+
|
454
|
+
@command_file_sources.setter
|
455
|
+
def command_file_sources(self, value: Sequence[str]):
|
456
|
+
self._set("command_file_sources", value)
|
457
|
+
|
458
|
+
@property
|
459
|
+
def parameter_sources(self) -> Sequence[str]:
|
460
|
+
"""
|
461
|
+
Where to get parameter descriptors.
|
462
|
+
Mapped to a field in the configuration file.
|
463
|
+
"""
|
464
|
+
return self._get("parameter_sources")
|
465
|
+
|
466
|
+
@parameter_sources.setter
|
467
|
+
def parameter_sources(self, value: Sequence[str]):
|
468
|
+
self._set("parameter_sources", value)
|
469
|
+
|
470
|
+
@property
|
471
|
+
def default_scheduler(self) -> str:
|
472
|
+
"""
|
473
|
+
The name of the default scheduler.
|
474
|
+
Mapped to a field in the configuration file.
|
475
|
+
"""
|
476
|
+
return self._get("default_scheduler")
|
477
|
+
|
478
|
+
@default_scheduler.setter
|
479
|
+
def default_scheduler(self, value: str):
|
480
|
+
self._set("default_scheduler", value)
|
481
|
+
|
482
|
+
@property
|
483
|
+
def default_shell(self) -> str:
|
484
|
+
"""
|
485
|
+
The name of the default shell.
|
486
|
+
Mapped to a field in the configuration file.
|
487
|
+
"""
|
488
|
+
return self._get("default_shell")
|
489
|
+
|
490
|
+
@default_shell.setter
|
491
|
+
def default_shell(self, value: str):
|
492
|
+
self._set("default_shell", value)
|
493
|
+
|
494
|
+
@property
|
495
|
+
def schedulers(self) -> Mapping[str, SchedulerConfigDescriptor]:
|
496
|
+
"""
|
497
|
+
Settings for supported scheduler(s).
|
498
|
+
Mapped to a field in the configuration file.
|
499
|
+
"""
|
500
|
+
return self._get("schedulers")
|
501
|
+
|
502
|
+
@schedulers.setter
|
503
|
+
def schedulers(self, value: Mapping[str, SchedulerConfigDescriptor]):
|
504
|
+
self._set("schedulers", value)
|
505
|
+
|
506
|
+
@property
|
507
|
+
def shells(self) -> Mapping[str, ShellConfigDescriptor]:
|
508
|
+
"""
|
509
|
+
Settings for supported shell(s).
|
510
|
+
Mapped to a field in the configuration file.
|
511
|
+
"""
|
512
|
+
return self._get("shells")
|
513
|
+
|
514
|
+
@shells.setter
|
515
|
+
def shells(self, value: Mapping[str, ShellConfigDescriptor]):
|
516
|
+
self._set("shells", value)
|
517
|
+
|
518
|
+
@property
|
519
|
+
def demo_data_dir(self) -> str | None:
|
520
|
+
"""
|
521
|
+
Location of demo data.
|
522
|
+
Mapped to a field in the configuration file.
|
523
|
+
"""
|
524
|
+
return self._get("demo_data_dir")
|
525
|
+
|
526
|
+
@demo_data_dir.setter
|
527
|
+
def demo_data_dir(self, value: str | None):
|
528
|
+
self._set("demo_data_dir", value)
|
529
|
+
|
530
|
+
def __getattr__(self, name: str):
|
531
|
+
if name.startswith("__"):
|
349
532
|
raise AttributeError(f"Attribute not known: {name!r}.")
|
533
|
+
return self._get(name)
|
350
534
|
|
351
|
-
def __setattr__(self, name, value):
|
535
|
+
def __setattr__(self, name: str, value):
|
352
536
|
if (
|
353
537
|
"_configurable_keys" in self.__dict__
|
354
538
|
and name in self.__dict__["_configurable_keys"]
|
@@ -357,37 +541,53 @@ class Config:
|
|
357
541
|
else:
|
358
542
|
super().__setattr__(name, value)
|
359
543
|
|
360
|
-
def _disable_callbacks(
|
361
|
-
|
544
|
+
def _disable_callbacks(
|
545
|
+
self, callbacks: Sequence[str]
|
546
|
+
) -> tuple[
|
547
|
+
dict[str, tuple[GetterCallback, ...]],
|
548
|
+
dict[str, tuple[SetterCallback, ...]],
|
549
|
+
dict[str, tuple[UnsetterCallback, ...]],
|
550
|
+
]:
|
551
|
+
"""
|
552
|
+
Disable named get, set, and unset callbacks.
|
362
553
|
|
363
554
|
Returns
|
364
555
|
-------
|
365
556
|
The original get and set callback dictionaries.
|
366
557
|
"""
|
367
558
|
self._logger.info(f"disabling config callbacks: {callbacks!r}")
|
368
|
-
get_callbacks_tmp = {
|
369
|
-
k: tuple(
|
559
|
+
get_callbacks_tmp: dict[str, tuple[GetterCallback, ...]] = {
|
560
|
+
k: tuple(cb for cb in v if cb.__name__ not in callbacks)
|
370
561
|
for k, v in self._get_callbacks.items()
|
371
562
|
}
|
372
|
-
set_callbacks_tmp = {
|
373
|
-
k: tuple(
|
563
|
+
set_callbacks_tmp: dict[str, tuple[SetterCallback, ...]] = {
|
564
|
+
k: tuple(cb for cb in v if cb.__name__ not in callbacks)
|
374
565
|
for k, v in self._set_callbacks.items()
|
375
566
|
}
|
567
|
+
unset_callbacks_tmp = {
|
568
|
+
k: tuple(i for i in v if i.__name__ not in callbacks)
|
569
|
+
for k, v in self._unset_callbacks.items()
|
570
|
+
}
|
376
571
|
get_callbacks = copy.deepcopy(self._get_callbacks)
|
377
572
|
set_callbacks = copy.deepcopy(self._set_callbacks)
|
573
|
+
unset_callbacks = copy.deepcopy(self._unset_callbacks)
|
378
574
|
self._get_callbacks = get_callbacks_tmp
|
379
575
|
self._set_callbacks = set_callbacks_tmp
|
380
|
-
|
576
|
+
self._unset_callbacks = unset_callbacks_tmp
|
577
|
+
return (get_callbacks, set_callbacks, unset_callbacks)
|
381
578
|
|
382
579
|
@contextlib.contextmanager
|
383
|
-
def _without_callbacks(self, *callbacks):
|
384
|
-
"""Context manager to temporarily exclude named get and
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
580
|
+
def _without_callbacks(self, *callbacks: str) -> Iterator[None]:
|
581
|
+
"""Context manager to temporarily exclude named get, set, and unset callbacks."""
|
582
|
+
get_cb, set_cb, unset_cb = self._disable_callbacks(callbacks)
|
583
|
+
try:
|
584
|
+
yield
|
585
|
+
finally:
|
586
|
+
self._get_callbacks = get_cb
|
587
|
+
self._set_callbacks = set_cb
|
588
|
+
self._unset_callbacks = unset_cb
|
589
|
+
|
590
|
+
def _validate(self) -> None:
|
391
591
|
data = self.get_all(include_overrides=True)
|
392
592
|
self._options.validate(
|
393
593
|
data=data,
|
@@ -396,95 +596,117 @@ class Config:
|
|
396
596
|
raise_with_metadata=True,
|
397
597
|
)
|
398
598
|
|
399
|
-
def _resolve_path(self, path):
|
599
|
+
def _resolve_path(self, path: PathLike) -> PathLike:
|
400
600
|
"""Resolve a file path, but leave fsspec protocols alone."""
|
401
|
-
if
|
402
|
-
|
403
|
-
|
404
|
-
if not path.is_absolute():
|
405
|
-
path = self._meta_data["config_directory"].joinpath(path)
|
406
|
-
else:
|
601
|
+
if path is None:
|
602
|
+
return None
|
603
|
+
if any(str(path).startswith(i + ":") for i in fsspec_protocols):
|
407
604
|
self._logger.debug(
|
408
605
|
f"Not resolving path {path!r} because it looks like an `fsspec` URL."
|
409
606
|
)
|
607
|
+
return path
|
608
|
+
real_path = Path(path).expanduser()
|
609
|
+
if real_path.is_absolute():
|
610
|
+
return real_path
|
611
|
+
return self._meta_data["config_directory"].joinpath(real_path)
|
612
|
+
|
613
|
+
def register_config_get_callback(
|
614
|
+
self, name: str
|
615
|
+
) -> Callable[[GetterCallback], GetterCallback]:
|
616
|
+
"""
|
617
|
+
Decorator to register a function as a configuration callback for a specified
|
618
|
+
configuration item name, to be invoked on `get` of the item.
|
619
|
+
"""
|
410
620
|
|
411
|
-
|
412
|
-
|
413
|
-
def register_config_get_callback(self, name):
|
414
|
-
"""Decorator to register a function as a configuration callback for a specified
|
415
|
-
configuration item name, to be invoked on `get` of the item."""
|
416
|
-
|
417
|
-
def decorator(func):
|
621
|
+
def decorator(func: GetterCallback) -> GetterCallback:
|
418
622
|
if name in self._get_callbacks:
|
419
|
-
self._get_callbacks[name] =
|
420
|
-
list(self._get_callbacks[name]) + [func]
|
421
|
-
)
|
623
|
+
self._get_callbacks[name] = self._get_callbacks[name] + (func,)
|
422
624
|
else:
|
423
625
|
self._get_callbacks[name] = (func,)
|
424
626
|
|
425
627
|
@functools.wraps(func)
|
426
|
-
def wrap(value):
|
427
|
-
return func(value)
|
628
|
+
def wrap(config: Config, value: T) -> T:
|
629
|
+
return func(config, value)
|
428
630
|
|
429
631
|
return wrap
|
430
632
|
|
431
633
|
return decorator
|
432
634
|
|
433
|
-
def register_config_set_callback(
|
434
|
-
|
435
|
-
|
635
|
+
def register_config_set_callback(
|
636
|
+
self, name: str
|
637
|
+
) -> Callable[[SetterCallback], SetterCallback]:
|
638
|
+
"""
|
639
|
+
Decorator to register a function as a configuration callback for a specified
|
640
|
+
configuration item name, to be invoked on `set` of the item.
|
641
|
+
"""
|
436
642
|
|
437
|
-
def decorator(func):
|
643
|
+
def decorator(func: SetterCallback) -> SetterCallback:
|
438
644
|
if name in self._set_callbacks:
|
439
|
-
self._set_callbacks[name] =
|
440
|
-
list(self._set_callbacks[name]) + [func]
|
441
|
-
)
|
645
|
+
self._set_callbacks[name] = self._set_callbacks[name] + (func,)
|
442
646
|
else:
|
443
647
|
self._set_callbacks[name] = (func,)
|
444
648
|
|
445
649
|
@functools.wraps(func)
|
446
|
-
def wrap(value):
|
447
|
-
return func(value)
|
650
|
+
def wrap(config: Config, value: T) -> Any:
|
651
|
+
return func(config, value)
|
448
652
|
|
449
653
|
return wrap
|
450
654
|
|
451
655
|
return decorator
|
452
656
|
|
453
657
|
@property
|
454
|
-
def _all_keys(self):
|
455
|
-
return self._configurable_keys
|
456
|
-
|
457
|
-
|
658
|
+
def _all_keys(self) -> list[str]:
|
659
|
+
return [*self._configurable_keys, *self._meta_data]
|
660
|
+
|
661
|
+
@overload
|
662
|
+
def get_all(
|
663
|
+
self, *, include_overrides: bool = True, as_str: Literal[True]
|
664
|
+
) -> Mapping[str, str]:
|
665
|
+
...
|
666
|
+
|
667
|
+
@overload
|
668
|
+
def get_all(
|
669
|
+
self, *, include_overrides: bool = True, as_str: Literal[False] = False
|
670
|
+
) -> Mapping[str, Any]:
|
671
|
+
...
|
672
|
+
|
673
|
+
def get_all(
|
674
|
+
self, *, include_overrides: bool = True, as_str: bool = False
|
675
|
+
) -> Mapping[str, Any]:
|
458
676
|
"""Get all configurable items."""
|
459
|
-
items = {}
|
677
|
+
items: dict[str, Any] = {}
|
460
678
|
for key in self._configurable_keys:
|
461
679
|
if key in self._unset_keys:
|
462
680
|
continue
|
463
|
-
|
464
|
-
|
465
|
-
|
681
|
+
try:
|
682
|
+
if as_str:
|
683
|
+
items[key] = self._get(
|
466
684
|
name=key,
|
467
685
|
include_overrides=include_overrides,
|
468
686
|
raise_on_missing=True,
|
469
|
-
as_str=
|
687
|
+
as_str=True,
|
470
688
|
)
|
471
|
-
|
472
|
-
|
473
|
-
|
689
|
+
else:
|
690
|
+
items[key] = self._get(
|
691
|
+
name=key,
|
692
|
+
include_overrides=include_overrides,
|
693
|
+
raise_on_missing=True,
|
694
|
+
)
|
695
|
+
except ValueError:
|
696
|
+
continue
|
474
697
|
return items
|
475
698
|
|
476
|
-
def _show(self, config=True, metadata=False):
|
477
|
-
group_args = []
|
699
|
+
def _show(self, config: bool = True, metadata: bool = False):
|
700
|
+
group_args: list[Panel] = []
|
478
701
|
if metadata:
|
479
|
-
|
480
|
-
|
481
|
-
|
702
|
+
tab = Table(show_header=False, box=None)
|
703
|
+
tab.add_column()
|
704
|
+
tab.add_column()
|
482
705
|
for k, v in self._meta_data.items():
|
483
706
|
if k == "config_file_contents":
|
484
707
|
continue
|
485
|
-
|
486
|
-
|
487
|
-
group_args.append(panel_md)
|
708
|
+
tab.add_row(k, Pretty(v))
|
709
|
+
group_args.append(Panel(tab, title="Config metadata"))
|
488
710
|
|
489
711
|
if config:
|
490
712
|
tab = Table(show_header=False, box=None)
|
@@ -492,15 +714,13 @@ class Config:
|
|
492
714
|
tab.add_column()
|
493
715
|
for k, v in self.get_all().items():
|
494
716
|
tab.add_row(k, Pretty(v))
|
495
|
-
|
496
|
-
group_args.append(panel)
|
717
|
+
group_args.append(Panel(tab, title=f"Config {self._config_key!r}"))
|
497
718
|
|
498
|
-
|
499
|
-
rich_print(group)
|
719
|
+
rich_print(Group(*group_args))
|
500
720
|
|
501
|
-
def _get_callback_value(self, name, value):
|
721
|
+
def _get_callback_value(self, name: str, value):
|
502
722
|
if name in self._get_callbacks and value is not None:
|
503
|
-
for cb in self._get_callbacks.get(name,
|
723
|
+
for cb in self._get_callbacks.get(name, ()):
|
504
724
|
self._logger.debug(
|
505
725
|
f"Invoking `config.get` callback ({cb.__name__!r}) for item {name!r}={value!r}"
|
506
726
|
)
|
@@ -510,9 +730,36 @@ class Config:
|
|
510
730
|
raise ConfigItemCallbackError(name, cb, err) from None
|
511
731
|
return value
|
512
732
|
|
733
|
+
@overload
|
734
|
+
def _get(
|
735
|
+
self,
|
736
|
+
name: str,
|
737
|
+
*,
|
738
|
+
include_overrides=True,
|
739
|
+
raise_on_missing=False,
|
740
|
+
as_str: Literal[False] = False,
|
741
|
+
callback=True,
|
742
|
+
default_value=None,
|
743
|
+
) -> Any:
|
744
|
+
...
|
745
|
+
|
746
|
+
@overload
|
513
747
|
def _get(
|
514
748
|
self,
|
515
|
-
name,
|
749
|
+
name: str,
|
750
|
+
*,
|
751
|
+
include_overrides=True,
|
752
|
+
raise_on_missing=False,
|
753
|
+
as_str: Literal[True],
|
754
|
+
callback=True,
|
755
|
+
default_value=None,
|
756
|
+
) -> list[str] | str:
|
757
|
+
...
|
758
|
+
|
759
|
+
def _get(
|
760
|
+
self,
|
761
|
+
name: str,
|
762
|
+
*,
|
516
763
|
include_overrides=True,
|
517
764
|
raise_on_missing=False,
|
518
765
|
as_str=False,
|
@@ -521,11 +768,22 @@ class Config:
|
|
521
768
|
):
|
522
769
|
"""Get a configuration item."""
|
523
770
|
|
771
|
+
if self._use_cache:
|
772
|
+
# note: we default_value is not necessarily hashable, so we can't cache on it!
|
773
|
+
key = (
|
774
|
+
name,
|
775
|
+
include_overrides,
|
776
|
+
raise_on_missing,
|
777
|
+
as_str,
|
778
|
+
)
|
779
|
+
if key in self._config_cache:
|
780
|
+
return self._config_cache[key]
|
781
|
+
|
524
782
|
if name not in self._all_keys:
|
525
783
|
raise ConfigUnknownItemError(name=name)
|
526
784
|
|
527
785
|
elif name in self._meta_data:
|
528
|
-
val = self._meta_data[name]
|
786
|
+
val = cast("dict", self._meta_data)[name]
|
529
787
|
|
530
788
|
elif include_overrides and name in self._overrides:
|
531
789
|
val = self._overrides[name]
|
@@ -538,7 +796,7 @@ class Config:
|
|
538
796
|
val = default_value
|
539
797
|
|
540
798
|
elif name in self._modified_keys:
|
541
|
-
val = self._modified_keys[name]
|
799
|
+
val = cast("dict", self._modified_keys)[name]
|
542
800
|
|
543
801
|
elif name in self._configurable_keys:
|
544
802
|
val = self._file.get_config_item(
|
@@ -557,74 +815,121 @@ class Config:
|
|
557
815
|
else:
|
558
816
|
val = str(val)
|
559
817
|
|
818
|
+
if self._use_cache:
|
819
|
+
self._config_cache[key] = val
|
820
|
+
|
560
821
|
return val
|
561
822
|
|
562
|
-
def _parse_JSON(self, name, value):
|
823
|
+
def _parse_JSON(self, name: str, value: str) -> Any:
|
563
824
|
try:
|
564
|
-
|
825
|
+
return json.loads(value)
|
565
826
|
except json.decoder.JSONDecodeError as err:
|
566
827
|
raise ConfigChangeInvalidJSONError(name=name, json_str=value, err=err)
|
567
|
-
return value
|
568
828
|
|
569
|
-
|
829
|
+
@overload
|
830
|
+
def _set(
|
831
|
+
self, name: str, value: str, *, is_json: Literal[True], callback=True, quiet=False
|
832
|
+
) -> None:
|
833
|
+
...
|
834
|
+
|
835
|
+
@overload
|
836
|
+
def _set(
|
837
|
+
self,
|
838
|
+
name: str,
|
839
|
+
value: Any,
|
840
|
+
*,
|
841
|
+
is_json: Literal[False] = False,
|
842
|
+
callback=True,
|
843
|
+
quiet=False,
|
844
|
+
) -> None:
|
845
|
+
...
|
846
|
+
|
847
|
+
def _set(
|
848
|
+
self, name: str, value, *, is_json=False, callback=True, quiet=False
|
849
|
+
) -> None:
|
850
|
+
"""
|
851
|
+
Set a configuration item.
|
852
|
+
"""
|
853
|
+
if self._use_cache:
|
854
|
+
raise ConfigReadOnlyError()
|
855
|
+
|
570
856
|
if name not in self._configurable_keys:
|
571
857
|
raise ConfigNonConfigurableError(name=name)
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
if callback_val != current_val:
|
581
|
-
was_in_modified = False
|
582
|
-
was_in_unset = False
|
583
|
-
prev_modified_val = None
|
584
|
-
modified_updated = False
|
585
|
-
|
586
|
-
if name in self._modified_keys:
|
587
|
-
was_in_modified = True
|
588
|
-
prev_modified_val = self._modified_keys[name]
|
589
|
-
|
590
|
-
if name in self._unset_keys:
|
591
|
-
was_in_unset = True
|
592
|
-
idx = self._unset_keys.index(name)
|
593
|
-
self._unset_keys.pop(idx)
|
594
|
-
|
595
|
-
if callback_val != file_val:
|
596
|
-
self._modified_keys[name] = value
|
597
|
-
modified_updated = True
|
858
|
+
if is_json:
|
859
|
+
value = self._parse_JSON(name, cast("str", value))
|
860
|
+
current_val = self._get(name)
|
861
|
+
callback_val = self._get_callback_value(name, value)
|
862
|
+
file_val = self._get_callback_value(
|
863
|
+
name, self._file.get_config_item(self._config_key, name)
|
864
|
+
)
|
598
865
|
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
866
|
+
if callback_val != current_val:
|
867
|
+
was_in_modified = False
|
868
|
+
was_in_unset = False
|
869
|
+
prev_modified_val = None
|
870
|
+
modified_updated = False
|
871
|
+
mk = cast("dict", self._modified_keys)
|
872
|
+
|
873
|
+
if name in self._modified_keys:
|
874
|
+
was_in_modified = True
|
875
|
+
prev_modified_val = mk[name]
|
876
|
+
|
877
|
+
if name in self._unset_keys:
|
878
|
+
was_in_unset = True
|
879
|
+
self._unset_keys.remove(name)
|
880
|
+
|
881
|
+
if callback_val != file_val:
|
882
|
+
mk[name] = value
|
883
|
+
modified_updated = True
|
884
|
+
|
885
|
+
try:
|
886
|
+
self._validate()
|
887
|
+
|
888
|
+
if callback:
|
889
|
+
for cb in self._set_callbacks.get(name, ()):
|
890
|
+
self._logger.debug(
|
891
|
+
f"Invoking `config.set` callback for item {name!r}: {cb.__name__!r}"
|
892
|
+
)
|
893
|
+
cb(self, callback_val)
|
894
|
+
|
895
|
+
except ConfigValidationError as err:
|
896
|
+
# revert:
|
897
|
+
if modified_updated:
|
898
|
+
if was_in_modified:
|
899
|
+
mk[name] = prev_modified_val
|
900
|
+
else:
|
901
|
+
del mk[name]
|
902
|
+
if was_in_unset:
|
903
|
+
self._unset_keys.add(name)
|
904
|
+
|
905
|
+
raise ConfigChangeValidationError(name, validation_err=err) from None
|
620
906
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
907
|
+
self._logger.debug(
|
908
|
+
f"Successfully set config item {name!r} to {callback_val!r}."
|
909
|
+
)
|
910
|
+
elif not quiet:
|
911
|
+
print(f"value is already: {callback_val!r}")
|
626
912
|
|
627
|
-
|
913
|
+
@overload
|
914
|
+
def set(
|
915
|
+
self,
|
916
|
+
path: str,
|
917
|
+
value: Any,
|
918
|
+
*,
|
919
|
+
is_json: Literal[False] = False,
|
920
|
+
quiet: bool = False,
|
921
|
+
) -> None:
|
922
|
+
...
|
923
|
+
|
924
|
+
@overload
|
925
|
+
def set(
|
926
|
+
self, path: str, value: str, *, is_json: Literal[True], quiet: bool = False
|
927
|
+
) -> None:
|
928
|
+
...
|
929
|
+
|
930
|
+
def set(
|
931
|
+
self, path: str, value: Any, *, is_json: bool = False, quiet: bool = False
|
932
|
+
) -> None:
|
628
933
|
"""
|
629
934
|
Set the value of a configuration item.
|
630
935
|
|
@@ -640,16 +945,15 @@ class Config:
|
|
640
945
|
if is_json:
|
641
946
|
value = self._parse_JSON(path, value)
|
642
947
|
|
643
|
-
|
644
|
-
name = parts[0]
|
948
|
+
name, *path_suffix = path.split(".")
|
645
949
|
root = deepcopy(self._get(name, callback=False))
|
646
|
-
if
|
950
|
+
if path_suffix:
|
647
951
|
if root is None:
|
648
952
|
root = {}
|
649
|
-
self.set(path=
|
953
|
+
self.set(path=name, value={}, quiet=True)
|
650
954
|
set_in_container(
|
651
955
|
root,
|
652
|
-
path=
|
956
|
+
path=path_suffix,
|
653
957
|
value=value,
|
654
958
|
ensure_path=True,
|
655
959
|
cast_indices=True,
|
@@ -658,13 +962,13 @@ class Config:
|
|
658
962
|
root = value
|
659
963
|
self._set(name, root, quiet=quiet)
|
660
964
|
|
661
|
-
def unset(self, name):
|
965
|
+
def unset(self, name: str, callback: bool = True) -> None:
|
662
966
|
"""
|
663
967
|
Unset the value of a configuration item.
|
664
968
|
|
665
969
|
Parameters
|
666
970
|
----------
|
667
|
-
name:
|
971
|
+
name:
|
668
972
|
The name of the configuration item.
|
669
973
|
|
670
974
|
Notes
|
@@ -676,48 +980,74 @@ class Config:
|
|
676
980
|
if name in self._unset_keys or not self._file.is_item_set(self._config_key, name):
|
677
981
|
raise ConfigItemAlreadyUnsetError(name=name)
|
678
982
|
|
679
|
-
self._unset_keys.
|
983
|
+
self._unset_keys.add(name)
|
680
984
|
try:
|
681
985
|
self._validate()
|
986
|
+
if callback:
|
987
|
+
for cb in self._unset_callbacks.get(name, []):
|
988
|
+
self._logger.debug(
|
989
|
+
f"Invoking `config.unset` callback for item {name!r}: "
|
990
|
+
f"{cb.__name__!r}."
|
991
|
+
)
|
992
|
+
cb(self)
|
682
993
|
except ConfigValidationError as err:
|
683
|
-
self._unset_keys.
|
994
|
+
self._unset_keys.remove(name)
|
684
995
|
raise ConfigChangeValidationError(name, validation_err=err) from None
|
685
996
|
|
997
|
+
@overload
|
686
998
|
def get(
|
687
999
|
self,
|
688
|
-
path,
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
default=None,
|
694
|
-
):
|
1000
|
+
path: str,
|
1001
|
+
*,
|
1002
|
+
callback: bool = True,
|
1003
|
+
copy: bool = False,
|
1004
|
+
ret_root_and_parts: Literal[False] = False,
|
1005
|
+
default: Any | None = None,
|
1006
|
+
) -> Any:
|
1007
|
+
...
|
1008
|
+
|
1009
|
+
@overload
|
1010
|
+
def get(
|
1011
|
+
self,
|
1012
|
+
path: str,
|
1013
|
+
*,
|
1014
|
+
callback: bool = True,
|
1015
|
+
copy: bool = False,
|
1016
|
+
ret_root_and_parts: Literal[True],
|
1017
|
+
default: Any | None = None,
|
1018
|
+
) -> tuple[Any, Any, list[str]]:
|
1019
|
+
...
|
1020
|
+
|
1021
|
+
def get(
|
1022
|
+
self,
|
1023
|
+
path: str,
|
1024
|
+
*,
|
1025
|
+
callback: bool = True,
|
1026
|
+
copy: bool = False,
|
1027
|
+
ret_root_and_parts: bool = False,
|
1028
|
+
default: Any | None = None,
|
1029
|
+
) -> Any:
|
695
1030
|
"""
|
696
1031
|
Get the value of a configuration item.
|
697
1032
|
|
698
1033
|
Parameters
|
699
1034
|
----------
|
700
|
-
path:
|
1035
|
+
path:
|
701
1036
|
The name of or path to the configuration item.
|
702
1037
|
"""
|
703
|
-
parts = path.split(".")
|
704
|
-
root = deepcopy(self._get(
|
1038
|
+
name, *suffix = parts = path.split(".")
|
1039
|
+
root = deepcopy(self._get(name, callback=callback))
|
705
1040
|
try:
|
706
|
-
out = get_in_container(root,
|
1041
|
+
out = get_in_container(root, suffix, cast_indices=True)
|
707
1042
|
except KeyError:
|
708
1043
|
out = default
|
709
1044
|
if copy:
|
710
1045
|
out = deepcopy(out)
|
711
|
-
if not
|
1046
|
+
if not ret_root_and_parts:
|
712
1047
|
return out
|
713
|
-
|
714
|
-
if ret_root:
|
715
|
-
ret += [root]
|
716
|
-
if ret_parts:
|
717
|
-
ret += [parts]
|
718
|
-
return tuple(ret)
|
1048
|
+
return out, root, parts
|
719
1049
|
|
720
|
-
def append(self, path, value, is_json=False):
|
1050
|
+
def append(self, path: str, value, *, is_json: bool = False) -> None:
|
721
1051
|
"""
|
722
1052
|
Append a value to a list-like configuration item.
|
723
1053
|
|
@@ -733,8 +1063,7 @@ class Config:
|
|
733
1063
|
|
734
1064
|
existing, root, parts = self.get(
|
735
1065
|
path,
|
736
|
-
|
737
|
-
ret_parts=True,
|
1066
|
+
ret_root_and_parts=True,
|
738
1067
|
callback=False,
|
739
1068
|
default=[],
|
740
1069
|
)
|
@@ -756,7 +1085,7 @@ class Config:
|
|
756
1085
|
root = new
|
757
1086
|
self._set(parts[0], root)
|
758
1087
|
|
759
|
-
def prepend(self, path, value, is_json=False):
|
1088
|
+
def prepend(self, path: str, value, *, is_json: bool = False) -> None:
|
760
1089
|
"""
|
761
1090
|
Prepend a value to a list-like configuration item.
|
762
1091
|
|
@@ -771,7 +1100,7 @@ class Config:
|
|
771
1100
|
value = self._parse_JSON(path, value)
|
772
1101
|
|
773
1102
|
existing, root, parts = self.get(
|
774
|
-
path,
|
1103
|
+
path, ret_root_and_parts=True, callback=False, default=[]
|
775
1104
|
)
|
776
1105
|
|
777
1106
|
try:
|
@@ -791,7 +1120,7 @@ class Config:
|
|
791
1120
|
root = new
|
792
1121
|
self._set(parts[0], root)
|
793
1122
|
|
794
|
-
def pop(self, path, index):
|
1123
|
+
def pop(self, path: str, index) -> None:
|
795
1124
|
"""
|
796
1125
|
Remove a value from a specified index of a list-like configuration item.
|
797
1126
|
|
@@ -802,11 +1131,9 @@ class Config:
|
|
802
1131
|
index: int
|
803
1132
|
Where to remove the value from. 0 for the first item, -1 for the last.
|
804
1133
|
"""
|
805
|
-
|
806
1134
|
existing, root, parts = self.get(
|
807
1135
|
path,
|
808
|
-
|
809
|
-
ret_parts=True,
|
1136
|
+
ret_root_and_parts=True,
|
810
1137
|
callback=False,
|
811
1138
|
default=[],
|
812
1139
|
)
|
@@ -832,8 +1159,9 @@ class Config:
|
|
832
1159
|
root = new
|
833
1160
|
self._set(parts[0], root)
|
834
1161
|
|
835
|
-
def update(self, path: str, value, is_json=False):
|
836
|
-
"""
|
1162
|
+
def update(self, path: str, value, *, is_json: bool = False) -> None:
|
1163
|
+
"""
|
1164
|
+
Update a map-like configuration item.
|
837
1165
|
|
838
1166
|
Parameters
|
839
1167
|
----------
|
@@ -842,15 +1170,13 @@ class Config:
|
|
842
1170
|
value: dict
|
843
1171
|
A dictionary to merge in.
|
844
1172
|
"""
|
845
|
-
|
846
1173
|
if is_json:
|
847
1174
|
value = self._parse_JSON(path, value)
|
848
1175
|
|
849
1176
|
val_mod, root, parts = self.get(
|
850
1177
|
path,
|
851
1178
|
copy=True,
|
852
|
-
|
853
|
-
ret_parts=True,
|
1179
|
+
ret_root_and_parts=True,
|
854
1180
|
callback=False,
|
855
1181
|
default={},
|
856
1182
|
)
|
@@ -872,20 +1198,22 @@ class Config:
|
|
872
1198
|
root = val_mod
|
873
1199
|
self._set(parts[0], root)
|
874
1200
|
|
875
|
-
def save(self):
|
1201
|
+
def save(self) -> None:
|
876
1202
|
"""Save any modified/unset configuration items into the file."""
|
877
1203
|
if not self._modified_keys and not self._unset_keys:
|
878
1204
|
print("No modifications to save!")
|
879
1205
|
else:
|
880
1206
|
self._file.save()
|
881
1207
|
|
882
|
-
def get_configurable(self):
|
1208
|
+
def get_configurable(self) -> Sequence[str]:
|
883
1209
|
"""Get a list of all configurable keys."""
|
884
1210
|
return self._configurable_keys
|
885
1211
|
|
886
|
-
def _get_user_id(self):
|
887
|
-
"""
|
888
|
-
|
1212
|
+
def _get_user_id(self) -> tuple[str, Path]:
|
1213
|
+
"""
|
1214
|
+
Retrieve (and set if non-existent) a unique user ID that is independent of the
|
1215
|
+
config directory.
|
1216
|
+
"""
|
889
1217
|
|
890
1218
|
uid_file_path = self._app.user_data_dir.joinpath("user_id.txt")
|
891
1219
|
if not uid_file_path.exists():
|
@@ -898,12 +1226,12 @@ class Config:
|
|
898
1226
|
|
899
1227
|
return uid, uid_file_path
|
900
1228
|
|
901
|
-
def reset(self):
|
1229
|
+
def reset(self) -> None:
|
902
1230
|
"""Reset to the default configuration."""
|
903
|
-
self._logger.info(
|
1231
|
+
self._logger.info("Resetting config file to defaults.")
|
904
1232
|
self._app.reset_config()
|
905
1233
|
|
906
|
-
def add_scheduler(self, scheduler, **defaults):
|
1234
|
+
def add_scheduler(self, scheduler: str, **defaults) -> None:
|
907
1235
|
"""
|
908
1236
|
Add a scheduler.
|
909
1237
|
"""
|
@@ -912,7 +1240,7 @@ class Config:
|
|
912
1240
|
return
|
913
1241
|
self.update(f"schedulers.{scheduler}.defaults", defaults)
|
914
1242
|
|
915
|
-
def add_shell(self, shell, **defaults):
|
1243
|
+
def add_shell(self, shell: str, **defaults) -> None:
|
916
1244
|
"""
|
917
1245
|
Add a shell.
|
918
1246
|
"""
|
@@ -923,7 +1251,7 @@ class Config:
|
|
923
1251
|
self.add_scheduler("direct_posix")
|
924
1252
|
self.update(f"shells.{shell}.defaults", defaults)
|
925
1253
|
|
926
|
-
def add_shell_WSL(self, **defaults):
|
1254
|
+
def add_shell_WSL(self, **defaults) -> None:
|
927
1255
|
"""
|
928
1256
|
Add shell with WSL prefix.
|
929
1257
|
"""
|
@@ -931,8 +1259,11 @@ class Config:
|
|
931
1259
|
defaults["WSL_executable"] = "wsl.exe"
|
932
1260
|
self.add_shell("wsl", **defaults)
|
933
1261
|
|
934
|
-
def import_from_file(
|
935
|
-
|
1262
|
+
def import_from_file(
|
1263
|
+
self, file_path: Path | str, *, rename=True, make_new=False
|
1264
|
+
) -> None:
|
1265
|
+
"""
|
1266
|
+
Import config items from a (remote or local) YAML file. Existing config items
|
936
1267
|
of the same names will be overwritten.
|
937
1268
|
|
938
1269
|
Parameters
|
@@ -947,17 +1278,12 @@ class Config:
|
|
947
1278
|
If True, add the config items as a new config, rather than modifying the
|
948
1279
|
current config. The name of the new config will be the stem of the file
|
949
1280
|
specified in `file_path`.
|
950
|
-
|
951
1281
|
"""
|
952
|
-
|
953
1282
|
self._logger.debug(f"import from file: {file_path!r}")
|
954
1283
|
|
955
1284
|
console = Console()
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
try:
|
960
|
-
file_dat = read_YAML_file(file_path)
|
1285
|
+
with console.status(f"Importing config from file {file_path!r}...") as status:
|
1286
|
+
file_dat: DefaultConfiguration = read_YAML_file(file_path)
|
961
1287
|
if rename or make_new:
|
962
1288
|
file_stem = Path(file_path).stem
|
963
1289
|
name = file_stem
|
@@ -992,72 +1318,102 @@ class Config:
|
|
992
1318
|
)
|
993
1319
|
|
994
1320
|
new_invoc = file_dat.get("invocation")
|
995
|
-
new_config = file_dat.get("config")
|
1321
|
+
new_config = file_dat.get("config", {})
|
996
1322
|
|
997
|
-
if new_invoc:
|
1323
|
+
if new_invoc is not None:
|
998
1324
|
status.update("Updating invocation details...")
|
999
1325
|
config_key = file_stem if (make_new or rename) else self._config_key
|
1000
1326
|
obj._file.update_invocation(
|
1001
1327
|
config_key=config_key,
|
1002
1328
|
environment_setup=new_invoc.get("environment_setup"),
|
1003
|
-
match=new_invoc.get("match"),
|
1329
|
+
match=new_invoc.get("match", {}),
|
1004
1330
|
)
|
1005
1331
|
|
1006
1332
|
# sort in reverse so "schedulers" and "shells" are set before
|
1007
1333
|
# "default_scheduler" and "default_shell" which might reference the former:
|
1008
|
-
|
1009
|
-
for k, v in new_config.items():
|
1334
|
+
for k, v in sorted(new_config.items(), reverse=True):
|
1010
1335
|
status.update(f"Updating configurable item {k!r}")
|
1011
1336
|
obj.set(k, value=v, quiet=True)
|
1012
1337
|
|
1013
1338
|
obj.save()
|
1014
1339
|
|
1015
|
-
except Exception:
|
1016
|
-
status.stop()
|
1017
|
-
raise
|
1018
|
-
|
1019
|
-
status.stop()
|
1020
1340
|
print(f"Config {name!r} updated.")
|
1021
1341
|
|
1022
|
-
def init(self, known_name: str, path:
|
1342
|
+
def init(self, known_name: str, path: str | None = None) -> None:
|
1023
1343
|
"""Configure from a known importable config."""
|
1024
1344
|
if not path:
|
1025
|
-
path
|
1026
|
-
if not path:
|
1345
|
+
if not (path := self._options.default_known_configs_dir):
|
1027
1346
|
raise ValueError("Specify an `path` to search for known config files.")
|
1028
1347
|
elif path == ".":
|
1029
1348
|
path = str(Path(path).resolve())
|
1030
1349
|
|
1031
1350
|
self._logger.debug(f"init with `path` = {path!r}")
|
1032
1351
|
|
1033
|
-
fs = fsspec.open(path).fs
|
1352
|
+
fs: AbstractFileSystem = fsspec.open(path).fs
|
1034
1353
|
local_path = f"{path}/" if isinstance(fs, LocalFileSystem) else ""
|
1035
1354
|
files = fs.glob(f"{local_path}*.yaml") + fs.glob(f"{local_path}*.yml")
|
1036
1355
|
self._logger.debug(f"All YAML files found in file-system {fs!r}: {files}")
|
1037
1356
|
|
1038
|
-
files
|
1039
|
-
if not files:
|
1357
|
+
if not (files := [i for i in files if Path(i).stem.startswith(known_name)]):
|
1040
1358
|
print(f"No configuration-import files found matching name {known_name!r}.")
|
1041
1359
|
return
|
1042
1360
|
|
1043
1361
|
print(f"Found configuration-import files: {files!r}")
|
1044
|
-
for
|
1045
|
-
self.import_from_file(file_path=
|
1362
|
+
for file_i in files:
|
1363
|
+
self.import_from_file(file_path=file_i, make_new=True)
|
1046
1364
|
|
1047
|
-
print(
|
1365
|
+
print("imports complete")
|
1048
1366
|
# if current config is named "default", rename machine to DEFAULT_CONFIG:
|
1049
1367
|
if self._config_key == "default":
|
1050
1368
|
self.set("machine", "DEFAULT_MACHINE")
|
1051
1369
|
self.save()
|
1052
1370
|
|
1053
|
-
def set_github_demo_data_dir(self, sha):
|
1054
|
-
"""
|
1371
|
+
def set_github_demo_data_dir(self, sha: str) -> None:
|
1372
|
+
"""
|
1373
|
+
Set the `demo_data_dir` item, to an fsspec Github URL.
|
1055
1374
|
|
1056
1375
|
We use this (via the CLI) when testing the frozen app on Github, because, by
|
1057
1376
|
default, the SHA is set to the current version tag, which might not include recent
|
1058
1377
|
changes to the demo data.
|
1059
|
-
|
1060
1378
|
"""
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1379
|
+
assert self._app.demo_data_dir is not None
|
1380
|
+
self.set(
|
1381
|
+
"demo_data_dir",
|
1382
|
+
self._app._get_github_url(
|
1383
|
+
sha=sha, path=self._app.demo_data_dir.replace(".", "/")
|
1384
|
+
),
|
1385
|
+
)
|
1386
|
+
|
1387
|
+
@contextlib.contextmanager
|
1388
|
+
def cached_config(self) -> Iterator[None]:
|
1389
|
+
try:
|
1390
|
+
self._use_cache = True
|
1391
|
+
yield
|
1392
|
+
finally:
|
1393
|
+
self._use_cache = False
|
1394
|
+
self._config_cache = {} # reset the cache
|
1395
|
+
|
1396
|
+
def _is_set(self, name: str) -> bool:
|
1397
|
+
"""Check if a (non-metadata) config item is set."""
|
1398
|
+
if name in self._unset_keys:
|
1399
|
+
return False
|
1400
|
+
elif name in self._modified_keys:
|
1401
|
+
return True
|
1402
|
+
else:
|
1403
|
+
return self._file.is_item_set(self._config_key, name)
|
1404
|
+
|
1405
|
+
@contextlib.contextmanager
|
1406
|
+
def _with_updates(self, updates: dict[str, Any]) -> Iterator[None]:
|
1407
|
+
# need to run callbacks for unsetting?
|
1408
|
+
prev_unset = copy.deepcopy(self._unset_keys)
|
1409
|
+
prev_modified = copy.deepcopy(self._modified_keys)
|
1410
|
+
to_unset = []
|
1411
|
+
try:
|
1412
|
+
for k, v in updates.items():
|
1413
|
+
if not self._is_set(k):
|
1414
|
+
to_unset.append(k)
|
1415
|
+
self.set(k, v)
|
1416
|
+
yield
|
1417
|
+
finally:
|
1418
|
+
self._unset_keys = prev_unset
|
1419
|
+
self._modified_keys = prev_modified
|