lib-layered-config 1.0.0__py3-none-any.whl → 1.1.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.
Potentially problematic release.
This version of lib-layered-config might be problematic. Click here for more details.
- lib_layered_config/__init__conf__.py +67 -0
- lib_layered_config/adapters/file_loaders/structured.py +103 -16
- lib_layered_config/cli/__init__.py +44 -62
- lib_layered_config/cli/common.py +260 -52
- lib_layered_config/examples/generate.py +1 -1
- {lib_layered_config-1.0.0.dist-info → lib_layered_config-1.1.0.dist-info}/METADATA +23 -18
- {lib_layered_config-1.0.0.dist-info → lib_layered_config-1.1.0.dist-info}/RECORD +10 -9
- {lib_layered_config-1.0.0.dist-info → lib_layered_config-1.1.0.dist-info}/WHEEL +0 -0
- {lib_layered_config-1.0.0.dist-info → lib_layered_config-1.1.0.dist-info}/entry_points.txt +0 -0
- {lib_layered_config-1.0.0.dist-info → lib_layered_config-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Static package metadata surfaced to CLI commands and documentation.
|
|
2
|
+
|
|
3
|
+
Purpose
|
|
4
|
+
-------
|
|
5
|
+
Expose the current project metadata as simple constants. These values are kept
|
|
6
|
+
in sync with ``pyproject.toml`` by development automation (tests, push
|
|
7
|
+
pipelines), so runtime code does not query packaging metadata.
|
|
8
|
+
|
|
9
|
+
Contents
|
|
10
|
+
--------
|
|
11
|
+
* Module-level constants describing the published package.
|
|
12
|
+
* :func:`print_info` rendering the constants for the CLI ``info`` command.
|
|
13
|
+
|
|
14
|
+
System Role
|
|
15
|
+
-----------
|
|
16
|
+
Lives in the adapters/platform layer; CLI transports import these constants to
|
|
17
|
+
present authoritative project information without invoking packaging APIs.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
#: Distribution name declared in ``pyproject.toml``.
|
|
23
|
+
name = "lib_layered_config"
|
|
24
|
+
#: Human-readable summary shown in CLI help output.
|
|
25
|
+
title = "Cross-platform layered configuration loader for Python"
|
|
26
|
+
#: Current release version pulled from ``pyproject.toml`` by automation.
|
|
27
|
+
version = "1.1.0"
|
|
28
|
+
#: Repository homepage presented to users.
|
|
29
|
+
homepage = "https://github.com/bitranox/lib_layered_config"
|
|
30
|
+
#: Author attribution surfaced in CLI output.
|
|
31
|
+
author = "bitranox"
|
|
32
|
+
#: Contact email surfaced in CLI output.
|
|
33
|
+
author_email = "bitranox@gmail.com"
|
|
34
|
+
#: Console-script name published by the package.
|
|
35
|
+
shell_command = "lib-layered-config"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def print_info() -> None:
|
|
39
|
+
"""Print the summarised metadata block used by the CLI ``info`` command.
|
|
40
|
+
|
|
41
|
+
Why
|
|
42
|
+
Provides a single, auditable rendering function so documentation and
|
|
43
|
+
CLI output always match the system design reference.
|
|
44
|
+
|
|
45
|
+
Side Effects
|
|
46
|
+
Writes to ``stdout``.
|
|
47
|
+
|
|
48
|
+
Examples
|
|
49
|
+
--------
|
|
50
|
+
>>> print_info() # doctest: +ELLIPSIS
|
|
51
|
+
Info for lib_layered_config:
|
|
52
|
+
...
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
fields = [
|
|
56
|
+
("name", name),
|
|
57
|
+
("title", title),
|
|
58
|
+
("version", version),
|
|
59
|
+
("homepage", homepage),
|
|
60
|
+
("author", author),
|
|
61
|
+
("author_email", author_email),
|
|
62
|
+
("shell_command", shell_command),
|
|
63
|
+
]
|
|
64
|
+
pad = max(len(label) for label, _ in fields)
|
|
65
|
+
lines = [f"Info for {name}:", ""]
|
|
66
|
+
lines.extend(f" {label.ljust(pad)} = {value}" for label, value in fields)
|
|
67
|
+
print("\n".join(lines))
|
|
@@ -25,18 +25,17 @@ before passing the results to the merge policy.
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
27
|
import json
|
|
28
|
+
from importlib import import_module
|
|
28
29
|
from pathlib import Path
|
|
29
30
|
from typing import Any, Mapping, NoReturn
|
|
31
|
+
from types import ModuleType
|
|
30
32
|
|
|
31
33
|
import tomllib
|
|
32
34
|
|
|
33
35
|
from ...domain.errors import InvalidFormat, NotFound
|
|
34
36
|
from ...observability import log_debug, log_error
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
import yaml # type: ignore[import-not-found]
|
|
38
|
-
except ModuleNotFoundError: # pragma: no cover - optional dependency
|
|
39
|
-
yaml = None # type: ignore[assignment]
|
|
38
|
+
yaml: ModuleType | None = None
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
FILE_LAYER = "file"
|
|
@@ -139,25 +138,73 @@ def _raise_invalid_format(path: str, format_name: str, exc: Exception) -> NoRetu
|
|
|
139
138
|
|
|
140
139
|
|
|
141
140
|
def _ensure_yaml_available() -> None:
|
|
142
|
-
"""
|
|
141
|
+
"""Announce clearly whether PyYAML can be reached.
|
|
143
142
|
|
|
144
143
|
Why
|
|
145
144
|
----
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
YAML support is optional; the loader must fail fast with guidance when the
|
|
146
|
+
dependency is absent so callers can install the expected extra.
|
|
148
147
|
|
|
149
148
|
Returns
|
|
150
149
|
-------
|
|
151
150
|
None
|
|
152
151
|
|
|
152
|
+
Raises
|
|
153
|
+
------
|
|
154
|
+
NotFound
|
|
155
|
+
When the PyYAML package cannot be imported.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
_require_yaml_module()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _require_yaml_module() -> ModuleType:
|
|
162
|
+
"""Fetch the PyYAML module or explain its absence.
|
|
163
|
+
|
|
164
|
+
Why
|
|
165
|
+
----
|
|
166
|
+
Downstream helpers need the module object for access to both ``safe_load``
|
|
167
|
+
and the package-specific ``YAMLError`` type.
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
ModuleType
|
|
172
|
+
The imported PyYAML module.
|
|
173
|
+
|
|
153
174
|
Raises
|
|
154
175
|
------
|
|
155
176
|
NotFound
|
|
156
177
|
When PyYAML is not installed.
|
|
157
178
|
"""
|
|
158
179
|
|
|
159
|
-
|
|
180
|
+
module = _load_yaml_module()
|
|
181
|
+
if module is None:
|
|
160
182
|
raise NotFound("PyYAML is required for YAML configuration support")
|
|
183
|
+
return module
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _load_yaml_module() -> ModuleType | None:
|
|
187
|
+
"""Import PyYAML on demand, caching the result for future readers.
|
|
188
|
+
|
|
189
|
+
Why
|
|
190
|
+
----
|
|
191
|
+
Avoid importing optional dependencies unless they are genuinely needed,
|
|
192
|
+
while still ensuring subsequent calls reuse the same module object.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
ModuleType | None
|
|
197
|
+
The PyYAML module when available; otherwise ``None``.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
global yaml
|
|
201
|
+
if yaml is not None:
|
|
202
|
+
return yaml
|
|
203
|
+
try:
|
|
204
|
+
yaml = import_module("yaml")
|
|
205
|
+
except ModuleNotFoundError: # pragma: no cover - optional dependency
|
|
206
|
+
yaml = None
|
|
207
|
+
return yaml
|
|
161
208
|
|
|
162
209
|
|
|
163
210
|
class BaseFileLoader:
|
|
@@ -390,7 +437,7 @@ class YAMLFileLoader(BaseFileLoader):
|
|
|
390
437
|
|
|
391
438
|
Examples
|
|
392
439
|
--------
|
|
393
|
-
>>> if
|
|
440
|
+
>>> if _load_yaml_module() is not None: # doctest: +SKIP
|
|
394
441
|
... from tempfile import NamedTemporaryFile
|
|
395
442
|
... tmp = NamedTemporaryFile('w', delete=False, encoding='utf-8')
|
|
396
443
|
... _ = tmp.write('key: 1')
|
|
@@ -400,11 +447,51 @@ class YAMLFileLoader(BaseFileLoader):
|
|
|
400
447
|
"""
|
|
401
448
|
|
|
402
449
|
_ensure_yaml_available()
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
data: object = dict[str, object]() if payload is None else payload
|
|
408
|
-
result = self._ensure_mapping(data, path=path)
|
|
450
|
+
yaml_module = _require_yaml_module()
|
|
451
|
+
raw_bytes = self._read(path)
|
|
452
|
+
parsed = _parse_yaml_bytes(raw_bytes, yaml_module, path)
|
|
453
|
+
mapping = self._ensure_mapping(parsed, path=path)
|
|
409
454
|
_log_file_loaded(path, "yaml")
|
|
410
|
-
return
|
|
455
|
+
return mapping
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _parse_yaml_bytes(payload: bytes, module: ModuleType, path: str) -> object:
|
|
459
|
+
"""Turn YAML bytes into a Python shape that mirrors the file.
|
|
460
|
+
|
|
461
|
+
Why
|
|
462
|
+
----
|
|
463
|
+
Normalise the PyYAML parsing contract so callers always receive a mapping,
|
|
464
|
+
raising a domain-specific error when the parser signals invalid syntax.
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
payload:
|
|
469
|
+
Raw YAML document supplied as bytes.
|
|
470
|
+
module:
|
|
471
|
+
PyYAML module providing ::func:`safe_load` and the ``YAMLError`` base class.
|
|
472
|
+
path:
|
|
473
|
+
Source identifier used to enrich error messages.
|
|
474
|
+
|
|
475
|
+
Returns
|
|
476
|
+
-------
|
|
477
|
+
object
|
|
478
|
+
Parsed document; an empty dict when the YAML payload evaluates to ``None``.
|
|
479
|
+
|
|
480
|
+
Raises
|
|
481
|
+
------
|
|
482
|
+
InvalidFormat
|
|
483
|
+
When PyYAML raises ``YAMLError`` while parsing the payload.
|
|
484
|
+
|
|
485
|
+
Examples
|
|
486
|
+
--------
|
|
487
|
+
>>> from types import SimpleNamespace
|
|
488
|
+
>>> fake = SimpleNamespace(safe_load=lambda data: {"key": data.decode("utf-8")}, YAMLError=Exception)
|
|
489
|
+
>>> _parse_yaml_bytes(b"value", fake, "memory.yaml") # doctest: +ELLIPSIS
|
|
490
|
+
{'key': 'value'}
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
document = module.safe_load(payload)
|
|
495
|
+
except module.YAMLError as exc: # type: ignore[attr-defined]
|
|
496
|
+
_raise_invalid_format(path, "yaml", exc)
|
|
497
|
+
return {} if document is None else document
|
|
@@ -2,31 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Callable, Iterable, Mapping, Optional, Sequence, cast
|
|
6
|
-
|
|
7
|
-
from importlib import metadata as _metadata
|
|
8
|
-
|
|
9
5
|
from pathlib import Path
|
|
6
|
+
from typing import Callable, Iterable, Mapping, Optional, Sequence, cast
|
|
10
7
|
|
|
11
|
-
import lib_cli_exit_tools
|
|
12
8
|
import rich_click as click
|
|
9
|
+
from lib_cli_exit_tools import cli_session
|
|
13
10
|
|
|
14
|
-
from ..
|
|
11
|
+
from ..application.ports import SourceInfoPayload
|
|
15
12
|
from .common import (
|
|
13
|
+
describe_distribution,
|
|
16
14
|
format_scalar,
|
|
17
15
|
json_paths,
|
|
18
|
-
load_distribution_metadata,
|
|
19
16
|
normalise_examples_platform_option,
|
|
20
17
|
normalise_platform_option,
|
|
21
18
|
normalise_prefer,
|
|
22
19
|
render_human,
|
|
23
|
-
toggle_traceback,
|
|
24
20
|
version_string,
|
|
25
21
|
)
|
|
26
22
|
from .constants import CLICK_CONTEXT_SETTINGS, TRACEBACK_SUMMARY, TRACEBACK_VERBOSE
|
|
27
|
-
from ..application.ports import SourceInfoPayload
|
|
28
|
-
|
|
29
|
-
|
|
30
23
|
from .read import read_command as cli_read_config, read_json_command as cli_read_config_json
|
|
31
24
|
|
|
32
25
|
|
|
@@ -46,33 +39,51 @@ from .read import read_command as cli_read_config, read_json_command as cli_read
|
|
|
46
39
|
default=False,
|
|
47
40
|
help="Show full Python traceback on errors",
|
|
48
41
|
)
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
@click.pass_context
|
|
43
|
+
def cli(ctx: click.Context, traceback: bool) -> None:
|
|
44
|
+
"""Root command storing the requested traceback preference."""
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
ctx.ensure_object(dict)
|
|
47
|
+
ctx.obj["traceback"] = traceback
|
|
53
48
|
|
|
54
49
|
|
|
55
50
|
def main(argv: Optional[Sequence[str]] = None, *, restore_traceback: bool = True) -> int:
|
|
56
|
-
"""Entry point
|
|
51
|
+
"""Entry point wiring the CLI through ``lib_cli_exit_tools.cli_session``."""
|
|
52
|
+
|
|
53
|
+
args_list = list(argv) if argv is not None else None
|
|
54
|
+
overrides = _session_overrides(args_list)
|
|
55
|
+
|
|
56
|
+
with cli_session(
|
|
57
|
+
summary_limit=TRACEBACK_SUMMARY,
|
|
58
|
+
verbose_limit=TRACEBACK_VERBOSE,
|
|
59
|
+
overrides=overrides or None,
|
|
60
|
+
restore=restore_traceback,
|
|
61
|
+
) as run:
|
|
62
|
+
runner = cast("Callable[..., int]", run)
|
|
63
|
+
return runner(
|
|
64
|
+
cli,
|
|
65
|
+
argv=args_list,
|
|
66
|
+
prog_name="lib_layered_config",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _session_overrides(argv: Sequence[str] | None) -> dict[str, object]:
|
|
71
|
+
"""Derive configuration overrides for ``cli_session`` based on CLI args."""
|
|
72
|
+
|
|
73
|
+
if not argv:
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
ctx = cli.make_context("lib_layered_config", list(argv), resilient_parsing=True)
|
|
78
|
+
except click.ClickException:
|
|
79
|
+
return {}
|
|
57
80
|
|
|
58
|
-
previous_traceback = getattr(lib_cli_exit_tools.config, "traceback", False)
|
|
59
|
-
previous_force_color = getattr(lib_cli_exit_tools.config, "traceback_force_color", False)
|
|
60
81
|
try:
|
|
61
|
-
|
|
62
|
-
run_cli = cast(Callable[..., int], lib_cli_exit_tools.run_cli) # pyright: ignore[reportUnknownMemberType]
|
|
63
|
-
return run_cli(cli, argv=list(argv) if argv is not None else None, prog_name="lib_layered_config")
|
|
64
|
-
except BaseException as exc: # noqa: BLE001
|
|
65
|
-
print_exception = cast(Callable[..., None], lib_cli_exit_tools.print_exception_message) # pyright: ignore[reportUnknownMemberType]
|
|
66
|
-
print_exception(
|
|
67
|
-
trace_back=lib_cli_exit_tools.config.traceback,
|
|
68
|
-
length_limit=TRACEBACK_VERBOSE if lib_cli_exit_tools.config.traceback else TRACEBACK_SUMMARY,
|
|
69
|
-
)
|
|
70
|
-
exit_code_fn = cast(Callable[[BaseException], int], lib_cli_exit_tools.get_system_exit_code) # pyright: ignore[reportUnknownMemberType]
|
|
71
|
-
return exit_code_fn(exc)
|
|
82
|
+
enabled = bool(ctx.params.get("traceback", False))
|
|
72
83
|
finally:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
ctx.close()
|
|
85
|
+
|
|
86
|
+
return {"traceback": enabled} if enabled else {}
|
|
76
87
|
|
|
77
88
|
|
|
78
89
|
def _register_commands() -> None:
|
|
@@ -84,8 +95,6 @@ def _register_commands() -> None:
|
|
|
84
95
|
|
|
85
96
|
_register_commands()
|
|
86
97
|
|
|
87
|
-
metadata = _metadata
|
|
88
|
-
_toggle_traceback = toggle_traceback
|
|
89
98
|
_version_string = version_string
|
|
90
99
|
|
|
91
100
|
|
|
@@ -113,44 +122,17 @@ def _normalise_prefer(values: Sequence[str]) -> tuple[str, ...] | None: # pyrig
|
|
|
113
122
|
return normalise_prefer(values)
|
|
114
123
|
|
|
115
124
|
|
|
116
|
-
def _load_distribution_metadata() -> _metadata.PackageMetadata | None:
|
|
117
|
-
"""Wrapper that allows tests to monkeypatch metadata loading."""
|
|
118
|
-
|
|
119
|
-
return load_distribution_metadata()
|
|
120
|
-
|
|
121
|
-
|
|
122
125
|
def _describe_distribution() -> tuple[str, ...]:
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
meta = _load_distribution_metadata()
|
|
126
|
-
if meta is None:
|
|
127
|
-
return ("lib_layered_config (metadata unavailable)",)
|
|
128
|
-
|
|
129
|
-
lines = [f"Info for {meta.get('Name', 'lib_layered_config')}:"]
|
|
130
|
-
lines.append(f" Version : {meta.get('Version', version_string())}")
|
|
131
|
-
lines.append(f" Requires-Python : {meta.get('Requires-Python', '>=3.13')}")
|
|
132
|
-
summary = meta.get("Summary")
|
|
133
|
-
if summary:
|
|
134
|
-
lines.append(f" Summary : {summary}")
|
|
135
|
-
|
|
136
|
-
def _no_urls(_: str) -> Iterable[str] | None:
|
|
137
|
-
return None
|
|
126
|
+
"""Expose CLI metadata lines for backwards-compatible tests."""
|
|
138
127
|
|
|
139
|
-
|
|
140
|
-
for entry in get_all("Project-URL") or []:
|
|
141
|
-
lines.append(f" {entry}")
|
|
142
|
-
return tuple(lines)
|
|
128
|
+
return tuple(describe_distribution())
|
|
143
129
|
|
|
144
130
|
|
|
145
131
|
__all__ = [
|
|
146
132
|
"cli",
|
|
147
133
|
"main",
|
|
148
|
-
"_default_env_prefix",
|
|
149
|
-
"metadata",
|
|
150
|
-
"_toggle_traceback",
|
|
151
134
|
"_version_string",
|
|
152
135
|
"_describe_distribution",
|
|
153
|
-
"_load_distribution_metadata",
|
|
154
136
|
"_normalise_platform",
|
|
155
137
|
"_normalise_examples_platform",
|
|
156
138
|
"_json_paths",
|
lib_layered_config/cli/common.py
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
"""Utilities shared by CLI command modules.
|
|
1
|
+
"""Utilities shared by CLI command modules.
|
|
2
|
+
|
|
3
|
+
Purpose
|
|
4
|
+
-------
|
|
5
|
+
Tell the CLI story in small, declarative helpers so commands remain tiny. These
|
|
6
|
+
functions construct read queries, choose output modes, format human summaries,
|
|
7
|
+
and surface metadata drawn from ``__init__conf__``.
|
|
8
|
+
|
|
9
|
+
Contents
|
|
10
|
+
--------
|
|
11
|
+
* :class:`ReadQuery` — frozen bundle capturing the parameters for configuration reads.
|
|
12
|
+
* Metadata helpers (:func:`version_string`, :func:`describe_distribution`).
|
|
13
|
+
* Query shaping (:func:`build_read_query`, :func:`normalise_prefer`, :func:`stringify`).
|
|
14
|
+
* Output shaping (:func:`json_payload`, :func:`human_payload`, :func:`render_human`).
|
|
15
|
+
* Human-friendly utilities (:func:`format_scalar`, :func:`json_paths`).
|
|
16
|
+
|
|
17
|
+
System Role
|
|
18
|
+
-----------
|
|
19
|
+
Commands import these helpers to stay declarative. They rely on the application
|
|
20
|
+
layer (`read_config*` functions) and on platform utilities for normalisation.
|
|
21
|
+
Updates here must be mirrored in ``docs/systemdesign/module_reference.md`` to
|
|
22
|
+
keep documentation and behaviour aligned.
|
|
23
|
+
"""
|
|
2
24
|
|
|
3
25
|
from __future__ import annotations
|
|
4
26
|
|
|
5
27
|
import json
|
|
6
28
|
from dataclasses import dataclass
|
|
7
|
-
from importlib import metadata
|
|
8
29
|
from pathlib import Path
|
|
9
|
-
from typing import Iterable, Mapping, Optional, Sequence, cast
|
|
30
|
+
from typing import Iterable, Mapping, Optional, Protocol, Sequence, cast
|
|
10
31
|
|
|
11
|
-
import lib_cli_exit_tools
|
|
12
32
|
import rich_click as click
|
|
13
33
|
|
|
34
|
+
from .. import __init__conf__
|
|
14
35
|
from .._platform import normalise_examples_platform as _normalise_examples_platform
|
|
15
36
|
from .._platform import normalise_resolver_platform as _normalise_resolver_platform
|
|
16
37
|
from ..application.ports import SourceInfoPayload
|
|
@@ -19,9 +40,47 @@ from .constants import DEFAULT_JSON_INDENT
|
|
|
19
40
|
from ..core import read_config, read_config_json, read_config_raw
|
|
20
41
|
|
|
21
42
|
|
|
22
|
-
|
|
43
|
+
class _PackageMetadata(Protocol):
|
|
44
|
+
name: str
|
|
45
|
+
title: str
|
|
46
|
+
version: str
|
|
47
|
+
homepage: str
|
|
48
|
+
author: str
|
|
49
|
+
author_email: str
|
|
50
|
+
shell_command: str
|
|
51
|
+
|
|
52
|
+
def info_lines(self) -> tuple[str, ...]: ...
|
|
53
|
+
|
|
54
|
+
def metadata_fields(self) -> tuple[tuple[str, str], ...]: ...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
package_metadata: _PackageMetadata = cast(_PackageMetadata, __init__conf__)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True, slots=True)
|
|
23
61
|
class ReadQuery:
|
|
24
|
-
"""Immutable bundle of parameters required to execute read commands.
|
|
62
|
+
"""Immutable bundle of parameters required to execute read commands.
|
|
63
|
+
|
|
64
|
+
Why
|
|
65
|
+
----
|
|
66
|
+
Capture CLI parameters in a frozen dataclass so functions can accept a
|
|
67
|
+
self-explanatory object rather than many loose arguments.
|
|
68
|
+
|
|
69
|
+
Attributes
|
|
70
|
+
----------
|
|
71
|
+
vendor:
|
|
72
|
+
Vendor namespace requested by the user.
|
|
73
|
+
app:
|
|
74
|
+
Application identifier within the vendor namespace.
|
|
75
|
+
slug:
|
|
76
|
+
Configuration slug (environment/project).
|
|
77
|
+
prefer:
|
|
78
|
+
Ordered tuple of preferred file extensions, lowercased; ``None`` when the CLI falls back to defaults.
|
|
79
|
+
start_dir:
|
|
80
|
+
Starting directory as a string or ``None`` to use the current working directory.
|
|
81
|
+
default_file:
|
|
82
|
+
Optional baseline configuration file to load before layered overrides.
|
|
83
|
+
"""
|
|
25
84
|
|
|
26
85
|
vendor: str
|
|
27
86
|
app: str
|
|
@@ -31,46 +90,41 @@ class ReadQuery:
|
|
|
31
90
|
default_file: str | None
|
|
32
91
|
|
|
33
92
|
|
|
34
|
-
def
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
lib_cli_exit_tools.config.traceback = show
|
|
38
|
-
lib_cli_exit_tools.config.traceback_force_color = show
|
|
93
|
+
def version_string() -> str:
|
|
94
|
+
"""Echo the project version declared in ``__init__conf__``.
|
|
39
95
|
|
|
96
|
+
Why
|
|
97
|
+
----
|
|
98
|
+
The CLI `--version` option should reflect the single source of truth
|
|
99
|
+
maintained by release automation.
|
|
40
100
|
|
|
41
|
-
|
|
42
|
-
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
str
|
|
104
|
+
Semantic version string from the generated metadata module.
|
|
105
|
+
"""
|
|
43
106
|
|
|
44
|
-
|
|
45
|
-
return metadata.version("lib_layered_config")
|
|
46
|
-
except metadata.PackageNotFoundError:
|
|
47
|
-
return "0.0.0"
|
|
107
|
+
return package_metadata.version
|
|
48
108
|
|
|
49
109
|
|
|
50
110
|
def describe_distribution() -> Iterable[str]:
|
|
51
|
-
"""Yield human-readable metadata lines
|
|
52
|
-
|
|
53
|
-
meta = load_distribution_metadata()
|
|
54
|
-
if meta is None:
|
|
55
|
-
yield "lib_layered_config (metadata unavailable)"
|
|
56
|
-
return
|
|
57
|
-
yield f"Info for {meta.get('Name', 'lib_layered_config')}:"
|
|
58
|
-
yield f" Version : {meta.get('Version', version_string())}"
|
|
59
|
-
yield f" Requires-Python : {meta.get('Requires-Python', '>=3.13')}"
|
|
60
|
-
summary = meta.get("Summary")
|
|
61
|
-
if summary:
|
|
62
|
-
yield f" Summary : {summary}"
|
|
63
|
-
for entry in meta.get_all("Project-URL") or []:
|
|
64
|
-
yield f" {entry}"
|
|
111
|
+
"""Yield human-readable metadata lines sourced from ``__init__conf__``.
|
|
65
112
|
|
|
113
|
+
Why
|
|
114
|
+
----
|
|
115
|
+
Support the `info` command with pre-formatted lines so the CLI stays thin.
|
|
66
116
|
|
|
67
|
-
|
|
68
|
-
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
Iterable[str]
|
|
120
|
+
Sequence of descriptive lines suitable for printing with ``click.echo``.
|
|
121
|
+
"""
|
|
69
122
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
123
|
+
lines_provider = getattr(package_metadata, "info_lines", None)
|
|
124
|
+
if callable(lines_provider):
|
|
125
|
+
yield from cast(Iterable[str], lines_provider())
|
|
126
|
+
return
|
|
127
|
+
yield from _fallback_info_lines()
|
|
74
128
|
|
|
75
129
|
|
|
76
130
|
def build_read_query(
|
|
@@ -81,7 +135,28 @@ def build_read_query(
|
|
|
81
135
|
start_dir: Optional[Path],
|
|
82
136
|
default_file: Optional[Path],
|
|
83
137
|
) -> ReadQuery:
|
|
84
|
-
"""Shape CLI parameters into a
|
|
138
|
+
"""Shape CLI parameters into a :class:`ReadQuery`.
|
|
139
|
+
|
|
140
|
+
Why
|
|
141
|
+
----
|
|
142
|
+
Centralise normalisation so every command builds queries in the same way.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
vendor, app, slug:
|
|
147
|
+
Raw CLI strings describing the configuration slice to read.
|
|
148
|
+
prefer:
|
|
149
|
+
List of extensions supplied via ``--prefer`` (possibly empty).
|
|
150
|
+
start_dir:
|
|
151
|
+
Optional explicit starting directory.
|
|
152
|
+
default_file:
|
|
153
|
+
Optional explicit baseline file.
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
ReadQuery
|
|
158
|
+
Frozen, normalised dataclass instance.
|
|
159
|
+
"""
|
|
85
160
|
|
|
86
161
|
return ReadQuery(
|
|
87
162
|
vendor=vendor,
|
|
@@ -94,7 +169,13 @@ def build_read_query(
|
|
|
94
169
|
|
|
95
170
|
|
|
96
171
|
def normalise_prefer(values: Sequence[str]) -> tuple[str, ...] | None:
|
|
97
|
-
"""
|
|
172
|
+
"""Normalise preferred extensions by lowercasing and trimming dots.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
tuple[str, ...] | None
|
|
177
|
+
Tuple of cleaned extensions, or ``None`` when no values were supplied.
|
|
178
|
+
"""
|
|
98
179
|
|
|
99
180
|
if not values:
|
|
100
181
|
return None
|
|
@@ -102,13 +183,33 @@ def normalise_prefer(values: Sequence[str]) -> tuple[str, ...] | None:
|
|
|
102
183
|
|
|
103
184
|
|
|
104
185
|
def normalise_targets(values: Sequence[str]) -> tuple[str, ...]:
|
|
105
|
-
"""Normalise deployment targets to lowercase for resolver routing.
|
|
186
|
+
"""Normalise deployment targets to lowercase for resolver routing.
|
|
187
|
+
|
|
188
|
+
Why
|
|
189
|
+
----
|
|
190
|
+
Deployment helpers expect stable lowercase slugs regardless of user input.
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
tuple[str, ...]
|
|
195
|
+
Lowercased targets suitable for lookups.
|
|
196
|
+
"""
|
|
106
197
|
|
|
107
198
|
return tuple(value.lower() for value in values)
|
|
108
199
|
|
|
109
200
|
|
|
110
201
|
def normalise_platform_option(value: Optional[str]) -> Optional[str]:
|
|
111
|
-
"""Map
|
|
202
|
+
"""Map friendly platform aliases to canonical resolver identifiers.
|
|
203
|
+
|
|
204
|
+
Why
|
|
205
|
+
----
|
|
206
|
+
Keep command options flexible without leaking resolver-specific tokens.
|
|
207
|
+
|
|
208
|
+
Raises
|
|
209
|
+
------
|
|
210
|
+
click.BadParameter
|
|
211
|
+
When the alias is unrecognised.
|
|
212
|
+
"""
|
|
112
213
|
|
|
113
214
|
try:
|
|
114
215
|
return _normalise_resolver_platform(value)
|
|
@@ -117,7 +218,18 @@ def normalise_platform_option(value: Optional[str]) -> Optional[str]:
|
|
|
117
218
|
|
|
118
219
|
|
|
119
220
|
def normalise_examples_platform_option(value: Optional[str]) -> Optional[str]:
|
|
120
|
-
"""Map example-generation platform aliases to canonical values.
|
|
221
|
+
"""Map example-generation platform aliases to canonical values.
|
|
222
|
+
|
|
223
|
+
Why
|
|
224
|
+
----
|
|
225
|
+
Example templates use only ``posix`` or ``windows``; synonyms must collapse
|
|
226
|
+
to those keys.
|
|
227
|
+
|
|
228
|
+
Raises
|
|
229
|
+
------
|
|
230
|
+
click.BadParameter
|
|
231
|
+
When the alias is unrecognised.
|
|
232
|
+
"""
|
|
121
233
|
|
|
122
234
|
try:
|
|
123
235
|
return _normalise_examples_platform(value)
|
|
@@ -126,25 +238,60 @@ def normalise_examples_platform_option(value: Optional[str]) -> Optional[str]:
|
|
|
126
238
|
|
|
127
239
|
|
|
128
240
|
def stringify(path: Optional[Path]) -> Optional[str]:
|
|
129
|
-
"""Return
|
|
241
|
+
"""Return an absolute path string or ``None`` when the input is ``None``.
|
|
242
|
+
|
|
243
|
+
Why
|
|
244
|
+
----
|
|
245
|
+
Downstream helpers prefer plain strings (for JSON serialization) while
|
|
246
|
+
preserving the absence of a path.
|
|
247
|
+
"""
|
|
130
248
|
|
|
131
249
|
return None if path is None else str(path)
|
|
132
250
|
|
|
133
251
|
|
|
134
252
|
def wants_json(output_format: str) -> bool:
|
|
135
|
-
"""
|
|
253
|
+
"""State plainly whether the caller requested JSON output.
|
|
254
|
+
|
|
255
|
+
Why
|
|
256
|
+
----
|
|
257
|
+
Commands toggle between human and JSON representations; clarity matters.
|
|
258
|
+
"""
|
|
136
259
|
|
|
137
260
|
return output_format.strip().lower() == "json"
|
|
138
261
|
|
|
139
262
|
|
|
140
263
|
def resolve_indent(enabled: bool) -> int | None:
|
|
141
|
-
"""Return default JSON indentation when
|
|
264
|
+
"""Return the default JSON indentation when pretty-printing is enabled.
|
|
265
|
+
|
|
266
|
+
Why
|
|
267
|
+
----
|
|
268
|
+
Provide a single source for the CLI's JSON formatting decision.
|
|
269
|
+
"""
|
|
142
270
|
|
|
143
271
|
return DEFAULT_JSON_INDENT if enabled else None
|
|
144
272
|
|
|
145
273
|
|
|
146
274
|
def json_payload(query: ReadQuery, indent: int | None, include_provenance: bool) -> str:
|
|
147
|
-
"""Build JSON payload for
|
|
275
|
+
"""Build a JSON payload for the provided query.
|
|
276
|
+
|
|
277
|
+
Why
|
|
278
|
+
----
|
|
279
|
+
Commands should share the same logic when emitting machine-readable output.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
query:
|
|
284
|
+
Normalised read parameters.
|
|
285
|
+
indent:
|
|
286
|
+
Indentation width or ``None`` for compact output.
|
|
287
|
+
include_provenance:
|
|
288
|
+
When ``True`` use :func:`read_config_json` to include source metadata.
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
str
|
|
293
|
+
JSON document ready for ``click.echo``.
|
|
294
|
+
"""
|
|
148
295
|
|
|
149
296
|
if include_provenance:
|
|
150
297
|
return read_config_json(
|
|
@@ -168,7 +315,20 @@ def json_payload(query: ReadQuery, indent: int | None, include_provenance: bool)
|
|
|
168
315
|
|
|
169
316
|
|
|
170
317
|
def render_human(data: Mapping[str, object], provenance: Mapping[str, SourceInfoPayload]) -> str:
|
|
171
|
-
"""
|
|
318
|
+
"""Render configuration values and provenance as friendly prose.
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
data:
|
|
323
|
+
Nested mapping of configuration values.
|
|
324
|
+
provenance:
|
|
325
|
+
Mapping of dotted keys to source metadata.
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
str
|
|
330
|
+
Multi-line description highlighting value and origin.
|
|
331
|
+
"""
|
|
172
332
|
|
|
173
333
|
entries = list(iter_leaf_items(data))
|
|
174
334
|
if not entries:
|
|
@@ -185,7 +345,12 @@ def render_human(data: Mapping[str, object], provenance: Mapping[str, SourceInfo
|
|
|
185
345
|
|
|
186
346
|
|
|
187
347
|
def iter_leaf_items(mapping: Mapping[str, object], prefix: tuple[str, ...] = ()) -> Iterable[tuple[str, object]]:
|
|
188
|
-
"""Yield dotted paths and values for every leaf
|
|
348
|
+
"""Yield dotted paths and values for every leaf node in *mapping*.
|
|
349
|
+
|
|
350
|
+
Why
|
|
351
|
+
----
|
|
352
|
+
Flatten nested structures so human-readable output can focus on leaves.
|
|
353
|
+
"""
|
|
189
354
|
|
|
190
355
|
for key, value in mapping.items():
|
|
191
356
|
dotted = ".".join((*prefix, key))
|
|
@@ -197,7 +362,13 @@ def iter_leaf_items(mapping: Mapping[str, object], prefix: tuple[str, ...] = ())
|
|
|
197
362
|
|
|
198
363
|
|
|
199
364
|
def format_scalar(value: object) -> str:
|
|
200
|
-
"""
|
|
365
|
+
"""Format a scalar value for human output.
|
|
366
|
+
|
|
367
|
+
Why
|
|
368
|
+
----
|
|
369
|
+
Keep representation consistent across CLI messages (booleans lowercase,
|
|
370
|
+
``None`` as ``null``).
|
|
371
|
+
"""
|
|
201
372
|
|
|
202
373
|
if isinstance(value, bool):
|
|
203
374
|
return "true" if value else "false"
|
|
@@ -207,13 +378,23 @@ def format_scalar(value: object) -> str:
|
|
|
207
378
|
|
|
208
379
|
|
|
209
380
|
def json_paths(paths: Iterable[Path]) -> str:
|
|
210
|
-
"""Return JSON array of stringified paths written by helper commands.
|
|
381
|
+
"""Return a JSON array of stringified paths written by helper commands.
|
|
382
|
+
|
|
383
|
+
Why
|
|
384
|
+
----
|
|
385
|
+
Provide machine-readable artifacts for deployment/generation commands.
|
|
386
|
+
"""
|
|
211
387
|
|
|
212
388
|
return json.dumps([str(path) for path in paths], indent=2)
|
|
213
389
|
|
|
214
390
|
|
|
215
391
|
def human_payload(query: ReadQuery) -> str:
|
|
216
|
-
"""Return prose describing config values and provenance.
|
|
392
|
+
"""Return prose describing config values and provenance.
|
|
393
|
+
|
|
394
|
+
Why
|
|
395
|
+
----
|
|
396
|
+
Offer a human-first view that mirrors the JSON content yet remains readable.
|
|
397
|
+
"""
|
|
217
398
|
|
|
218
399
|
data, meta = read_config_raw(
|
|
219
400
|
vendor=query.vendor,
|
|
@@ -227,6 +408,33 @@ def human_payload(query: ReadQuery) -> str:
|
|
|
227
408
|
|
|
228
409
|
|
|
229
410
|
def default_env_prefix(slug: str) -> str:
|
|
230
|
-
"""Expose the canonical environment prefix for CLI/commands.
|
|
411
|
+
"""Expose the canonical environment prefix for CLI/commands.
|
|
412
|
+
|
|
413
|
+
Why
|
|
414
|
+
----
|
|
415
|
+
Sustain backward compatibility for callers that relied on the CLI proxy.
|
|
416
|
+
"""
|
|
231
417
|
|
|
232
418
|
return compute_default_env_prefix(slug)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _fallback_info_lines() -> tuple[str, ...]:
|
|
422
|
+
"""Construct info lines from metadata constants when helpers are absent."""
|
|
423
|
+
|
|
424
|
+
fields_provider = getattr(package_metadata, "metadata_fields", None)
|
|
425
|
+
if callable(fields_provider):
|
|
426
|
+
fields = cast(tuple[tuple[str, str], ...], fields_provider())
|
|
427
|
+
else:
|
|
428
|
+
fields: tuple[tuple[str, str], ...] = (
|
|
429
|
+
("name", package_metadata.name),
|
|
430
|
+
("title", package_metadata.title),
|
|
431
|
+
("version", package_metadata.version),
|
|
432
|
+
("homepage", package_metadata.homepage),
|
|
433
|
+
("author", package_metadata.author),
|
|
434
|
+
("author_email", package_metadata.author_email),
|
|
435
|
+
("shell_command", package_metadata.shell_command),
|
|
436
|
+
)
|
|
437
|
+
pad = max(len(label) for label, _ in fields)
|
|
438
|
+
lines = [f"Info for {package_metadata.name}:", ""]
|
|
439
|
+
lines.extend(f" {label.ljust(pad)} = {value}" for label, value in fields)
|
|
440
|
+
return tuple(lines)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lib_layered_config
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Cross-platform layered configuration loader for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/bitranox/lib_layered_config
|
|
6
6
|
Project-URL: Repository, https://github.com/bitranox/lib_layered_config.git
|
|
@@ -16,26 +16,26 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
Requires-Python: >=3.13
|
|
19
|
-
Requires-Dist: lib-cli-exit-tools>=1.
|
|
20
|
-
Requires-Dist: rich-click>=1.9.
|
|
19
|
+
Requires-Dist: lib-cli-exit-tools>=2.1.0
|
|
20
|
+
Requires-Dist: rich-click>=1.9.3
|
|
21
21
|
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: bandit>=1.
|
|
23
|
-
Requires-Dist: build>=1.3; extra == 'dev'
|
|
24
|
-
Requires-Dist: codecov-cli>=
|
|
22
|
+
Requires-Dist: bandit>=1.8.6; extra == 'dev'
|
|
23
|
+
Requires-Dist: build>=1.3.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: codecov-cli>=11.2.3; extra == 'dev'
|
|
25
25
|
Requires-Dist: coverage[toml]>=7.10.7; extra == 'dev'
|
|
26
26
|
Requires-Dist: hypothesis>=6.140.3; extra == 'dev'
|
|
27
|
-
Requires-Dist: import-linter>=2.
|
|
28
|
-
Requires-Dist: pip-audit>=2.
|
|
29
|
-
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
30
|
-
Requires-Dist: pytest-asyncio>=0
|
|
31
|
-
Requires-Dist: pytest-cov>=7.0; extra == 'dev'
|
|
32
|
-
Requires-Dist: pytest>=8.4; extra == 'dev'
|
|
33
|
-
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
34
|
-
Requires-Dist: ruff>=0.
|
|
35
|
-
Requires-Dist: textual>=6.
|
|
36
|
-
Requires-Dist: twine>=6.2; extra == 'dev'
|
|
27
|
+
Requires-Dist: import-linter>=2.5.2; extra == 'dev'
|
|
28
|
+
Requires-Dist: pip-audit>=2.9.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pyright>=1.1.406; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=1.2.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.4.2; extra == 'dev'
|
|
33
|
+
Requires-Dist: pyyaml>=6.0.3; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.14.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: textual>=6.3.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: twine>=6.2.0; extra == 'dev'
|
|
37
37
|
Provides-Extra: yaml
|
|
38
|
-
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
|
|
38
|
+
Requires-Dist: pyyaml>=6.0.3; extra == 'yaml'
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
|
|
41
41
|
# lib_layered_config
|
|
@@ -342,6 +342,10 @@ make build # build wheel / sdist artifacts
|
|
|
342
342
|
make run -- --help # run the CLI via the repo entrypoint
|
|
343
343
|
```
|
|
344
344
|
|
|
345
|
+
The development extra now targets the latest stable releases of the toolchain
|
|
346
|
+
(pytest 8.4.2, ruff 0.14.0, codecov-cli 11.2.3, etc.), so upgrading your local
|
|
347
|
+
environment before running `make` is recommended.
|
|
348
|
+
|
|
345
349
|
*Formatting gate:* Ruff formatting runs in check mode during `make test`. Run `ruff format .` (or `pre-commit run --all-files`) before pushing and consider `pre-commit install` to keep local edits aligned.
|
|
346
350
|
|
|
347
351
|
*Coverage gate:* the maintained test suite must stay ≥90% (see `pyproject.toml`). Add targeted unit tests if you extend functionality.
|
|
@@ -357,7 +361,8 @@ The GitHub Actions workflow executes three jobs:
|
|
|
357
361
|
|
|
358
362
|
- **Test matrix** (Linux/macOS/Windows, Python 3.13 + latest 3.x) running the same pipeline as `make test`.
|
|
359
363
|
- **pipx / uv verification** to prove the built wheel installs cleanly with the common Python app launchers.
|
|
360
|
-
- **Notebook smoke test** that executes `notebooks/Quickstart.ipynb` to keep the tutorial in sync.
|
|
364
|
+
- **Notebook smoke test** that executes `notebooks/Quickstart.ipynb` to keep the tutorial in sync using the native nbformat workflow (no compatibility shims required).
|
|
365
|
+
- CLI jobs run through `lib_cli_exit_tools.cli_session`, ensuring the `--traceback` flag behaves the same locally and in automation.
|
|
361
366
|
|
|
362
367
|
Packaging-specific jobs (conda, Nix, Homebrew sync) were retired; the Python packaging metadata in `pyproject.toml` remains the single source of truth.
|
|
363
368
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
lib_layered_config/__init__.py,sha256=nJZkUMWFr_LhE71vquCUojqIy8miwo_T4WexbRRSA0o,1578
|
|
2
|
+
lib_layered_config/__init__conf__.py,sha256=NEGCiwr_pri2exixloo6pWs893u_oTKyTXYIMvDvuKY,2154
|
|
2
3
|
lib_layered_config/__main__.py,sha256=8zOwFdtTW_y8WBSefP1qYr99x3MWp6JOsTA1E0n-ceY,467
|
|
3
4
|
lib_layered_config/_layers.py,sha256=21a_6pLIM2Hb-N54BAXVQxzbhmnNNXWNnJppKHl6ht4,13334
|
|
4
5
|
lib_layered_config/_platform.py,sha256=eIvp_P8Cn-0meIzkxgZEQpKdgA9yU-fUYYKkp0Af1Qo,5819
|
|
@@ -12,14 +13,14 @@ lib_layered_config/adapters/dotenv/default.py,sha256=ghsgSgw99k14aQxPoXXsVk_oE_m
|
|
|
12
13
|
lib_layered_config/adapters/env/__init__.py,sha256=EZY49s8TGhHvPWfEleRCIRc7W2eeAArpZPkaGTxUXX8,110
|
|
13
14
|
lib_layered_config/adapters/env/default.py,sha256=r0SCfn24NxhVgmn5gFX07bPtTFTw-kk9AWtX0-b5rtI,13136
|
|
14
15
|
lib_layered_config/adapters/file_loaders/__init__.py,sha256=V4KQJvY7xKJRMnY0cktkwj7SOILS6hO3eAfVBHP5B1k,45
|
|
15
|
-
lib_layered_config/adapters/file_loaders/structured.py,sha256=
|
|
16
|
+
lib_layered_config/adapters/file_loaders/structured.py,sha256=eDq2o2_V2vObBq8X0hitjDrhCv8rXMMmyVcaLw998s0,13444
|
|
16
17
|
lib_layered_config/adapters/path_resolvers/__init__.py,sha256=-SEC9-2kwEZcW4hOrVbdfpMqmPKQ9UlI_PgvxLZ9m8I,65
|
|
17
18
|
lib_layered_config/adapters/path_resolvers/default.py,sha256=7JE6xHuiplwqXptsH30m_q5PmqRHZMi7YQ8nuQSU5mk,21358
|
|
18
19
|
lib_layered_config/application/__init__.py,sha256=fGbDqRVtb6tIXhdjYwL22548P1opLgau8BkzmXs_KiA,276
|
|
19
20
|
lib_layered_config/application/merge.py,sha256=ZpRRQoLhFBcmubCXeXpKbOCFse1w_2GljXuCnCR-VTQ,12536
|
|
20
21
|
lib_layered_config/application/ports.py,sha256=8HyqTTVlFxZurhjR805tmEV1D3MdzZdTXbjfUTSuoac,3047
|
|
21
|
-
lib_layered_config/cli/__init__.py,sha256=
|
|
22
|
-
lib_layered_config/cli/common.py,sha256=
|
|
22
|
+
lib_layered_config/cli/__init__.py,sha256=Br31K5W-afy2e-1ZIcqyro-vrbbEc-WjY2SPDhLJUbc,4060
|
|
23
|
+
lib_layered_config/cli/common.py,sha256=IUrYoT9Iyn46SDJymKrdrU2wkZVeZyF7JwnYE-zRrBM,12449
|
|
23
24
|
lib_layered_config/cli/constants.py,sha256=15Cu0OfkP-iOmHTecKEDLK4YShYxgMeSWNKThAZV4DA,467
|
|
24
25
|
lib_layered_config/cli/deploy.py,sha256=ZhxUbMx1rjWLwNTVDx8nfM63H7-6GFp60S6_zl2vGGg,2025
|
|
25
26
|
lib_layered_config/cli/fail.py,sha256=PBuHrTvSdZ1mv-UGlZYbWUF24ii09IzQYnHqN1GR6JE,523
|
|
@@ -31,9 +32,9 @@ lib_layered_config/domain/config.py,sha256=j6L9Igxyxi9emW3wlqLDZWhdNhB0R-L06DuGs
|
|
|
31
32
|
lib_layered_config/domain/errors.py,sha256=811KWV98R8eF142ReSYjKZ2LXCQdys75y3SpKwWAn3E,1929
|
|
32
33
|
lib_layered_config/examples/__init__.py,sha256=1ShHdxvsxIxWzRAYfOxaIeL2m9Je32B2I9bX8MUwXHw,996
|
|
33
34
|
lib_layered_config/examples/deploy.py,sha256=1aHXsIzw9L94dXgi8xr_rTshaDUpezKV_csU81nkx6w,11211
|
|
34
|
-
lib_layered_config/examples/generate.py,sha256=
|
|
35
|
-
lib_layered_config-1.
|
|
36
|
-
lib_layered_config-1.
|
|
37
|
-
lib_layered_config-1.
|
|
38
|
-
lib_layered_config-1.
|
|
39
|
-
lib_layered_config-1.
|
|
35
|
+
lib_layered_config/examples/generate.py,sha256=YCmUms5oqWze5lv1NRN7sjT0MVO7zr6-70a7yCWnX_E,13820
|
|
36
|
+
lib_layered_config-1.1.0.dist-info/METADATA,sha256=48RYeqs_KwWgwQlvgdyBMel_PoroSV5CE_1_dcW10rE,16764
|
|
37
|
+
lib_layered_config-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
38
|
+
lib_layered_config-1.1.0.dist-info/entry_points.txt,sha256=peN9XKyKARqvDRE-DpK4eeZRc1JeiQqPpzx4BGv_EkE,116
|
|
39
|
+
lib_layered_config-1.1.0.dist-info/licenses/LICENSE,sha256=6s9SL2InRrwynPjJYiER7ONHBshbGj_yC5EBzfo7l9I,1072
|
|
40
|
+
lib_layered_config-1.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|