Skip to content

Commit 8a87412

Browse files
authored
Merge pull request #1 from lzjever/lzj/feature/filter_nonleaf_entries
Lzj/feature/filter nonleaf entries
2 parents 6cd5570 + fde38b1 commit 8a87412

File tree

15 files changed

+920
-19
lines changed

15 files changed

+920
-19
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.7.1] - 2026-01-09
9+
10+
### Added
11+
- **Configuration Export Functionality**: New methods to export current configuration to various file formats
12+
- `Config.to_dict()` - Get current configuration as dictionary
13+
- `Config.dump_json(file_path, ...)` - Export configuration to JSON file
14+
- `Config.dump_yaml(file_path, ...)` - Export configuration to YAML file
15+
- `Config.dump_toml(file_path, ...)` - Export configuration to TOML file
16+
- `Config.dump_env(file_path, ...)` - Export configuration to .env file
17+
- All export methods handle nested dataclass structures correctly
18+
- Support for custom options (prefix, uppercase, nested separator for .env export)
19+
- Clear error messages for missing optional dependencies (PyYAML, tomli-w)
20+
- **Enhanced Diagnostic Table**: Improved `--check-variables` output
21+
- Now filters out non-leaf nodes (intermediate nested config objects)
22+
- Only displays leaf-level configuration variables (e.g., `ai.completion.model` instead of `ai.completion`)
23+
- Cleaner, more focused diagnostic output for nested configurations
24+
25+
### Changed
26+
- `format_diagnostic_table()` now only shows leaf nodes in variable status table
27+
- Export methods use `asdict()` for proper nested dataclass conversion
28+
829
## [0.7.0] - 2026-01-08
930

1031
### Added

docs/source/api_reference/sources.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,5 @@ Etcd Source
2727

2828
.. automodule:: varlord.sources.etcd
2929
:members:
30-
:special-members: __init__
30+
:special-members: \_\_init\_\_
3131
:exclude-members: _get_client, _model
32-

docs/source/conf.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@
108108
# The error occurs because Sphinx may interpret "etcd" in docstrings as a reference
109109
suppress_warnings = [
110110
'ref.docutils', # Suppress docutils reference warnings (includes "Unknown target name" errors)
111-
'ref.any', # Suppress any reference warnings
112-
'ref.python', # Suppress Python reference warnings
113111
]
114112

115113
# Configure docutils to be less strict about unknown references

docs/source/user_guide/cli_commands.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,13 @@ The ``--check-variables`` (or ``-cv``) option displays comprehensive diagnostic
107107
about your configuration:
108108

109109
1. **Variable Status Table**: Shows all configuration variables with:
110-
- Variable name
110+
- Variable name (only leaf nodes are shown - nested intermediate objects like `ai.completion` are filtered out)
111111
- Required/Optional status
112112
- Current status (Loaded, Using Default, Missing, etc.)
113113
- Source (which source provided the value)
114114
- Value (truncated if too long)
115+
116+
**Note**: For nested configurations, only leaf-level variables are displayed. For example, if you have `ai.completion.model` and `ai.completion.api_key`, the table will show these two variables but not the intermediate `ai.completion` object.
115117

116118
2. **Source Information Table**: Shows detailed diagnostics for each source:
117119
- Priority order (1 = lowest, higher numbers = higher priority)
@@ -131,7 +133,13 @@ Example diagnostic output:
131133
| host | Required | Missing | defaults | None |
132134
| port | Optional | Using Default | defaults | 8000 |
133135
| debug | Optional | Using Default | defaults | False |
136+
| ai.completion.model | Required | Loaded | yaml | deepseek-chat |
137+
| ai.completion.api_key | Required | Loaded | yaml | sk-... |
138+
| ai.performance.max_tokens_input | Optional | Loaded | yaml | 131072 |
134139
+--------------------------------------------+----------+---------------+----------+--------+
140+
141+
Note: Intermediate nested objects (like `ai.completion` or `ai.performance`) are not shown,
142+
only the leaf-level configuration variables are displayed.
135143
136144
Configuration Source Priority and Details:
137145

docs/source/user_guide/export.rst

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
Configuration Export
2+
====================
3+
4+
Varlord provides functionality to export the current configuration to various file formats,
5+
making it easy to save, share, or version control your configuration.
6+
7+
Basic Usage
8+
-----------
9+
10+
After loading your configuration, you can export it to different formats:
11+
12+
.. code-block:: python
13+
:linenos:
14+
15+
from varlord import Config, sources
16+
from dataclasses import dataclass, field
17+
18+
@dataclass
19+
class AppConfig:
20+
api_key: str = field()
21+
host: str = field(default="0.0.0.0")
22+
port: int = field(default=8000)
23+
24+
# Create and load config
25+
cfg = Config(
26+
model=AppConfig,
27+
sources=[sources.Env(), sources.CLI()],
28+
)
29+
cfg.handle_cli_commands()
30+
config = cfg.load()
31+
32+
# Export to different formats
33+
cfg.dump_json("config.json")
34+
cfg.dump_yaml("config.yaml")
35+
cfg.dump_toml("config.toml")
36+
cfg.dump_env(".env", prefix="APP_")
37+
38+
Export Methods
39+
--------------
40+
41+
Get Configuration as Dictionary
42+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43+
44+
Use ``to_dict()`` to get the current configuration as a dictionary without writing to a file:
45+
46+
.. code-block:: python
47+
48+
config_dict = cfg.to_dict()
49+
print(config_dict["host"])
50+
print(config_dict["port"])
51+
52+
This is useful when you need to programmatically access configuration values or pass them to other functions.
53+
54+
JSON Export
55+
~~~~~~~~~~~
56+
57+
Export configuration to JSON format:
58+
59+
.. code-block:: python
60+
61+
cfg.dump_json("config.json", indent=4)
62+
63+
**Parameters:**
64+
- ``file_path``: Path to output JSON file (str or Path)
65+
- ``validate``: Whether to validate required fields before export (default: True)
66+
- ``indent``: JSON indentation level (default: 2)
67+
68+
**Example output:**
69+
70+
.. code-block:: json
71+
72+
{
73+
"api_key": "sk-...",
74+
"host": "0.0.0.0",
75+
"port": 8000
76+
}
77+
78+
YAML Export
79+
~~~~~~~~~~~
80+
81+
Export configuration to YAML format:
82+
83+
.. code-block:: python
84+
85+
cfg.dump_yaml("config.yaml", default_flow_style=False)
86+
87+
**Parameters:**
88+
- ``file_path``: Path to output YAML file (str or Path)
89+
- ``validate``: Whether to validate required fields before export (default: True)
90+
- ``default_flow_style``: Use flow style (default: False, uses block style)
91+
92+
**Dependencies:** Requires ``pyyaml`` package. Install with: ``pip install pyyaml``
93+
94+
**Example output:**
95+
96+
.. code-block:: yaml
97+
98+
api_key: sk-...
99+
host: 0.0.0.0
100+
port: 8000
101+
102+
TOML Export
103+
~~~~~~~~~~~
104+
105+
Export configuration to TOML format:
106+
107+
.. code-block:: python
108+
109+
cfg.dump_toml("config.toml")
110+
111+
**Parameters:**
112+
- ``file_path``: Path to output TOML file (str or Path)
113+
- ``validate``: Whether to validate required fields before export (default: True)
114+
115+
**Dependencies:** Requires ``tomli-w`` package. Install with: ``pip install tomli-w``
116+
117+
**Example output:**
118+
119+
.. code-block:: toml
120+
121+
api_key = "sk-..."
122+
host = "0.0.0.0"
123+
port = 8000
124+
125+
.env Export
126+
~~~~~~~~~~~
127+
128+
Export configuration to .env file format (for environment variables):
129+
130+
.. code-block:: python
131+
132+
cfg.dump_env(
133+
".env",
134+
prefix="APP_",
135+
uppercase=True,
136+
nested_separator="__"
137+
)
138+
139+
**Parameters:**
140+
- ``file_path``: Path to output .env file (str or Path)
141+
- ``validate``: Whether to validate required fields before export (default: True)
142+
- ``prefix``: Optional prefix for all environment variable names (e.g., ``APP_``)
143+
- ``uppercase``: Convert keys to uppercase (default: True)
144+
- ``nested_separator``: Separator for nested keys (default: "__")
145+
146+
**Example output:**
147+
148+
.. code-block:: text
149+
150+
APP_API_KEY=sk-...
151+
APP_HOST=0.0.0.0
152+
APP_PORT=8000
153+
154+
Nested Configuration
155+
--------------------
156+
157+
All export methods correctly handle nested dataclass structures:
158+
159+
.. code-block:: python
160+
:linenos:
161+
162+
@dataclass
163+
class DBConfig:
164+
host: str = field(default="localhost")
165+
port: int = field(default=5432)
166+
167+
@dataclass
168+
class AppConfig:
169+
api_key: str = field()
170+
db: DBConfig = field(default_factory=lambda: DBConfig())
171+
172+
cfg = Config(model=AppConfig, sources=[...])
173+
cfg.dump_json("config.json")
174+
175+
**JSON output:**
176+
177+
.. code-block:: json
178+
179+
{
180+
"api_key": "sk-...",
181+
"db": {
182+
"host": "localhost",
183+
"port": 5432
184+
}
185+
}
186+
187+
**YAML output:**
188+
189+
.. code-block:: yaml
190+
191+
api_key: sk-...
192+
db:
193+
host: localhost
194+
port: 5432
195+
196+
**TOML output:**
197+
198+
.. code-block:: toml
199+
200+
api_key = "sk-..."
201+
[db]
202+
host = "localhost"
203+
port = 5432
204+
205+
**.env output (with nested separator):**
206+
207+
.. code-block:: text
208+
209+
API_KEY=sk-...
210+
DB__HOST=localhost
211+
DB__PORT=5432
212+
213+
Use Cases
214+
---------
215+
216+
Configuration Backup
217+
~~~~~~~~~~~~~~~~~~~~~
218+
219+
Export your current configuration to save a snapshot:
220+
221+
.. code-block:: python
222+
223+
# Save current config as backup
224+
cfg.dump_yaml(f"config_backup_{datetime.now().isoformat()}.yaml")
225+
226+
Configuration Templates
227+
~~~~~~~~~~~~~~~~~~~~~~~~
228+
229+
Generate configuration templates for users:
230+
231+
.. code-block:: python
232+
233+
# Create template with defaults
234+
template_cfg = Config(model=AppConfig, sources=[])
235+
template_cfg.dump_json("config.template.json")
236+
237+
Environment Setup
238+
~~~~~~~~~~~~~~~~~
239+
240+
Generate .env files for different environments:
241+
242+
.. code-block:: python
243+
244+
# Development environment
245+
cfg.dump_env(".env.development", prefix="APP_DEV_")
246+
247+
# Production environment
248+
cfg.dump_env(".env.production", prefix="APP_PROD_")
249+
250+
Configuration Sharing
251+
~~~~~~~~~~~~~~~~~~~~~
252+
253+
Export configuration to share with team members or for documentation:
254+
255+
.. code-block:: python
256+
257+
# Export to YAML for documentation
258+
cfg.dump_yaml("docs/example_config.yaml")
259+
260+
Dependencies
261+
------------
262+
263+
- **JSON**: Built-in (no extra dependencies required)
264+
- **YAML**: Requires ``pyyaml`` (``pip install pyyaml``)
265+
- **TOML**: Requires ``tomli-w`` (``pip install tomli-w``)
266+
- **.env**: Built-in (no extra dependencies required)
267+
268+
If a required dependency is missing, the export method will raise an ``ImportError`` with a clear message indicating which package needs to be installed.
269+
270+
Best Practices
271+
--------------
272+
273+
1. **Validate before export**: By default, all export methods validate required fields. Set ``validate=False`` only if you're intentionally exporting incomplete configurations.
274+
275+
2. **Use appropriate formats**:
276+
- Use JSON for machine-readable configs and APIs
277+
- Use YAML for human-readable configs and documentation
278+
- Use TOML for Python projects (pyproject.toml style)
279+
- Use .env for environment variable exports
280+
281+
3. **Handle nested structures**: All export methods automatically handle nested dataclasses, so you don't need to manually flatten structures.
282+
283+
4. **Use prefixes for .env**: When exporting to .env format, use prefixes to avoid conflicts with other environment variables.
284+
285+
5. **Version control**: Consider adding exported config files to ``.gitignore`` if they contain sensitive information like API keys.

docs/source/user_guide/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This guide covers all aspects of using Varlord.
1313
key_mapping
1414
cli_commands
1515
subcommands
16+
export
1617
custom_sources
1718
priority
1819
validation

docs/source/user_guide/key_mapping.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,21 @@ Env (Environment Variables)
101101
- ``K8S_POD_NAME`` → ``k8s_pod_name`` (single ``_`` preserved)
102102
- ``OTHER_VAR`` → ignored (not in model fields)
103103

104+
**Prefix Filtering** (optional):
105+
106+
- Use ``prefix`` parameter to filter environment variables by prefix
107+
- Example: ``sources.Env(prefix="APP__")`` only loads variables starting with ``APP__``
108+
- Case-insensitive matching (e.g., ``app__`` matches ``APP__``)
109+
- Prefix is automatically removed before key normalization
110+
- Useful for isolating application-specific environment variables in containerized deployments
111+
104112
**Notes**:
105113

106114
- Model is required and will be auto-injected by ``Config`` if not provided
107115
- All environment variables are checked against model fields
108116
- Only variables that map to model fields are loaded
109-
- No prefix filtering - use model fields to control which variables are loaded
110-
- If you want to use a prefix in environment variable names, ensure your model field names match after normalization
117+
- If ``prefix`` is not provided, all environment variables matching model fields are loaded
118+
- Prefix matching is case-insensitive for better compatibility
111119

112120
CLI (Command-Line Arguments)
113121
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

0 commit comments

Comments
 (0)