brkraw 0.3.11__py3-none-any.whl → 0.5.0__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.
- brkraw/__init__.py +9 -3
- brkraw/apps/__init__.py +12 -0
- brkraw/apps/addon/__init__.py +30 -0
- brkraw/apps/addon/core.py +35 -0
- brkraw/apps/addon/dependencies.py +402 -0
- brkraw/apps/addon/installation.py +500 -0
- brkraw/apps/addon/io.py +21 -0
- brkraw/apps/hook/__init__.py +25 -0
- brkraw/apps/hook/core.py +636 -0
- brkraw/apps/loader/__init__.py +10 -0
- brkraw/apps/loader/core.py +622 -0
- brkraw/apps/loader/formatter.py +288 -0
- brkraw/apps/loader/helper.py +797 -0
- brkraw/apps/loader/info/__init__.py +11 -0
- brkraw/apps/loader/info/scan.py +85 -0
- brkraw/apps/loader/info/scan.yaml +90 -0
- brkraw/apps/loader/info/study.py +69 -0
- brkraw/apps/loader/info/study.yaml +156 -0
- brkraw/apps/loader/info/transform.py +92 -0
- brkraw/apps/loader/types.py +220 -0
- brkraw/cli/__init__.py +5 -0
- brkraw/cli/commands/__init__.py +2 -0
- brkraw/cli/commands/addon.py +327 -0
- brkraw/cli/commands/config.py +205 -0
- brkraw/cli/commands/convert.py +903 -0
- brkraw/cli/commands/hook.py +348 -0
- brkraw/cli/commands/info.py +74 -0
- brkraw/cli/commands/init.py +214 -0
- brkraw/cli/commands/params.py +106 -0
- brkraw/cli/commands/prune.py +288 -0
- brkraw/cli/commands/session.py +371 -0
- brkraw/cli/hook_args.py +80 -0
- brkraw/cli/main.py +83 -0
- brkraw/cli/utils.py +60 -0
- brkraw/core/__init__.py +13 -0
- brkraw/core/config.py +380 -0
- brkraw/core/entrypoints.py +25 -0
- brkraw/core/formatter.py +367 -0
- brkraw/core/fs.py +495 -0
- brkraw/core/jcamp.py +600 -0
- brkraw/core/layout.py +451 -0
- brkraw/core/parameters.py +781 -0
- brkraw/core/zip.py +1121 -0
- brkraw/dataclasses/__init__.py +14 -0
- brkraw/dataclasses/node.py +139 -0
- brkraw/dataclasses/reco.py +33 -0
- brkraw/dataclasses/scan.py +61 -0
- brkraw/dataclasses/study.py +131 -0
- brkraw/default/__init__.py +3 -0
- brkraw/default/pruner_specs/deid4share.yaml +42 -0
- brkraw/default/rules/00_default.yaml +4 -0
- brkraw/default/specs/metadata_dicom.yaml +236 -0
- brkraw/default/specs/metadata_transforms.py +92 -0
- brkraw/resolver/__init__.py +7 -0
- brkraw/resolver/affine.py +539 -0
- brkraw/resolver/datatype.py +69 -0
- brkraw/resolver/fid.py +90 -0
- brkraw/resolver/helpers.py +36 -0
- brkraw/resolver/image.py +188 -0
- brkraw/resolver/nifti.py +370 -0
- brkraw/resolver/shape.py +235 -0
- brkraw/schema/__init__.py +3 -0
- brkraw/schema/context_map.yaml +62 -0
- brkraw/schema/meta.yaml +57 -0
- brkraw/schema/niftiheader.yaml +95 -0
- brkraw/schema/pruner.yaml +55 -0
- brkraw/schema/remapper.yaml +128 -0
- brkraw/schema/rules.yaml +154 -0
- brkraw/specs/__init__.py +10 -0
- brkraw/specs/hook/__init__.py +12 -0
- brkraw/specs/hook/logic.py +31 -0
- brkraw/specs/hook/validator.py +22 -0
- brkraw/specs/meta/__init__.py +5 -0
- brkraw/specs/meta/validator.py +156 -0
- brkraw/specs/pruner/__init__.py +15 -0
- brkraw/specs/pruner/logic.py +361 -0
- brkraw/specs/pruner/validator.py +119 -0
- brkraw/specs/remapper/__init__.py +27 -0
- brkraw/specs/remapper/logic.py +924 -0
- brkraw/specs/remapper/validator.py +314 -0
- brkraw/specs/rules/__init__.py +6 -0
- brkraw/specs/rules/logic.py +263 -0
- brkraw/specs/rules/validator.py +103 -0
- brkraw-0.5.0.dist-info/METADATA +81 -0
- brkraw-0.5.0.dist-info/RECORD +88 -0
- {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info}/WHEEL +1 -2
- brkraw-0.5.0.dist-info/entry_points.txt +13 -0
- brkraw/lib/__init__.py +0 -4
- brkraw/lib/backup.py +0 -641
- brkraw/lib/bids.py +0 -0
- brkraw/lib/errors.py +0 -125
- brkraw/lib/loader.py +0 -1220
- brkraw/lib/orient.py +0 -194
- brkraw/lib/parser.py +0 -48
- brkraw/lib/pvobj.py +0 -301
- brkraw/lib/reference.py +0 -245
- brkraw/lib/utils.py +0 -471
- brkraw/scripts/__init__.py +0 -0
- brkraw/scripts/brk_backup.py +0 -106
- brkraw/scripts/brkraw.py +0 -744
- brkraw/ui/__init__.py +0 -0
- brkraw/ui/config.py +0 -17
- brkraw/ui/main_win.py +0 -214
- brkraw/ui/previewer.py +0 -225
- brkraw/ui/scan_info.py +0 -72
- brkraw/ui/scan_list.py +0 -73
- brkraw/ui/subj_info.py +0 -128
- brkraw-0.3.11.dist-info/METADATA +0 -25
- brkraw-0.3.11.dist-info/RECORD +0 -28
- brkraw-0.3.11.dist-info/entry_points.txt +0 -3
- brkraw-0.3.11.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info/licenses}/LICENSE +0 -0
brkraw/core/config.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, Optional, Union
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
ENV_CONFIG_HOME = "BRKRAW_CONFIG_HOME"
|
|
12
|
+
DEFAULT_PROFILE_DIRNAME = ".brkraw"
|
|
13
|
+
CONFIG_VERSION = 0
|
|
14
|
+
|
|
15
|
+
DEFAULT_CONFIG_YAML = """# brkraw user configuration
|
|
16
|
+
# This file is optional. Delete it to fall back to package defaults.
|
|
17
|
+
# You can override the config root by setting BRKRAW_CONFIG_HOME.
|
|
18
|
+
config_version: 0
|
|
19
|
+
|
|
20
|
+
# Editor command used by brkraw config/addon edit.
|
|
21
|
+
editor: null
|
|
22
|
+
|
|
23
|
+
logging:
|
|
24
|
+
level: INFO
|
|
25
|
+
print_width: 120
|
|
26
|
+
|
|
27
|
+
output:
|
|
28
|
+
# output.layout_entries defines how NIfTI filenames are built.
|
|
29
|
+
layout_entries:
|
|
30
|
+
- key: Subject.ID
|
|
31
|
+
entry: sub
|
|
32
|
+
hide: false
|
|
33
|
+
- key: Study.ID
|
|
34
|
+
entry: study
|
|
35
|
+
hide: false
|
|
36
|
+
- key: ScanID
|
|
37
|
+
entry: scan
|
|
38
|
+
hide: false
|
|
39
|
+
- key: Protocol
|
|
40
|
+
hide: true
|
|
41
|
+
layout_template: null
|
|
42
|
+
slicepack_suffix: "_slpack{index}"
|
|
43
|
+
# float_decimals: 6
|
|
44
|
+
|
|
45
|
+
# Viewer settings for brkraw-viewer (optional GUI extension).
|
|
46
|
+
viewer:
|
|
47
|
+
cache:
|
|
48
|
+
# Cache loaded scan data in memory to speed up space/pose changes.
|
|
49
|
+
enabled: true
|
|
50
|
+
# Maximum number of scan/reco entries to keep (LRU). 0 disables caching.
|
|
51
|
+
max_items: 10
|
|
52
|
+
|
|
53
|
+
# rules_dir: rules
|
|
54
|
+
# specs_dir: specs
|
|
55
|
+
# pruner_specs_dir: pruner_specs
|
|
56
|
+
# transforms_dir: transforms
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class ConfigPaths:
|
|
62
|
+
root: Path
|
|
63
|
+
config_file: Path
|
|
64
|
+
specs_dir: Path
|
|
65
|
+
pruner_specs_dir: Path
|
|
66
|
+
rules_dir: Path
|
|
67
|
+
transforms_dir: Path
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resolve_root(root: Optional[Union[str, Path]] = None) -> Path:
|
|
71
|
+
if root is not None:
|
|
72
|
+
return Path(root).expanduser()
|
|
73
|
+
env_root = os.environ.get(ENV_CONFIG_HOME)
|
|
74
|
+
if env_root:
|
|
75
|
+
return Path(env_root).expanduser()
|
|
76
|
+
return Path.home() / DEFAULT_PROFILE_DIRNAME
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_paths(root: Optional[Union[str, Path]] = None) -> ConfigPaths:
|
|
80
|
+
base = resolve_root(root)
|
|
81
|
+
return ConfigPaths(
|
|
82
|
+
root=base,
|
|
83
|
+
config_file=base / "config.yaml",
|
|
84
|
+
specs_dir=base / "specs",
|
|
85
|
+
pruner_specs_dir=base / "pruner_specs",
|
|
86
|
+
rules_dir=base / "rules",
|
|
87
|
+
transforms_dir=base / "transforms",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def paths(root: Optional[Union[str, Path]] = None) -> ConfigPaths:
|
|
92
|
+
return get_paths(root=root)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_path(name: str, root: Optional[Union[str, Path]] = None) -> Path:
|
|
96
|
+
paths_obj = get_paths(root=root)
|
|
97
|
+
mapping = {
|
|
98
|
+
"root": paths_obj.root,
|
|
99
|
+
"config": paths_obj.config_file,
|
|
100
|
+
"specs": paths_obj.specs_dir,
|
|
101
|
+
"pruner_specs": paths_obj.pruner_specs_dir,
|
|
102
|
+
"rules": paths_obj.rules_dir,
|
|
103
|
+
"transforms": paths_obj.transforms_dir,
|
|
104
|
+
}
|
|
105
|
+
if name not in mapping:
|
|
106
|
+
raise KeyError(f"Unknown config path: {name}")
|
|
107
|
+
return mapping[name]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def is_initialized(root: Optional[Union[str, Path]] = None) -> bool:
|
|
111
|
+
paths = get_paths(root)
|
|
112
|
+
return paths.config_file.exists()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def ensure_initialized(
|
|
116
|
+
root: Optional[Union[str, Path]] = None,
|
|
117
|
+
*,
|
|
118
|
+
create_config: bool = True,
|
|
119
|
+
exist_ok: bool = True,
|
|
120
|
+
) -> ConfigPaths:
|
|
121
|
+
paths = get_paths(root)
|
|
122
|
+
if paths.root.exists() and not exist_ok:
|
|
123
|
+
raise FileExistsError(paths.root)
|
|
124
|
+
paths.root.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
paths.specs_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
paths.pruner_specs_dir.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
paths.rules_dir.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
paths.transforms_dir.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
if create_config and not paths.config_file.exists():
|
|
130
|
+
paths.config_file.write_text(DEFAULT_CONFIG_YAML, encoding="utf-8")
|
|
131
|
+
return paths
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def init(
|
|
135
|
+
root: Optional[Union[str, Path]] = None,
|
|
136
|
+
*,
|
|
137
|
+
create_config: bool = True,
|
|
138
|
+
exist_ok: bool = True,
|
|
139
|
+
) -> ConfigPaths:
|
|
140
|
+
return ensure_initialized(root=root, create_config=create_config, exist_ok=exist_ok)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def load_config(root: Optional[Union[str, Path]] = None) -> Optional[Dict[str, Any]]:
|
|
144
|
+
paths = get_paths(root)
|
|
145
|
+
if not paths.config_file.exists():
|
|
146
|
+
return None
|
|
147
|
+
with paths.config_file.open("r", encoding="utf-8") as handle:
|
|
148
|
+
data = yaml.safe_load(handle)
|
|
149
|
+
if data is None:
|
|
150
|
+
return {}
|
|
151
|
+
if not isinstance(data, dict):
|
|
152
|
+
raise ValueError("config.yaml must contain a YAML mapping at the top level.")
|
|
153
|
+
return data
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def load(root: Optional[Union[str, Path]] = None) -> Optional[Dict[str, Any]]:
|
|
157
|
+
return load_config(root=root)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def write_config(data: Dict[str, Any], root: Optional[Union[str, Path]] = None) -> None:
|
|
161
|
+
data = _normalize_config(dict(data))
|
|
162
|
+
data["config_version"] = CONFIG_VERSION
|
|
163
|
+
paths = ensure_initialized(root=root, create_config=False, exist_ok=True)
|
|
164
|
+
paths.config_file.write_text(
|
|
165
|
+
yaml.safe_dump(data, sort_keys=False),
|
|
166
|
+
encoding="utf-8",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def reset_config(root: Optional[Union[str, Path]] = None) -> None:
|
|
171
|
+
paths = ensure_initialized(root=root, create_config=False, exist_ok=True)
|
|
172
|
+
paths.config_file.write_text(DEFAULT_CONFIG_YAML, encoding="utf-8")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def default_config() -> Dict[str, Any]:
|
|
176
|
+
data = yaml.safe_load(DEFAULT_CONFIG_YAML)
|
|
177
|
+
if not isinstance(data, dict):
|
|
178
|
+
return {}
|
|
179
|
+
return _normalize_config(data)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def resolve_config(root: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
|
|
183
|
+
defaults = default_config()
|
|
184
|
+
overrides = _normalize_config(load(root=root) or {})
|
|
185
|
+
overrides.pop("nifti_filename_template", None)
|
|
186
|
+
overrides.pop("output_format", None)
|
|
187
|
+
overrides["config_version"] = CONFIG_VERSION
|
|
188
|
+
defaults.pop("nifti_filename_template", None)
|
|
189
|
+
defaults.pop("output_format", None)
|
|
190
|
+
return _deep_merge(defaults, overrides)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def resolve_editor_binary(root: Optional[Union[str, Path]] = None) -> Optional[str]:
|
|
194
|
+
config = resolve_config(root=root)
|
|
195
|
+
editor = config.get("editor")
|
|
196
|
+
if not isinstance(editor, str) or not editor.strip():
|
|
197
|
+
editor = config.get("editor_binary")
|
|
198
|
+
if isinstance(editor, str) and editor.strip():
|
|
199
|
+
return editor.strip()
|
|
200
|
+
env_editor = os.environ.get("VISUAL") or os.environ.get("EDITOR")
|
|
201
|
+
if isinstance(env_editor, str) and env_editor.strip():
|
|
202
|
+
return env_editor.strip()
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def clear_config(
|
|
207
|
+
root: Optional[Union[str, Path]] = None,
|
|
208
|
+
*,
|
|
209
|
+
keep_config: bool = False,
|
|
210
|
+
keep_rules: bool = False,
|
|
211
|
+
keep_specs: bool = False,
|
|
212
|
+
keep_pruner_specs: bool = False,
|
|
213
|
+
keep_transforms: bool = False,
|
|
214
|
+
) -> None:
|
|
215
|
+
paths = get_paths(root=root)
|
|
216
|
+
if not paths.root.exists():
|
|
217
|
+
return
|
|
218
|
+
if paths.config_file.exists() and not keep_config:
|
|
219
|
+
paths.config_file.unlink()
|
|
220
|
+
if paths.rules_dir.exists() and not keep_rules:
|
|
221
|
+
_remove_tree(paths.rules_dir)
|
|
222
|
+
if paths.specs_dir.exists() and not keep_specs:
|
|
223
|
+
_remove_tree(paths.specs_dir)
|
|
224
|
+
if paths.pruner_specs_dir.exists() and not keep_pruner_specs:
|
|
225
|
+
_remove_tree(paths.pruner_specs_dir)
|
|
226
|
+
if paths.transforms_dir.exists() and not keep_transforms:
|
|
227
|
+
_remove_tree(paths.transforms_dir)
|
|
228
|
+
try:
|
|
229
|
+
paths.root.rmdir()
|
|
230
|
+
except OSError:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def clear(
|
|
235
|
+
root: Optional[Union[str, Path]] = None,
|
|
236
|
+
*,
|
|
237
|
+
keep_config: bool = False,
|
|
238
|
+
keep_rules: bool = False,
|
|
239
|
+
keep_specs: bool = False,
|
|
240
|
+
keep_pruner_specs: bool = False,
|
|
241
|
+
keep_transforms: bool = False,
|
|
242
|
+
) -> None:
|
|
243
|
+
clear_config(
|
|
244
|
+
root=root,
|
|
245
|
+
keep_config=keep_config,
|
|
246
|
+
keep_rules=keep_rules,
|
|
247
|
+
keep_specs=keep_specs,
|
|
248
|
+
keep_pruner_specs=keep_pruner_specs,
|
|
249
|
+
keep_transforms=keep_transforms,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def configure_logging(
|
|
254
|
+
*,
|
|
255
|
+
root: Optional[Union[str, Path]] = None,
|
|
256
|
+
level: Optional[Union[str, int]] = None,
|
|
257
|
+
stream=None,
|
|
258
|
+
) -> logging.Logger:
|
|
259
|
+
config = resolve_config(root=root)
|
|
260
|
+
if level is None:
|
|
261
|
+
level = config.get("logging", {}).get("level", "INFO")
|
|
262
|
+
if isinstance(level, str):
|
|
263
|
+
level = getattr(logging, level.upper(), logging.INFO)
|
|
264
|
+
if not logging.getLogger().handlers:
|
|
265
|
+
if level == logging.INFO:
|
|
266
|
+
fmt = "%(message)s"
|
|
267
|
+
else:
|
|
268
|
+
fmt = "%(levelname)s %(asctime)s %(message)s"
|
|
269
|
+
logging.basicConfig(level=level, format=fmt, stream=stream)
|
|
270
|
+
return logging.getLogger("brkraw")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def output_width(root: Optional[Union[str, Path]] = None, default: int = 120) -> int:
|
|
274
|
+
config = resolve_config(root=root)
|
|
275
|
+
width = config.get("logging", {}).get("print_width", default)
|
|
276
|
+
try:
|
|
277
|
+
return int(width)
|
|
278
|
+
except (TypeError, ValueError):
|
|
279
|
+
return default
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def float_decimals(root: Optional[Union[str, Path]] = None, default: int = 6) -> int:
|
|
283
|
+
config = resolve_config(root=root)
|
|
284
|
+
output_cfg = config.get("output", {})
|
|
285
|
+
decimals = output_cfg.get("float_decimals", config.get("float_decimals", default))
|
|
286
|
+
try:
|
|
287
|
+
return int(decimals)
|
|
288
|
+
except (TypeError, ValueError):
|
|
289
|
+
return default
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def affine_decimals(root: Optional[Union[str, Path]] = None, default: int = 6) -> int:
|
|
293
|
+
return float_decimals(root=root, default=default)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def layout_template(
|
|
297
|
+
root: Optional[Union[str, Path]] = None,
|
|
298
|
+
) -> Optional[str]:
|
|
299
|
+
config = resolve_config(root=root)
|
|
300
|
+
output_cfg = config.get("output", {})
|
|
301
|
+
value = output_cfg.get("layout_template")
|
|
302
|
+
if isinstance(value, str) and value.strip():
|
|
303
|
+
return value
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def layout_entries(
|
|
308
|
+
root: Optional[Union[str, Path]] = None,
|
|
309
|
+
default: Optional[list] = None,
|
|
310
|
+
) -> list:
|
|
311
|
+
config = resolve_config(root=root)
|
|
312
|
+
output_cfg = config.get("output", {})
|
|
313
|
+
fields = output_cfg.get("layout_entries")
|
|
314
|
+
if fields is None:
|
|
315
|
+
fields = output_cfg.get("layout_fields")
|
|
316
|
+
if fields is None:
|
|
317
|
+
fields = output_cfg.get("format_fields")
|
|
318
|
+
if isinstance(fields, list):
|
|
319
|
+
return fields
|
|
320
|
+
if default is None:
|
|
321
|
+
default = default_config().get("output", {}).get("layout_entries", [])
|
|
322
|
+
return list(default) if isinstance(default, list) else []
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def output_slicepack_suffix(
|
|
326
|
+
root: Optional[Union[str, Path]] = None,
|
|
327
|
+
default: str = "_slpack{index}",
|
|
328
|
+
) -> str:
|
|
329
|
+
config = resolve_config(root=root)
|
|
330
|
+
value = config.get("output", {}).get("slicepack_suffix", default)
|
|
331
|
+
return str(value) if isinstance(value, str) and value else default
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _normalize_config(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
335
|
+
config = dict(data)
|
|
336
|
+
logging_cfg = dict(config.get("logging") or {})
|
|
337
|
+
output_cfg = dict(config.get("output") or {})
|
|
338
|
+
|
|
339
|
+
config.pop("output_format", None)
|
|
340
|
+
if "log_level" in config and "level" not in logging_cfg:
|
|
341
|
+
logging_cfg["level"] = config.pop("log_level")
|
|
342
|
+
if "output_width" in config and "print_width" not in logging_cfg:
|
|
343
|
+
logging_cfg["print_width"] = config.pop("output_width")
|
|
344
|
+
config.pop("output_format_fields", None)
|
|
345
|
+
config.pop("output_format_spec", None)
|
|
346
|
+
if "layout_fields" in output_cfg and "layout_entries" not in output_cfg:
|
|
347
|
+
output_cfg["layout_entries"] = output_cfg["layout_fields"]
|
|
348
|
+
if "float_decimals" in config and "float_decimals" not in output_cfg:
|
|
349
|
+
output_cfg["float_decimals"] = config.pop("float_decimals")
|
|
350
|
+
if "editor_binary" in config and "editor" not in config:
|
|
351
|
+
config["editor"] = config.pop("editor_binary")
|
|
352
|
+
|
|
353
|
+
if logging_cfg:
|
|
354
|
+
config["logging"] = logging_cfg
|
|
355
|
+
if output_cfg:
|
|
356
|
+
config["output"] = output_cfg
|
|
357
|
+
return config
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
|
|
361
|
+
merged = dict(base)
|
|
362
|
+
for key, value in override.items():
|
|
363
|
+
if (
|
|
364
|
+
key in merged
|
|
365
|
+
and isinstance(merged[key], dict)
|
|
366
|
+
and isinstance(value, dict)
|
|
367
|
+
):
|
|
368
|
+
merged[key] = _deep_merge(merged[key], value)
|
|
369
|
+
else:
|
|
370
|
+
merged[key] = value
|
|
371
|
+
return merged
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _remove_tree(path: Path) -> None:
|
|
375
|
+
for child in path.iterdir():
|
|
376
|
+
if child.is_dir():
|
|
377
|
+
_remove_tree(child)
|
|
378
|
+
else:
|
|
379
|
+
child.unlink()
|
|
380
|
+
path.rmdir()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
from typing import Optional, List, Any, cast
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def list_entry_points(group: str, name: Optional[str] = None) -> List[importlib.metadata.EntryPoint]:
|
|
8
|
+
"""List installed entry points for a group/name.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
group: Entry point group name.
|
|
12
|
+
name: Optional entry point name filter.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
List of matching entry points.
|
|
16
|
+
"""
|
|
17
|
+
eps = importlib.metadata.entry_points()
|
|
18
|
+
if hasattr(eps, "select"):
|
|
19
|
+
eps_any = cast(Any, eps)
|
|
20
|
+
if name is not None:
|
|
21
|
+
return list(eps_any.select(group=group, name=name))
|
|
22
|
+
return list(eps_any.select(group=group))
|
|
23
|
+
if name is not None:
|
|
24
|
+
return [ep for ep in eps.get(group, []) if ep.name == name] # type: ignore[call-arg,attr-defined]
|
|
25
|
+
return list(eps.get(group, [])) # type: ignore[call-arg,attr-defined]
|