44YAML files available as a single dictionary and allow attribute (``.``)
55lookups.
66"""
7+ from __future__ import annotations
78
89import json
9- import typing as ty
10+ import os . path
1011from collections .abc import ItemsView , KeysView , Mapping , MutableMapping , ValuesView
1112from pathlib import Path
1213
14+ TYPE_CHECKING = False
15+ if TYPE_CHECKING :
16+ from collections .abc import Iterator
17+ from typing import Any , Self
1318
14- def _expand_dots (entry : tuple [str , ty .Any ]) -> tuple [str , ty .Any ]:
19+ from acres import typ as at
20+
21+
22+ def _expand_dots (entry : tuple [str , Any ]) -> tuple [str , Any ]:
1523 # Helper function for expand
1624 key , val = entry
1725 if "." in key :
@@ -20,7 +28,7 @@ def _expand_dots(entry: tuple[str, ty.Any]) -> tuple[str, ty.Any]:
2028 return key , expand (val )
2129
2230
23- def expand (element ) :
31+ def expand (element : dict [ str , Any ]) -> dict [ str , Any ] :
2432 """Expand a dict, recursively, to replace dots in keys with recursive dictionaries
2533
2634 Parameters
@@ -46,18 +54,18 @@ def expand(element):
4654
4755
4856class NsItemsView (ItemsView ):
49- def __init__ (self , namespace , level ):
57+ def __init__ (self , namespace : Mapping , level : int ):
5058 self ._mapping = namespace
5159 self ._level = level
5260
53- def __contains__ (self , item ) :
61+ def __contains__ (self , item : Any ) -> bool :
5462 key , val = item
5563 keys = key .split ("." , self ._level - 1 )
5664 if "." in keys [- 1 ]:
5765 return False
5866 return self ._mapping [key ] == val
5967
60- def __iter__ (self ):
68+ def __iter__ (self ) -> Iterator [ tuple [ str , Any ]] :
6169 l1 = ItemsView (self ._mapping )
6270 if self ._level == 1 :
6371 yield from l1
@@ -73,30 +81,30 @@ def __iter__(self):
7381
7482
7583class NsKeysView (KeysView ):
76- def __init__ (self , namespace , level ):
84+ def __init__ (self , namespace : Mapping , level : int ):
7785 self ._mapping = namespace
7886 self ._level = level
7987
80- def __contains__ (self , key ) :
88+ def __contains__ (self , key : Any ) -> bool :
8189 keys = key .split ("." , self ._level - 1 )
8290 if "." in keys [- 1 ]:
8391 return False
8492 return key in self ._mapping
8593
86- def __iter__ (self ):
94+ def __iter__ (self ) -> Iterator [ str ] :
8795 yield from (key for key , val in NsItemsView (self ._mapping , self ._level ))
8896
8997
9098class NsValuesView (ValuesView ):
91- def __init__ (self , namespace , level ):
99+ def __init__ (self , namespace : Mapping , level : int ):
92100 self ._mapping = namespace
93101 self ._level = level
94102 self ._items = NsItemsView (namespace , level )
95103
96- def __contains__ (self , val ) :
104+ def __contains__ (self , val : object ) -> bool :
97105 return any (val == item [1 ] for item in self ._items )
98106
99- def __iter__ (self ):
107+ def __iter__ (self ) -> Iterator [ Any ] :
100108 yield from (val for key , val in self ._items )
101109
102110
@@ -162,7 +170,7 @@ class Namespace(MutableMapping):
162170 >>> del ns['d']
163171 """
164172
165- def __init__ (self , * args , ** kwargs ):
173+ def __init__ (self , * args , ** kwargs ) -> None :
166174 self ._properties = dict (* args , ** kwargs )
167175
168176 def to_dict (self ) -> dict :
@@ -177,7 +185,7 @@ def _to_dict(obj):
177185
178186 return _to_dict (self )
179187
180- def __deepcopy__ (self , memo ):
188+ def __deepcopy__ (self , memo ) -> Self :
181189 return self .build (self .to_dict ())
182190
183191 @classmethod
@@ -218,42 +226,43 @@ def __getattribute__(self, key):
218226 except KeyError :
219227 raise err
220228
221- def _get_mapping (self , key : str ) -> tuple [Mapping , str ]:
229+ def _get_mapping (self , key : str ) -> tuple [MutableMapping , str ]:
222230 subkeys = key .split ("." )
223231 mapping = self ._properties
224232 for subkey in subkeys [:- 1 ]:
225233 mapping = mapping .setdefault (subkey , {})
226- mapping = getattr (mapping , "_properties" , mapping )
234+ if isinstance (mapping , Namespace ):
235+ mapping = mapping ._properties
227236 if not isinstance (mapping , Mapping ):
228237 raise KeyError (f"{ key } (subkey={ subkey } )" )
229238 return mapping , subkeys [- 1 ]
230239
231- def __getitem__ (self , key ) :
240+ def __getitem__ (self , key : str ) -> Any :
232241 mapping , subkey = self ._get_mapping (key )
233242 val = mapping [subkey ]
234243 if isinstance (val , dict ):
235244 val = self .view (val )
236245 return val
237246
238- def __setitem__ (self , key , val ):
247+ def __setitem__ (self , key : str , val : Any ):
239248 mapping , subkey = self ._get_mapping (key )
240249 mapping [subkey ] = val
241250
242- def __delitem__ (self , key ):
251+ def __delitem__ (self , key : str ):
243252 mapping , subkey = self ._get_mapping (key )
244253 del mapping [subkey ]
245254
246- def __repr__ (self ):
255+ def __repr__ (self ) -> str :
247256 return f"<Namespace { self ._properties } >"
248257
249- def __len__ (self ):
258+ def __len__ (self ) -> int :
250259 return len (self ._properties )
251260
252- def __iter__ (self ):
261+ def __iter__ (self ) -> Iterator [ str ] :
253262 return iter (self ._properties )
254263
255264 @classmethod
256- def from_directory (cls , path , fmt = "yaml" ):
265+ def from_directory (cls , path : at . Traversable | str , fmt : str = "yaml" ) -> Self :
257266 if fmt == "yaml" :
258267 if isinstance (path , str ):
259268 path = Path (path )
@@ -264,27 +273,27 @@ def to_json(self, **kwargs) -> str:
264273 return json .dumps (self , cls = MappingEncoder , ** kwargs )
265274
266275 @classmethod
267- def from_json (cls , jsonstr : str ):
276+ def from_json (cls , jsonstr : str ) -> Self :
268277 return cls .build (json .loads (jsonstr ))
269278
270279
271- def _read_yaml_dir (path : Path ) -> dict :
280+ def _read_yaml_dir (path : at . Traversable ) -> dict :
272281 mapping = {}
273- for subpath in sorted (path .iterdir ()):
282+ for subpath in sorted (path .iterdir (), key = lambda p : p . name ):
274283 if subpath .is_dir ():
275284 mapping [subpath .name ] = _read_yaml_dir (subpath )
276285 elif subpath .name .endswith ("yaml" ):
277286 import yaml
278287
279288 try :
280- mapping [subpath .stem ] = yaml .safe_load (subpath .read_text ())
289+ mapping [os . path . splitext ( subpath .name )[ 0 ] ] = yaml .safe_load (subpath .read_text ())
281290 except Exception as e :
282291 raise ValueError (f"There was an error reading the file: { subpath } " ) from e
283292 return mapping
284293
285294
286295class MappingEncoder (json .JSONEncoder ):
287- def default (self , o ) :
296+ def default (self , o : object ) -> object :
288297 try :
289298 return super ().default (o )
290299 except TypeError as e :
0 commit comments