lib-layered-config 1.0.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__.py +60 -0
- lib_layered_config/__main__.py +19 -0
- lib_layered_config/_layers.py +457 -0
- lib_layered_config/_platform.py +200 -0
- lib_layered_config/adapters/__init__.py +13 -0
- lib_layered_config/adapters/dotenv/__init__.py +1 -0
- lib_layered_config/adapters/dotenv/default.py +438 -0
- lib_layered_config/adapters/env/__init__.py +5 -0
- lib_layered_config/adapters/env/default.py +509 -0
- lib_layered_config/adapters/file_loaders/__init__.py +1 -0
- lib_layered_config/adapters/file_loaders/structured.py +410 -0
- lib_layered_config/adapters/path_resolvers/__init__.py +1 -0
- lib_layered_config/adapters/path_resolvers/default.py +727 -0
- lib_layered_config/application/__init__.py +12 -0
- lib_layered_config/application/merge.py +442 -0
- lib_layered_config/application/ports.py +109 -0
- lib_layered_config/cli/__init__.py +162 -0
- lib_layered_config/cli/common.py +232 -0
- lib_layered_config/cli/constants.py +12 -0
- lib_layered_config/cli/deploy.py +70 -0
- lib_layered_config/cli/fail.py +21 -0
- lib_layered_config/cli/generate.py +60 -0
- lib_layered_config/cli/info.py +31 -0
- lib_layered_config/cli/read.py +117 -0
- lib_layered_config/core.py +384 -0
- lib_layered_config/domain/__init__.py +7 -0
- lib_layered_config/domain/config.py +490 -0
- lib_layered_config/domain/errors.py +65 -0
- lib_layered_config/examples/__init__.py +29 -0
- lib_layered_config/examples/deploy.py +305 -0
- lib_layered_config/examples/generate.py +537 -0
- lib_layered_config/observability.py +306 -0
- lib_layered_config/py.typed +0 -0
- lib_layered_config/testing.py +55 -0
- lib_layered_config-1.0.0.dist-info/METADATA +366 -0
- lib_layered_config-1.0.0.dist-info/RECORD +39 -0
- lib_layered_config-1.0.0.dist-info/WHEEL +4 -0
- lib_layered_config-1.0.0.dist-info/entry_points.txt +3 -0
- lib_layered_config-1.0.0.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""Structured logging helpers distilled into tiny orchestration phrases.
|
|
2
|
+
|
|
3
|
+
Purpose
|
|
4
|
+
Keep every emission of logging data predictable, contextual, and ready for
|
|
5
|
+
downstream aggregation pipelines without forcing applications to adopt a
|
|
6
|
+
specific logging backend.
|
|
7
|
+
|
|
8
|
+
Contents
|
|
9
|
+
- ``TRACE_ID``: context variable storing the active trace identifier.
|
|
10
|
+
- ``get_logger``: returns the shared package logger (quiet by default).
|
|
11
|
+
- ``bind_trace_id``: binds or clears the active trace identifier.
|
|
12
|
+
- ``log_debug`` / ``log_info`` / ``log_error``: emit structured entries via a
|
|
13
|
+
single private emitter.
|
|
14
|
+
- ``make_event``: convenience builder for structured event payloads.
|
|
15
|
+
|
|
16
|
+
System Integration
|
|
17
|
+
Used by adapters and the composition root to ensure all diagnostics carry
|
|
18
|
+
the same trace metadata. Keeps the domain layer free from logging concerns
|
|
19
|
+
while still offering consumers consistent observability hooks.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from contextvars import ContextVar
|
|
26
|
+
from typing import Any, Final, Mapping
|
|
27
|
+
|
|
28
|
+
TRACE_ID: ContextVar[str | None] = ContextVar("lib_layered_config_trace_id", default=None)
|
|
29
|
+
"""Current trace identifier propagated through logging helpers.
|
|
30
|
+
|
|
31
|
+
Why
|
|
32
|
+
----
|
|
33
|
+
Cross-cutting observability features (CLI, adapters) need a shared context without threading identifiers manually.
|
|
34
|
+
|
|
35
|
+
What
|
|
36
|
+
----
|
|
37
|
+
Context variable storing a string identifier or ``None``.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_LOGGER: Final[logging.Logger] = logging.getLogger("lib_layered_config")
|
|
41
|
+
_LOGGER.addHandler(logging.NullHandler())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_logger() -> logging.Logger:
|
|
45
|
+
"""Expose the package logger so applications may attach handlers.
|
|
46
|
+
|
|
47
|
+
Why
|
|
48
|
+
----
|
|
49
|
+
Leave the library silent by default while giving host applications full control over handler configuration.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
logging.Logger
|
|
54
|
+
Shared logger instance for ``lib_layered_config``.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
return _LOGGER
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def bind_trace_id(trace_id: str | None) -> None:
|
|
61
|
+
"""Bind or clear the active trace identifier.
|
|
62
|
+
|
|
63
|
+
Why
|
|
64
|
+
----
|
|
65
|
+
Correlate configuration events with external trace spans.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
trace_id:
|
|
70
|
+
Identifier string or ``None`` to drop the binding.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
None
|
|
75
|
+
|
|
76
|
+
Side Effects
|
|
77
|
+
------------
|
|
78
|
+
Mutates :data:`TRACE_ID`, affecting subsequent logging helpers.
|
|
79
|
+
|
|
80
|
+
Examples
|
|
81
|
+
--------
|
|
82
|
+
>>> bind_trace_id('abc123')
|
|
83
|
+
>>> TRACE_ID.get()
|
|
84
|
+
'abc123'
|
|
85
|
+
>>> bind_trace_id(None)
|
|
86
|
+
>>> TRACE_ID.get() is None
|
|
87
|
+
True
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
TRACE_ID.set(trace_id)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def log_debug(message: str, **fields: Any) -> None:
|
|
94
|
+
"""Emit a structured debug log entry that includes the trace context.
|
|
95
|
+
|
|
96
|
+
Why
|
|
97
|
+
----
|
|
98
|
+
Provide consistent debug telemetry across adapters while threading trace metadata.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
message:
|
|
103
|
+
Event name rendered by the logger.
|
|
104
|
+
**fields:
|
|
105
|
+
Structured context merged into the payload.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
None
|
|
110
|
+
|
|
111
|
+
Side Effects
|
|
112
|
+
------------
|
|
113
|
+
Calls :func:`_emit`, which writes to the shared logger.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
_emit(logging.DEBUG, message, fields)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def log_info(message: str, **fields: Any) -> None:
|
|
120
|
+
"""Emit a structured info log entry that includes the trace context.
|
|
121
|
+
|
|
122
|
+
Why
|
|
123
|
+
----
|
|
124
|
+
Capture high-level lifecycle events (layer loaded, merge complete) with trace IDs attached.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
message:
|
|
129
|
+
Event name rendered by the logger.
|
|
130
|
+
**fields:
|
|
131
|
+
Structured context merged into the payload.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
None
|
|
136
|
+
|
|
137
|
+
Side Effects
|
|
138
|
+
------------
|
|
139
|
+
Calls :func:`_emit` with ``logging.INFO``.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
_emit(logging.INFO, message, fields)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def log_error(message: str, **fields: Any) -> None:
|
|
146
|
+
"""Emit a structured error log entry that includes the trace context.
|
|
147
|
+
|
|
148
|
+
Why
|
|
149
|
+
----
|
|
150
|
+
Surface recoverable adapter failures (e.g., invalid files) with enough context for diagnosis.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
message:
|
|
155
|
+
Event name rendered by the logger.
|
|
156
|
+
**fields:
|
|
157
|
+
Structured context merged into the payload.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
None
|
|
162
|
+
|
|
163
|
+
Side Effects
|
|
164
|
+
------------
|
|
165
|
+
Calls :func:`_emit` with ``logging.ERROR``.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
_emit(logging.ERROR, message, fields)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def make_event(
|
|
172
|
+
layer: str,
|
|
173
|
+
path: str | None,
|
|
174
|
+
payload: Mapping[str, Any] | None = None,
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
"""Build a structured logging payload for configuration lifecycle events.
|
|
177
|
+
|
|
178
|
+
Why
|
|
179
|
+
----
|
|
180
|
+
Keep event construction consistent so downstream log processors can rely on stable keys.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
layer:
|
|
185
|
+
Name of the configuration layer being observed.
|
|
186
|
+
path:
|
|
187
|
+
Filesystem path associated with the event, if available.
|
|
188
|
+
payload:
|
|
189
|
+
Optional mapping with extra diagnostic detail.
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
dict[str, Any]
|
|
194
|
+
Data safe to unpack into :func:`log_debug`, :func:`log_info`, or :func:`log_error`.
|
|
195
|
+
|
|
196
|
+
Examples
|
|
197
|
+
--------
|
|
198
|
+
>>> make_event('env', None, {'keys': 3})
|
|
199
|
+
{'layer': 'env', 'path': None, 'keys': 3}
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
event = _base_event(layer, path)
|
|
203
|
+
return _merge_payload(event, payload)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _emit(level: int, message: str, fields: Mapping[str, Any]) -> None:
|
|
207
|
+
"""Send a log entry through the shared logger with contextual metadata.
|
|
208
|
+
|
|
209
|
+
Why
|
|
210
|
+
----
|
|
211
|
+
Centralise the call to ``logging.Logger.log`` so trace injection and field handling stay consistent.
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
level:
|
|
216
|
+
Standard library logging level.
|
|
217
|
+
message:
|
|
218
|
+
Event name rendered by the logger.
|
|
219
|
+
fields:
|
|
220
|
+
Structured payload to merge with the trace identifier.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
None
|
|
225
|
+
|
|
226
|
+
Side Effects
|
|
227
|
+
------------
|
|
228
|
+
Writes to the shared package logger.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
_LOGGER.log(level, message, extra={"context": _with_trace(fields)})
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _with_trace(fields: Mapping[str, Any]) -> dict[str, Any]:
|
|
235
|
+
"""Attach the current trace identifier to the provided structured fields.
|
|
236
|
+
|
|
237
|
+
Why
|
|
238
|
+
----
|
|
239
|
+
Guarantee that every log entry includes the active trace (when present).
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
fields:
|
|
244
|
+
Mapping of additional structured context.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
dict[str, Any]
|
|
249
|
+
Copy of ``fields`` with ``trace_id`` added.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
context = {"trace_id": TRACE_ID.get()}
|
|
253
|
+
context.update(fields)
|
|
254
|
+
return context
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _base_event(layer: str, path: str | None) -> dict[str, Any]:
|
|
258
|
+
"""Create the minimal event payload containing layer and path information.
|
|
259
|
+
|
|
260
|
+
Why
|
|
261
|
+
----
|
|
262
|
+
Provide a consistent foundation for layer-related logging events.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
layer:
|
|
267
|
+
Layer name to annotate the event.
|
|
268
|
+
path:
|
|
269
|
+
Optional filesystem path associated with the event.
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
dict[str, Any]
|
|
274
|
+
Base payload ready for augmentation.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
return {"layer": layer, "path": path}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _merge_payload(event: dict[str, Any], payload: Mapping[str, Any] | None) -> dict[str, Any]:
|
|
281
|
+
"""Merge optional diagnostic data into the event payload when provided.
|
|
282
|
+
|
|
283
|
+
Why
|
|
284
|
+
----
|
|
285
|
+
Allow callers to enrich events without mutating the original dictionary outside this helper.
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
event:
|
|
290
|
+
Base event payload.
|
|
291
|
+
payload:
|
|
292
|
+
Optional mapping of diagnostic data.
|
|
293
|
+
|
|
294
|
+
Returns
|
|
295
|
+
-------
|
|
296
|
+
dict[str, Any]
|
|
297
|
+
Updated payload containing merged data.
|
|
298
|
+
|
|
299
|
+
Side Effects
|
|
300
|
+
------------
|
|
301
|
+
Mutates ``event`` when ``payload`` is provided.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
if payload:
|
|
305
|
+
event |= dict(payload)
|
|
306
|
+
return event
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Testing diagnostics that keep failure scenarios observable and predictable.
|
|
2
|
+
|
|
3
|
+
Purpose
|
|
4
|
+
Provide intentionally failing helpers that exercise error-handling paths in
|
|
5
|
+
the CLI and integration suites without relying on brittle fixtures.
|
|
6
|
+
|
|
7
|
+
Contents
|
|
8
|
+
- ``FAILURE_MESSAGE``: stable message used when forcing a failure.
|
|
9
|
+
- ``i_should_fail``: raises ``RuntimeError`` so callers can assert on the
|
|
10
|
+
propagated error details.
|
|
11
|
+
|
|
12
|
+
System Integration
|
|
13
|
+
Resides in the testing support layer referenced by CLI end-to-end tests and
|
|
14
|
+
notebooks that demonstrate error propagation semantics.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Final
|
|
20
|
+
|
|
21
|
+
FAILURE_MESSAGE: Final[str] = "i should fail"
|
|
22
|
+
"""Stable message emitted when :func:`i_should_fail` triggers a failure.
|
|
23
|
+
|
|
24
|
+
Why
|
|
25
|
+
----
|
|
26
|
+
Integration tests and tutorial notebooks assert on the wording to guarantee
|
|
27
|
+
deterministic output during regression checks.
|
|
28
|
+
|
|
29
|
+
What
|
|
30
|
+
----
|
|
31
|
+
A short, lower-case sentence that stays compatible with published examples.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def i_should_fail() -> None:
|
|
36
|
+
"""Raise a deterministic :class:`RuntimeError` for failure-path testing.
|
|
37
|
+
|
|
38
|
+
Why
|
|
39
|
+
----
|
|
40
|
+
Validates that higher-level orchestrators preserve stack traces and
|
|
41
|
+
messages when surfacing errors to end users.
|
|
42
|
+
|
|
43
|
+
Side Effects
|
|
44
|
+
------------
|
|
45
|
+
Always raises :class:`RuntimeError` with :data:`FAILURE_MESSAGE`.
|
|
46
|
+
|
|
47
|
+
Examples
|
|
48
|
+
--------
|
|
49
|
+
>>> i_should_fail()
|
|
50
|
+
Traceback (most recent call last):
|
|
51
|
+
...
|
|
52
|
+
RuntimeError: i should fail
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
raise RuntimeError(FAILURE_MESSAGE)
|