11from pathlib import Path
22from typing import IO , Any , Sequence , Union
3+ import os
34
45from pyamlo .merge import deep_merge
56from pyamlo .include import process_includes , set_base_paths
6- from pyamlo .tags import ConfigLoader
7+ from pyamlo .tags import ConfigLoader , CallSpec , ExtendSpec , PatchSpec
78from 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+
1040def _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
2555def _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
3771def 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
4579def 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 :
0 commit comments