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.

Files changed (39) hide show
  1. lib_layered_config/__init__.py +60 -0
  2. lib_layered_config/__main__.py +19 -0
  3. lib_layered_config/_layers.py +457 -0
  4. lib_layered_config/_platform.py +200 -0
  5. lib_layered_config/adapters/__init__.py +13 -0
  6. lib_layered_config/adapters/dotenv/__init__.py +1 -0
  7. lib_layered_config/adapters/dotenv/default.py +438 -0
  8. lib_layered_config/adapters/env/__init__.py +5 -0
  9. lib_layered_config/adapters/env/default.py +509 -0
  10. lib_layered_config/adapters/file_loaders/__init__.py +1 -0
  11. lib_layered_config/adapters/file_loaders/structured.py +410 -0
  12. lib_layered_config/adapters/path_resolvers/__init__.py +1 -0
  13. lib_layered_config/adapters/path_resolvers/default.py +727 -0
  14. lib_layered_config/application/__init__.py +12 -0
  15. lib_layered_config/application/merge.py +442 -0
  16. lib_layered_config/application/ports.py +109 -0
  17. lib_layered_config/cli/__init__.py +162 -0
  18. lib_layered_config/cli/common.py +232 -0
  19. lib_layered_config/cli/constants.py +12 -0
  20. lib_layered_config/cli/deploy.py +70 -0
  21. lib_layered_config/cli/fail.py +21 -0
  22. lib_layered_config/cli/generate.py +60 -0
  23. lib_layered_config/cli/info.py +31 -0
  24. lib_layered_config/cli/read.py +117 -0
  25. lib_layered_config/core.py +384 -0
  26. lib_layered_config/domain/__init__.py +7 -0
  27. lib_layered_config/domain/config.py +490 -0
  28. lib_layered_config/domain/errors.py +65 -0
  29. lib_layered_config/examples/__init__.py +29 -0
  30. lib_layered_config/examples/deploy.py +305 -0
  31. lib_layered_config/examples/generate.py +537 -0
  32. lib_layered_config/observability.py +306 -0
  33. lib_layered_config/py.typed +0 -0
  34. lib_layered_config/testing.py +55 -0
  35. lib_layered_config-1.0.0.dist-info/METADATA +366 -0
  36. lib_layered_config-1.0.0.dist-info/RECORD +39 -0
  37. lib_layered_config-1.0.0.dist-info/WHEEL +4 -0
  38. lib_layered_config-1.0.0.dist-info/entry_points.txt +3 -0
  39. 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)