dynamic-config-loader 2.4.0__tar.gz → 2.6.0__tar.gz
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.
- {dynamic_config_loader-2.4.0/src/dynamic_config_loader.egg-info → dynamic_config_loader-2.6.0}/PKG-INFO +98 -52
- dynamic_config_loader-2.6.0/README.md +140 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/pyproject.toml +3 -1
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/environment.py +14 -4
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/__init__.py +4 -0
- dynamic_config_loader-2.6.0/src/dynamic_config_loader/strategies/env/__init__.py +36 -0
- dynamic_config_loader-2.6.0/src/dynamic_config_loader/strategies/env/merger.py +73 -0
- dynamic_config_loader-2.6.0/src/dynamic_config_loader/strategies/env/parser.py +71 -0
- dynamic_config_loader-2.6.0/src/dynamic_config_loader/strategies/properties/__init__.py +36 -0
- dynamic_config_loader-2.6.0/src/dynamic_config_loader/strategies/properties/merger.py +73 -0
- dynamic_config_loader-2.6.0/src/dynamic_config_loader/strategies/properties/parser.py +86 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0/src/dynamic_config_loader.egg-info}/PKG-INFO +98 -52
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader.egg-info/SOURCES.txt +8 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_env_injection.py +27 -0
- dynamic_config_loader-2.6.0/tests/test_env_strategy.py +140 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_factory.py +10 -0
- dynamic_config_loader-2.6.0/tests/test_properties_strategy.py +120 -0
- dynamic_config_loader-2.4.0/README.md +0 -94
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/AUTHORS +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/LICENSE +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/NOTICE +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/setup.cfg +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/__init__.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/loader.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/base_strategy.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/ini/__init__.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/ini/merger.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/ini/parser.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/json/__init__.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/json/merger.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/json/parser.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/toml/__init__.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/toml/merger.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/toml/parser.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/xml/__init__.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/xml/merger.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/xml/parser.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/yaml/__init__.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/yaml/merger.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/strategies/yaml/parser.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader.egg-info/dependency_links.txt +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader.egg-info/requires.txt +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader.egg-info/top_level.txt +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_deep_merge.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_ini_strategy.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_json_strategy.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_loader.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_logging_safety.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_toml_strategy.py +0 -0
- {dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/tests/test_xml_strategy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dynamic-config-loader
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: An enterprise-grade configuration orchestration engine designed to ingest, cascade, and deeply merge multiple configuration file layers.
|
|
5
5
|
Author-email: Avinash Kumar <halfhiddencode@gmail.com>
|
|
6
6
|
License:
|
|
@@ -210,7 +210,7 @@ Project-URL: Repository, https://gitlab.com/halfhiddencode/python-dynamic-config
|
|
|
210
210
|
Project-URL: Issues, https://gitlab.com/halfhiddencode/python-dynamic-config-loader/-/issues
|
|
211
211
|
Project-URL: Documentation, https://gitlab.com/halfhiddencode/python-dynamic-config-loader/-/tree/main/docs
|
|
212
212
|
Project-URL: Changelog, https://gitlab.com/halfhiddencode/python-dynamic-config-loader/-/blob/main/CHANGELOG.md
|
|
213
|
-
Keywords: config,configuration,dynamic-loader,cascading-config,deep-merge,strategy-pattern,yaml,json,xml,toml,ini,conf,devops
|
|
213
|
+
Keywords: config,configuration,dynamic-loader,cascading-config,deep-merge,strategy-pattern,yaml,json,xml,toml,ini,conf,properties,dotenv,devops
|
|
214
214
|
Classifier: Development Status :: 5 - Production/Stable
|
|
215
215
|
Classifier: Intended Audience :: Developers
|
|
216
216
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -237,49 +237,67 @@ Dynamic: license-file
|
|
|
237
237
|
|
|
238
238
|
# Dynamic Configuration Loader
|
|
239
239
|
|
|
240
|
-
A lightweight, production-grade, strategy-driven configuration orchestration engine designed to dynamically discover,
|
|
240
|
+
A lightweight, production-grade, strategy-driven configuration orchestration engine designed to dynamically discover, cascade, and deep-merge multi-layered configuration files.
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 📌 Quick Navigation Index
|
|
243
245
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
246
|
+
- [⚡ Minimal Quick Start](#-quick-start-minimal-3-lines)
|
|
247
|
+
- [📦 Installation](#-installation)
|
|
248
|
+
- [⚙️ Supported Configuration Formats](#%EF%B8%8F-supported-configuration-formats)
|
|
249
|
+
- [🔀 Multi-Layer Overrides & Environment Injection](#-multi-layer-overrides--environment-injection)
|
|
250
|
+
- [📖 Documentation Index](#-documentation-index)
|
|
251
|
+
- [🎨 System Architecture Visualizer](#-system-architecture)
|
|
250
252
|
|
|
251
253
|
---
|
|
252
254
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
### 🚀 Configuration Lifecycle Matrix: Minimal to Advanced
|
|
256
|
+
|
|
257
|
+
| Lifecycle Phase | Component Requirement | How It Works in Code / Environment | Primary Purpose |
|
|
258
|
+
| :--- | :--- | :--- | :--- |
|
|
259
|
+
| **1. Local Dev (Coding Time)** | `[OPTIONAL FALLBACK]` | `loader = DynamicConfigLoader("config.yaml")` | Local fallback file for offline developer workstations. |
|
|
260
|
+
| **2. Image Build (CI/CD Build Time)** | `[REQUIRED IN CODE]` | `loader = DynamicConfigLoader()` | **Image Immutability!** Zero hardcoded secrets in Docker image. |
|
|
261
|
+
| **3. Deployment (K8s Runtime Time)** | `[OPTIONAL AT DEPLOYMENT]` | `export PYTHON_ADDITIONAL_CONFIG="/etc/config/prod.yaml"` | Mounts K8s ConfigMaps & Vault secrets into container at runtime. |
|
|
262
|
+
| **4. Live Ops Overrides (Runtime)** | `[OPTIONAL AT RUNTIME]` | `export DCL_ENV_LOADER=true` <br> `export DCL_PORT=8080` | Overrides specific keys in memory dynamically without rebuilding image. |
|
|
263
|
+
|
|
264
|
+
---
|
|
255
265
|
|
|
256
|
-
|
|
257
|
-
you may not use this file except in compliance with the License.
|
|
258
|
-
You may obtain a copy of the License at
|
|
266
|
+
### Minimal Execution Code Snippets
|
|
259
267
|
|
|
260
|
-
|
|
268
|
+
#### Mode 1: Container / Kubernetes Mode (`[RECOMMENDED FOR PROD]`)
|
|
269
|
+
|
|
270
|
+
> [!NOTE]
|
|
271
|
+
> **[OPTIONAL FILE PATH - CAN SKIP!]**
|
|
272
|
+
> If you don't have a local config file, **YOU CAN SKIP IT!** In production container deployments, you do NOT need any hardcoded file paths in your code. The engine will handle configuration loading automatically at deployment time via environment variables (`PYTHON_ADDITIONAL_CONFIG` or `DCL_`).
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from dynamic_config_loader import DynamicConfigLoader
|
|
261
276
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
See the License for the specific language governing permissions and
|
|
266
|
-
limitations under the License.
|
|
277
|
+
# Zero hardcoded file paths! Fully handled at deployment time
|
|
278
|
+
loader = DynamicConfigLoader()
|
|
279
|
+
config = loader.load()
|
|
267
280
|
```
|
|
268
281
|
|
|
269
|
-
|
|
282
|
+
#### Mode 2: Local Development Mode (`[OPTIONAL FALLBACK]`)
|
|
283
|
+
|
|
284
|
+
> [!NOTE]
|
|
285
|
+
> **[LOCAL FALLBACK ONLY]**
|
|
286
|
+
> Useful for local developer laptops so you can run `python app.py` offline without configuring environment variables.
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from dynamic_config_loader import DynamicConfigLoader
|
|
270
290
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
* **Licensing:** Apache License, Version 2.0
|
|
276
|
-
* **Core Design Patterns:** Strategy Pattern, Registry Factory
|
|
291
|
+
# Optional local fallback file
|
|
292
|
+
loader = DynamicConfigLoader("config.yaml")
|
|
293
|
+
config = loader.load()
|
|
294
|
+
```
|
|
277
295
|
|
|
278
296
|
---
|
|
279
297
|
|
|
280
|
-
## Installation
|
|
298
|
+
## 📦 Installation
|
|
281
299
|
|
|
282
|
-
Install
|
|
300
|
+
Install from PyPI via `pip`:
|
|
283
301
|
|
|
284
302
|
```bash
|
|
285
303
|
pip install dynamic-config-loader
|
|
@@ -293,39 +311,67 @@ pip install -e .
|
|
|
293
311
|
|
|
294
312
|
---
|
|
295
313
|
|
|
296
|
-
##
|
|
314
|
+
## ⚙️ Supported Configuration Formats
|
|
297
315
|
|
|
298
|
-
|
|
299
|
-
import os
|
|
300
|
-
from dynamic_config_loader import DynamicConfigLoader
|
|
316
|
+
The orchestration engine natively ingests, parses, and cascades **7 configuration formats**:
|
|
301
317
|
|
|
302
|
-
|
|
303
|
-
|
|
318
|
+
| Format | Extensions | Key Behavior & Conversion | Guide Link |
|
|
319
|
+
| :--- | :--- | :--- | :--- |
|
|
320
|
+
| **YAML** | `.yaml`, `.yml` | Native YAML indentation & deep dictionary parsing | [View Details](docs/guides/user_usage.md#71-yaml-strategy-yamlstrategy) |
|
|
321
|
+
| **JSON** | `.json` | Strict JSON objects & typed arrays | [View Details](docs/guides/user_usage.md#72-json-strategy-jsonstrategy) |
|
|
322
|
+
| **Dotenv** | `.env` | **100% Flat Keys** (`SERVICE_GATEWAY_NAME` $\rightarrow$ `config["service_gateway_name"]`) | [View Details](docs/guides/user_usage.md#87-dotenv-strategy-envstrategy) |
|
|
323
|
+
| **TOML** | `.toml` | Section headers (`[table]`), inline tables, & datetimes | [View Details](docs/guides/user_usage.md#74-toml-strategy-tomlstrategy) |
|
|
324
|
+
| **INI / Conf** | `.ini`, `.conf` | Section headers (`[section]`) & automatic primitive type casting | [View Details](docs/guides/user_usage.md#75-ini--conf-strategy-inistrategy) |
|
|
325
|
+
| **Java Properties**| `.properties` | Dot notation (`app.database.port=5432` $\rightarrow$ `app.database.port`) | [View Details](docs/guides/user_usage.md#76-java-properties-strategy-propertiesstrategy) |
|
|
326
|
+
| **XML** | `.xml` | Tag element hierarchies auto-converted into Python dicts | [View Details](docs/guides/user_usage.md#73-xml-strategy-xmlstrategy) |
|
|
304
327
|
|
|
305
|
-
|
|
306
|
-
loader = DynamicConfigLoader(default_config_path="/app/configs/base_defaults.yaml")
|
|
328
|
+
---
|
|
307
329
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
330
|
+
## 🔀 Multi-Layer Overrides & Environment Injection
|
|
331
|
+
|
|
332
|
+
<details>
|
|
333
|
+
<summary><b>Click to expand Advanced Environment Overrides & Logging Options</b></summary>
|
|
334
|
+
|
|
335
|
+
### 1. External File/Directory Overrides (`PYTHON_ADDITIONAL_CONFIG`)
|
|
336
|
+
Specify an external file or directory of override files via system environment variables:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
export PYTHON_ADDITIONAL_CONFIG="/etc/app/production_overrides.yaml"
|
|
311
340
|
```
|
|
312
341
|
|
|
342
|
+
### 2. Environment Variable Injection (`DCL_ENV_LOADER=true`)
|
|
343
|
+
Inject or override specific keys dynamically at runtime using environment variables:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
export DCL_ENV_LOADER=true
|
|
347
|
+
export DCL_PORT=9000
|
|
348
|
+
export DCL_LOG_LEVEL=info
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 3. Log Redaction Controls (`ENABLE_UNSAFE_LOGGING`)
|
|
352
|
+
* **Default (Safe Mode):** Sensitive passwords and tokens are redacted (`[REDACTED]`) in logs.
|
|
353
|
+
* **Unsafe Mode (`ENABLE_UNSAFE_LOGGING=true`):** Exposes raw values during local debug sessions.
|
|
354
|
+
|
|
355
|
+
</details>
|
|
356
|
+
|
|
313
357
|
---
|
|
314
358
|
|
|
315
|
-
##
|
|
359
|
+
## 🎨 System Architecture
|
|
360
|
+
|
|
361
|
+

|
|
362
|
+
|
|
363
|
+
---
|
|
316
364
|
|
|
317
|
-
|
|
365
|
+
## 📖 Documentation Index
|
|
318
366
|
|
|
319
|
-
| Track |
|
|
367
|
+
| Guide Track | Link | Description |
|
|
320
368
|
| :--- | :--- | :--- |
|
|
321
|
-
|
|
|
322
|
-
|
|
|
323
|
-
|
|
|
324
|
-
|
|
|
369
|
+
| **🚀 Getting Started Tutorial** | [getting_started.md](docs/tutorials/getting_started.md) | Step-by-step onboarding & minimal execution tracks. |
|
|
370
|
+
| **📖 Operations & Usage Guide** | [user_usage.md](docs/guides/user_usage.md) | Complete format matrix, K8s/DevOps deployment scenarios, & path edge cases. |
|
|
371
|
+
| **🛠️ Contributor Guide** | [contributor_guide.md](docs/guides/contributor_guide.md) | Adding custom format strategies & factory registry hooks. |
|
|
372
|
+
| **🏗️ Architecture Reference** | [architecture.md](docs/reference/architecture.md) | System design, UML diagrams, & deep merge algorithms. |
|
|
325
373
|
|
|
326
374
|
---
|
|
327
375
|
|
|
328
|
-
##
|
|
329
|
-
|
|
330
|
-
* Security Policy: [SECURITY.md](https://gitlab.com/halfhiddencode/python-dynamic-config-loader/-/blob/main/SECURITY.md)
|
|
331
|
-
* Contribution Protocols: [CONTRIBUTING.md](https://gitlab.com/halfhiddencode/python-dynamic-config-loader/-/blob/main/CONTRIBUTING.md)
|
|
376
|
+
## 📜 License
|
|
377
|
+
Licensed under the [Apache License, Version 2.0](LICENSE).
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Dynamic Configuration Loader
|
|
2
|
+
|
|
3
|
+
A lightweight, production-grade, strategy-driven configuration orchestration engine designed to dynamically discover, cascade, and deep-merge multi-layered configuration files.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📌 Quick Navigation Index
|
|
8
|
+
|
|
9
|
+
- [⚡ Minimal Quick Start](#-quick-start-minimal-3-lines)
|
|
10
|
+
- [📦 Installation](#-installation)
|
|
11
|
+
- [⚙️ Supported Configuration Formats](#%EF%B8%8F-supported-configuration-formats)
|
|
12
|
+
- [🔀 Multi-Layer Overrides & Environment Injection](#-multi-layer-overrides--environment-injection)
|
|
13
|
+
- [📖 Documentation Index](#-documentation-index)
|
|
14
|
+
- [🎨 System Architecture Visualizer](#-system-architecture)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### 🚀 Configuration Lifecycle Matrix: Minimal to Advanced
|
|
19
|
+
|
|
20
|
+
| Lifecycle Phase | Component Requirement | How It Works in Code / Environment | Primary Purpose |
|
|
21
|
+
| :--- | :--- | :--- | :--- |
|
|
22
|
+
| **1. Local Dev (Coding Time)** | `[OPTIONAL FALLBACK]` | `loader = DynamicConfigLoader("config.yaml")` | Local fallback file for offline developer workstations. |
|
|
23
|
+
| **2. Image Build (CI/CD Build Time)** | `[REQUIRED IN CODE]` | `loader = DynamicConfigLoader()` | **Image Immutability!** Zero hardcoded secrets in Docker image. |
|
|
24
|
+
| **3. Deployment (K8s Runtime Time)** | `[OPTIONAL AT DEPLOYMENT]` | `export PYTHON_ADDITIONAL_CONFIG="/etc/config/prod.yaml"` | Mounts K8s ConfigMaps & Vault secrets into container at runtime. |
|
|
25
|
+
| **4. Live Ops Overrides (Runtime)** | `[OPTIONAL AT RUNTIME]` | `export DCL_ENV_LOADER=true` <br> `export DCL_PORT=8080` | Overrides specific keys in memory dynamically without rebuilding image. |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### Minimal Execution Code Snippets
|
|
30
|
+
|
|
31
|
+
#### Mode 1: Container / Kubernetes Mode (`[RECOMMENDED FOR PROD]`)
|
|
32
|
+
|
|
33
|
+
> [!NOTE]
|
|
34
|
+
> **[OPTIONAL FILE PATH - CAN SKIP!]**
|
|
35
|
+
> If you don't have a local config file, **YOU CAN SKIP IT!** In production container deployments, you do NOT need any hardcoded file paths in your code. The engine will handle configuration loading automatically at deployment time via environment variables (`PYTHON_ADDITIONAL_CONFIG` or `DCL_`).
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from dynamic_config_loader import DynamicConfigLoader
|
|
39
|
+
|
|
40
|
+
# Zero hardcoded file paths! Fully handled at deployment time
|
|
41
|
+
loader = DynamicConfigLoader()
|
|
42
|
+
config = loader.load()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Mode 2: Local Development Mode (`[OPTIONAL FALLBACK]`)
|
|
46
|
+
|
|
47
|
+
> [!NOTE]
|
|
48
|
+
> **[LOCAL FALLBACK ONLY]**
|
|
49
|
+
> Useful for local developer laptops so you can run `python app.py` offline without configuring environment variables.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from dynamic_config_loader import DynamicConfigLoader
|
|
53
|
+
|
|
54
|
+
# Optional local fallback file
|
|
55
|
+
loader = DynamicConfigLoader("config.yaml")
|
|
56
|
+
config = loader.load()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 📦 Installation
|
|
62
|
+
|
|
63
|
+
Install from PyPI via `pip`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install dynamic-config-loader
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or install in editable mode for local development:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install -e .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## ⚙️ Supported Configuration Formats
|
|
78
|
+
|
|
79
|
+
The orchestration engine natively ingests, parses, and cascades **7 configuration formats**:
|
|
80
|
+
|
|
81
|
+
| Format | Extensions | Key Behavior & Conversion | Guide Link |
|
|
82
|
+
| :--- | :--- | :--- | :--- |
|
|
83
|
+
| **YAML** | `.yaml`, `.yml` | Native YAML indentation & deep dictionary parsing | [View Details](docs/guides/user_usage.md#71-yaml-strategy-yamlstrategy) |
|
|
84
|
+
| **JSON** | `.json` | Strict JSON objects & typed arrays | [View Details](docs/guides/user_usage.md#72-json-strategy-jsonstrategy) |
|
|
85
|
+
| **Dotenv** | `.env` | **100% Flat Keys** (`SERVICE_GATEWAY_NAME` $\rightarrow$ `config["service_gateway_name"]`) | [View Details](docs/guides/user_usage.md#87-dotenv-strategy-envstrategy) |
|
|
86
|
+
| **TOML** | `.toml` | Section headers (`[table]`), inline tables, & datetimes | [View Details](docs/guides/user_usage.md#74-toml-strategy-tomlstrategy) |
|
|
87
|
+
| **INI / Conf** | `.ini`, `.conf` | Section headers (`[section]`) & automatic primitive type casting | [View Details](docs/guides/user_usage.md#75-ini--conf-strategy-inistrategy) |
|
|
88
|
+
| **Java Properties**| `.properties` | Dot notation (`app.database.port=5432` $\rightarrow$ `app.database.port`) | [View Details](docs/guides/user_usage.md#76-java-properties-strategy-propertiesstrategy) |
|
|
89
|
+
| **XML** | `.xml` | Tag element hierarchies auto-converted into Python dicts | [View Details](docs/guides/user_usage.md#73-xml-strategy-xmlstrategy) |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 🔀 Multi-Layer Overrides & Environment Injection
|
|
94
|
+
|
|
95
|
+
<details>
|
|
96
|
+
<summary><b>Click to expand Advanced Environment Overrides & Logging Options</b></summary>
|
|
97
|
+
|
|
98
|
+
### 1. External File/Directory Overrides (`PYTHON_ADDITIONAL_CONFIG`)
|
|
99
|
+
Specify an external file or directory of override files via system environment variables:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
export PYTHON_ADDITIONAL_CONFIG="/etc/app/production_overrides.yaml"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 2. Environment Variable Injection (`DCL_ENV_LOADER=true`)
|
|
106
|
+
Inject or override specific keys dynamically at runtime using environment variables:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export DCL_ENV_LOADER=true
|
|
110
|
+
export DCL_PORT=9000
|
|
111
|
+
export DCL_LOG_LEVEL=info
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Log Redaction Controls (`ENABLE_UNSAFE_LOGGING`)
|
|
115
|
+
* **Default (Safe Mode):** Sensitive passwords and tokens are redacted (`[REDACTED]`) in logs.
|
|
116
|
+
* **Unsafe Mode (`ENABLE_UNSAFE_LOGGING=true`):** Exposes raw values during local debug sessions.
|
|
117
|
+
|
|
118
|
+
</details>
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 🎨 System Architecture
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 📖 Documentation Index
|
|
129
|
+
|
|
130
|
+
| Guide Track | Link | Description |
|
|
131
|
+
| :--- | :--- | :--- |
|
|
132
|
+
| **🚀 Getting Started Tutorial** | [getting_started.md](docs/tutorials/getting_started.md) | Step-by-step onboarding & minimal execution tracks. |
|
|
133
|
+
| **📖 Operations & Usage Guide** | [user_usage.md](docs/guides/user_usage.md) | Complete format matrix, K8s/DevOps deployment scenarios, & path edge cases. |
|
|
134
|
+
| **🛠️ Contributor Guide** | [contributor_guide.md](docs/guides/contributor_guide.md) | Adding custom format strategies & factory registry hooks. |
|
|
135
|
+
| **🏗️ Architecture Reference** | [architecture.md](docs/reference/architecture.md) | System design, UML diagrams, & deep merge algorithms. |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 📜 License
|
|
140
|
+
Licensed under the [Apache License, Version 2.0](LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dynamic-config-loader"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.6.0"
|
|
8
8
|
description = "An enterprise-grade configuration orchestration engine designed to ingest, cascade, and deeply merge multiple configuration file layers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -27,6 +27,8 @@ keywords = [
|
|
|
27
27
|
"toml",
|
|
28
28
|
"ini",
|
|
29
29
|
"conf",
|
|
30
|
+
"properties",
|
|
31
|
+
"dotenv",
|
|
30
32
|
"devops"
|
|
31
33
|
]
|
|
32
34
|
|
{dynamic_config_loader-2.4.0 → dynamic_config_loader-2.6.0}/src/dynamic_config_loader/environment.py
RENAMED
|
@@ -56,11 +56,21 @@ def inject_environment_variables(
|
|
|
56
56
|
if env_key.startswith(prefix):
|
|
57
57
|
# Strip prefix
|
|
58
58
|
sliced_key = env_key[len(prefix):]
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
parsed_val = _parse_env_value(env_val)
|
|
60
|
+
|
|
61
|
+
flat_key = sliced_key.strip().lower()
|
|
62
|
+
|
|
63
|
+
# Resolution order:
|
|
64
|
+
# 1. Direct flat key match in config_data (e.g. DCL_TEST_NAME -> config_data["test_name"])
|
|
65
|
+
# 2. Double-underscore '__' explicit namespace splitting (e.g. DCL_AUTH__TENANT_ID -> config_data["auth"]["tenant_id"])
|
|
66
|
+
# 3. Single-underscore '_' nested path splitting (e.g. DCL_DATABASE_PORT -> config_data["database"]["port"])
|
|
67
|
+
if flat_key in config_data:
|
|
68
|
+
parts = [flat_key]
|
|
69
|
+
elif "__" in sliced_key:
|
|
70
|
+
parts = [p.lower() for p in sliced_key.split("__") if p]
|
|
71
|
+
else:
|
|
72
|
+
parts = [p.lower() for p in sliced_key.split("_") if p]
|
|
61
73
|
|
|
62
|
-
# Case Normalization and tokenizing
|
|
63
|
-
parts = [p.lower() for p in sliced_key.split("_") if p]
|
|
64
74
|
if not parts:
|
|
65
75
|
continue
|
|
66
76
|
|
|
@@ -19,6 +19,8 @@ from dynamic_config_loader.strategies.json import JsonStrategy
|
|
|
19
19
|
from dynamic_config_loader.strategies.xml import XmlStrategy
|
|
20
20
|
from dynamic_config_loader.strategies.ini import IniStrategy
|
|
21
21
|
from dynamic_config_loader.strategies.toml import TomlStrategy
|
|
22
|
+
from dynamic_config_loader.strategies.env import EnvStrategy
|
|
23
|
+
from dynamic_config_loader.strategies.properties import PropertiesStrategy
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class StrategyFactory:
|
|
@@ -39,6 +41,8 @@ class StrategyFactory:
|
|
|
39
41
|
".ini": IniStrategy(),
|
|
40
42
|
".conf": IniStrategy(),
|
|
41
43
|
".toml": TomlStrategy(),
|
|
44
|
+
".env": EnvStrategy(),
|
|
45
|
+
".properties": PropertiesStrategy(),
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
def get_strategy(self, extension: str) -> Optional[BaseStrategy]:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
# Copyright (C) 2026 Avinash Kumar
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Env Strategy Entrypoint.
|
|
8
|
+
|
|
9
|
+
Exposes the EnvStrategy wrapper subclassing BaseStrategy.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
from dynamic_config_loader.strategies.base_strategy import BaseStrategy
|
|
15
|
+
from dynamic_config_loader.strategies.env.parser import parse_env_file
|
|
16
|
+
from dynamic_config_loader.strategies.env.merger import execute_env_deep_merge
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EnvStrategy(BaseStrategy):
|
|
20
|
+
"""
|
|
21
|
+
Strategy implementation managing the parsing and deep-merging of dotenv (.env) assets.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def parse(self, file_path: Path) -> Dict[str, Any]:
|
|
25
|
+
return parse_env_file(file_path)
|
|
26
|
+
|
|
27
|
+
def merge(
|
|
28
|
+
self,
|
|
29
|
+
base_dict: Dict[str, Any],
|
|
30
|
+
update_dict: Dict[str, Any],
|
|
31
|
+
path: str = "",
|
|
32
|
+
mask_secrets: bool = True,
|
|
33
|
+
) -> None:
|
|
34
|
+
execute_env_deep_merge(
|
|
35
|
+
base_dict, update_dict, path=path, mask_secrets=mask_secrets
|
|
36
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
# Copyright (C) 2026 Avinash Kumar
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Env Merging Strategy Module.
|
|
8
|
+
|
|
9
|
+
Provides recursive tree-merging algorithms engineered to blend cascading dotenv layers.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def execute_env_deep_merge(
|
|
19
|
+
base_dict: Dict[str, Any],
|
|
20
|
+
update_dict: Dict[str, Any],
|
|
21
|
+
path: str = "",
|
|
22
|
+
mask_secrets: bool = True,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Recursively deep-merges update_dict into base_dict in-place.
|
|
26
|
+
"""
|
|
27
|
+
for key, value in update_dict.items():
|
|
28
|
+
current_path = f"{path}.{key}" if path else key
|
|
29
|
+
|
|
30
|
+
# Case A: Both values are dictionaries -> recurse
|
|
31
|
+
if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict):
|
|
32
|
+
logger.debug(f"Deep merging nested env structure at path: {current_path}")
|
|
33
|
+
execute_env_deep_merge(
|
|
34
|
+
base_dict[key], value, path=current_path, mask_secrets=mask_secrets
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Case B: Structural conflict or key exists
|
|
38
|
+
elif key in base_dict:
|
|
39
|
+
if isinstance(base_dict[key], dict) or isinstance(value, dict):
|
|
40
|
+
logger.warning(
|
|
41
|
+
f"Type mismatch conflict detected at path '{current_path}'. "
|
|
42
|
+
f"Overwriting base env structural type ({type(base_dict[key]).__name__}) "
|
|
43
|
+
f"with incoming type ({type(value).__name__})."
|
|
44
|
+
)
|
|
45
|
+
base_dict[key] = value
|
|
46
|
+
else:
|
|
47
|
+
if base_dict[key] != value:
|
|
48
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
49
|
+
if not mask_secrets:
|
|
50
|
+
logger.debug(
|
|
51
|
+
f"Updating env path '{current_path}': {repr(base_dict[key])} -> {repr(value)}"
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
logger.debug(
|
|
55
|
+
f"Updating env path '{current_path}': [REDACTED] -> [REDACTED]"
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
logger.info(
|
|
59
|
+
f"Updating configuration attribute path: {current_path} [Value Redacted]"
|
|
60
|
+
)
|
|
61
|
+
base_dict[key] = value
|
|
62
|
+
# Case C: New key
|
|
63
|
+
else:
|
|
64
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
65
|
+
if not mask_secrets:
|
|
66
|
+
logger.debug(f"Adding new env key at path '{current_path}': {repr(value)}")
|
|
67
|
+
else:
|
|
68
|
+
logger.debug(f"Adding new env key at path '{current_path}': [REDACTED]")
|
|
69
|
+
else:
|
|
70
|
+
logger.info(
|
|
71
|
+
f"Adding new configuration attribute path: {current_path} [Value Redacted]"
|
|
72
|
+
)
|
|
73
|
+
base_dict[key] = value
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
# Copyright (C) 2026 Avinash Kumar
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Env Parsing Strategy Module.
|
|
8
|
+
|
|
9
|
+
Provides isolated, low-level file stream ingestion utilities optimized
|
|
10
|
+
for handling standard dotenv configuration assets.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _parse_env_file_value(val: str) -> Any:
|
|
21
|
+
"""
|
|
22
|
+
Parses env string values into standard primitives (bool, int, float, str).
|
|
23
|
+
"""
|
|
24
|
+
val_lower = val.strip().lower()
|
|
25
|
+
if val_lower == "true":
|
|
26
|
+
return True
|
|
27
|
+
if val_lower == "false":
|
|
28
|
+
return False
|
|
29
|
+
try:
|
|
30
|
+
return int(val)
|
|
31
|
+
except ValueError:
|
|
32
|
+
pass
|
|
33
|
+
try:
|
|
34
|
+
return float(val)
|
|
35
|
+
except ValueError:
|
|
36
|
+
pass
|
|
37
|
+
return val.strip()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_env_file(file_path: Path) -> Dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Reads a dotenv configuration file asset from disk and returns a dictionary mapping.
|
|
43
|
+
"""
|
|
44
|
+
data: Dict[str, Any] = {}
|
|
45
|
+
try:
|
|
46
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
47
|
+
for line in f:
|
|
48
|
+
line = line.strip()
|
|
49
|
+
if not line or line.startswith("#") or line.startswith("!") or "=" not in line:
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
key, val = line.split("=", 1)
|
|
53
|
+
key = key.strip().lower()
|
|
54
|
+
val = val.strip()
|
|
55
|
+
|
|
56
|
+
if not key:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# Strip surrounding double or single quotes
|
|
60
|
+
if len(val) >= 2 and (
|
|
61
|
+
(val[0] == '"' and val[-1] == '"') or (val[0] == "'" and val[-1] == "'")
|
|
62
|
+
):
|
|
63
|
+
val = val[1:-1]
|
|
64
|
+
|
|
65
|
+
parsed_val = _parse_env_file_value(val)
|
|
66
|
+
data[key] = parsed_val
|
|
67
|
+
|
|
68
|
+
except (IOError, PermissionError) as e:
|
|
69
|
+
logger.exception(f"Failed to read or parse dotenv file: {file_path.name}", exc_info=e)
|
|
70
|
+
|
|
71
|
+
return data
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
# Copyright (C) 2026 Avinash Kumar
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Java Properties Strategy Entrypoint.
|
|
8
|
+
|
|
9
|
+
Exposes the PropertiesStrategy wrapper subclassing BaseStrategy.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
from dynamic_config_loader.strategies.base_strategy import BaseStrategy
|
|
15
|
+
from dynamic_config_loader.strategies.properties.parser import parse_properties_file
|
|
16
|
+
from dynamic_config_loader.strategies.properties.merger import execute_properties_deep_merge
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PropertiesStrategy(BaseStrategy):
|
|
20
|
+
"""
|
|
21
|
+
Strategy implementation managing the parsing and deep-merging of Java properties assets.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def parse(self, file_path: Path) -> Dict[str, Any]:
|
|
25
|
+
return parse_properties_file(file_path)
|
|
26
|
+
|
|
27
|
+
def merge(
|
|
28
|
+
self,
|
|
29
|
+
base_dict: Dict[str, Any],
|
|
30
|
+
update_dict: Dict[str, Any],
|
|
31
|
+
path: str = "",
|
|
32
|
+
mask_secrets: bool = True,
|
|
33
|
+
) -> None:
|
|
34
|
+
execute_properties_deep_merge(
|
|
35
|
+
base_dict, update_dict, path=path, mask_secrets=mask_secrets
|
|
36
|
+
)
|