Skip to content

Commit e9b23e2

Browse files
update for dict support
1 parent 923ba31 commit e9b23e2

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,25 @@ pip install pyamlo
6767
```python
6868
from pyamlo import load_config
6969

70+
# Load from YAML file
7071
config = load_config("examples/test_config.yaml")
7172
print(config)
7273

74+
# Load from Python dictionary
75+
config_dict = {
76+
"app": {"name": "MyApp", "workers": 4},
77+
"database": {"pool_size": "${app.workers * 2}"}
78+
}
79+
config = load_config(config_dict)
80+
print(config)
81+
82+
# Load from multiple sources (files and dictionaries)
83+
config = load_config([
84+
"base_config.yaml",
85+
{"app": {"debug": True}},
86+
"override_config.yaml"
87+
])
88+
print(config)
7389
```
7490
See for more details on the [examples documentation](https://martvanrijthoven.github.io/pyamlo/examples/).
7591

pyamlo/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
def load_config(
14-
source: Union[str, Path, IO[str], Sequence[Union[str, Path, IO[str]]]],
14+
source: Union[str, Path, IO[str], dict, Sequence[Union[str, Path, IO[str], dict]]],
1515
overrides: Optional[list[str]] = None,
1616
use_cli: bool = False,
1717
security_policy: SecurityPolicy = SecurityPolicy(restrictive=False),

pyamlo/include.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ def load_raw(path: str) -> dict[str, Any]:
3434

3535

3636
def process_includes(
37-
raw: dict[str, Any], base_path: str | None = None, security_policy: SecurityPolicy = None
37+
raw: dict[str, Any],
38+
base_path: str | None = None,
39+
security_policy: SecurityPolicy = None,
3840
) -> dict[str, Any]:
3941
incs = raw.pop("include!", [])
4042
merged: dict[str, Any] = {}

pyamlo/sources.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
11
from pathlib import Path
22
from typing import IO, Any, Sequence, Union
3+
import os
34

45
from pyamlo.merge import deep_merge
56
from pyamlo.include import process_includes, set_base_paths
6-
from pyamlo.tags import ConfigLoader
7+
from pyamlo.tags import ConfigLoader, CallSpec, ExtendSpec, PatchSpec
78
from pyamlo.security import SecurityPolicy
89

910

11+
def _process_dict_tags(data: Any, security_policy: SecurityPolicy) -> Any:
12+
"""Process YAML-style tags in dictionary data."""
13+
if isinstance(data, dict):
14+
return {k: _process_dict_tags(v, security_policy) for k, v in data.items()}
15+
elif isinstance(data, list):
16+
return [_process_dict_tags(item, security_policy) for item in data]
17+
elif isinstance(data, str):
18+
# Check for YAML tags in string values
19+
if data.startswith("!@"):
20+
# Object instantiation tag
21+
parts = data[2:].strip().split(None, 1)
22+
path = parts[0]
23+
args = [parts[1]] if len(parts) > 1 else []
24+
return CallSpec(path, args, {}, is_interpolated=False)
25+
elif data.startswith("!env "):
26+
# Environment variable tag
27+
var = data[5:].strip()
28+
security_policy.check_env_var(var)
29+
val = os.environ.get(var)
30+
if val is None:
31+
raise ValueError(f"Environment variable '{var}' not set")
32+
return val
33+
elif data == "!extend":
34+
return ExtendSpec([])
35+
elif data == "!patch":
36+
return PatchSpec({})
37+
return data
38+
39+
1040
def _load_source(
1141
source: Union[str, Path, IO[str]], security_policy: SecurityPolicy
1242
) -> dict[str, Any]:
@@ -23,27 +53,31 @@ def _load_with_loader(stream):
2353

2454

2555
def _process_single_source(
26-
src: Union[str, Path, IO[str]], security_policy: SecurityPolicy
56+
src: Union[str, Path, IO[str], dict], security_policy: SecurityPolicy
2757
) -> dict[str, Any]:
28-
raw = _load_source(src, security_policy=security_policy)
58+
if isinstance(src, dict):
59+
raw = _process_dict_tags(src.copy(), security_policy)
60+
src_path = "<dict>"
61+
else:
62+
raw = _load_source(src, security_policy=security_policy)
63+
src_path = src.name if isinstance(src, IO) else str(src)
2964
if not raw:
3065
return {}
31-
src_path = src.name if isinstance(src, IO) else str(src)
32-
if src_path:
66+
if src_path and src_path != "<dict>":
3367
set_base_paths(raw, src_path)
3468
return process_includes(raw, src_path, security_policy=security_policy)
3569

3670

3771
def get_sources(
38-
source: Union[str, Path, IO[str], Sequence[Union[str, Path, IO[str]]]],
39-
) -> list[Union[str, Path, IO[str]]]:
72+
source: Union[str, Path, IO[str], dict, Sequence[Union[str, Path, IO[str], dict]]],
73+
) -> list[Union[str, Path, IO[str], dict]]:
4074
if not isinstance(source, Sequence) or isinstance(source, (str, Path)):
4175
return [source]
4276
return list(source)
4377

4478

4579
def merge_all_sources(
46-
sources: list[Union[str, Path, IO[str]]], security_policy: SecurityPolicy
80+
sources: list[Union[str, Path, IO[str], dict]], security_policy: SecurityPolicy
4781
) -> dict[str, Any]:
4882
config: dict[str, Any] = {}
4983
for src in sources:

pyamlo/tags.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def construct_callspec(
130130
node: Union[MappingNode, SequenceNode, ScalarNode],
131131
) -> CallSpec:
132132
args, kwargs = _construct_callspec_args(loader, node)
133-
if '$' in suffix:
133+
if "$" in suffix:
134134
return CallSpec(suffix, args, kwargs, is_interpolated=True)
135135
return CallSpec(suffix, args, kwargs, is_interpolated=False)
136136

0 commit comments

Comments
 (0)