1515 NO_DEFAULT ,
1616 DatapathError ,
1717 ValidationError ,
18- TypeValidationError ,
19- TypeMismatchValidationError ,
2018 InvalidIterationError ,
2119 PathLookupError ,
22- _IterationPoint ,
20+ _PathPart ,
21+ _IndexPart ,
22+ _KeyPart ,
2323 _ListIterationPoint ,
2424 _StarIterationPoint ,
2525 _RangeIterationPoint ,
@@ -58,7 +58,7 @@ def is_path(path: str, iterable: bool = True) -> bool:
5858 if iterable :
5959 return True
6060 try :
61- _split_match (match , iterable = False )
61+ _split_path_from_match (match , iterable = False )
6262 return True
6363 except ValidationError :
6464 return False
@@ -74,33 +74,42 @@ def validate_path(path: str, iterable: bool = True) -> None:
7474 return
7575 match = _match_validate (path )
7676 if not iterable :
77- _split_match (match , iterable = False )
77+ _split_path_from_match (match , iterable = False )
7878
7979
8080def split (path : str , iterable : bool = False ) -> SplitPath :
8181 """inverse of join() -- split the path string to it's component keys/indexes in order"""
8282 if path == '' :
8383 return ()
8484 match = _match_validate (path )
85- return _split_match (match , iterable )
85+ return _split_path_from_match (match , iterable )
8686
8787
88- def _split_match (match : re .Match , iterable : bool ) -> SplitPath :
88+ def _split_path_from_match (match : re .Match , iterable : bool ) -> SplitPath :
89+ return _split_path_from_iterable (match .captures ('part' ), iterable )
90+
91+
92+ def _split_path_from_iterable (parts : Iterable [Key | _PathPart ], iterable : bool ) -> SplitPath :
8993 split_path : list [Key ] = []
90- for part in match .captures ('part' ):
91- if part [0 ] == '[' and part [- 1 ] == ']' :
92- index = part [1 :- 1 ]
93- if ':' in index :
94+ for part in parts :
95+ if isinstance (part , _PathPart ):
96+ path_part = part
97+ elif isinstance (part , int ):
98+ path_part = _IndexPart (f'[{ part } ]' )
99+ elif not isinstance (part , str ):
100+ raise ValidationError ('path parts must be str or int' )
101+ elif part [0 ] == '[' and part [- 1 ] == ']' :
102+ if ':' in part :
94103 path_part = _RangeIterationPoint (part )
95- elif index :
96- path_part = int (index )
97- else :
104+ elif part == '[]' :
98105 path_part = _ListIterationPoint (part )
106+ else :
107+ path_part = _IndexPart (part )
99108 elif '*' in part :
100109 path_part = _StarIterationPoint (part )
101110 else :
102- path_part = part
103- if not iterable and isinstance ( path_part , _IterationPoint ) :
111+ path_part = _KeyPart ( part )
112+ if not iterable and path_part . iterable :
104113 raise InvalidIterationError (f'iterable { path_part .name } { path_part } not allowed here' )
105114 split_path .append (path_part )
106115 return tuple (split_path )
@@ -117,54 +126,22 @@ def join(split_path: Iterable[Key]) -> str:
117126 ```
118127 """
119128 path = ''
120- for i , part in enumerate (split_path ):
121- if isinstance (part , str ):
122- if path :
123- path = f'{ path } .{ part } '
124- else :
125- path = part
126- elif isinstance (part , int ):
127- if path :
128- path = f'{ path } [{ part } ]'
129- else :
130- path = f'[{ part } ]'
131- elif isinstance (part , _IterationPoint ):
132- path = part .append_path (path )
133- else :
134- raise ValidationError (f'index { i } is invalid, must be str/int or iteration point, '
135- f'got { type (part ).__name__ } ' )
129+ for i , part in enumerate (_split_path_from_iterable (split_path , True )):
130+ path = part .append_path (path )
136131 return path
137132
138133
139- def _validate_key_collection_type (obj : Collection , key : Key ) -> None :
140- """
141- validate a collection object and key are valid and corresponding types
142- raise a ValidationError if they are not
143- """
144- if isinstance (key , _IterationPoint ):
145- raise TypeError ('bug: iteration not supported here' )
146- if not isinstance (obj , _collection_types ):
147- raise TypeValidationError ('object must be list/dict' )
148- if not isinstance (key , _key_types ):
149- raise TypeValidationError ('path parts must all be str or int' )
150- if isinstance (key , int ) and not isinstance (obj , list ):
151- raise TypeMismatchValidationError (f'int key requires list, got { type (obj ).__name__ } ' )
152- if isinstance (key , str ) and not isinstance (obj , dict ):
153- raise TypeMismatchValidationError (f'str key requires dict, got { type (obj ).__name__ } ' )
154-
155-
156- def _contextual_validate_key_collection_type (at_path : list [Key ],
157- obj : Collection ,
158- key : Key ) -> None :
134+ def _check_collection_for_path_part (at_path : list [_PathPart ], path_part : _PathPart , obj : Collection ) -> None :
159135 """
160136 validate_key_collection_type(), except the path where the error occurred is prepended
161137 to the exception message
162138 """
163- at_path .append (key )
139+ if path_part .iterable :
140+ raise RuntimeError ('bug: iteration not supported here' )
164141 try :
165- _validate_key_collection_type (obj , key )
142+ path_part . check (obj )
166143 except ValidationError as e :
167- raise type ( e ) (f'{ join (at_path )} : { e } ' ) from None
144+ raise ValidationError (f'{ join (at_path )} : { e } ' ) from None
168145
169146
170147def leaf (obj : Collection , path : str ) -> CollectionKey :
@@ -174,16 +151,17 @@ def leaf(obj: Collection, path: str) -> CollectionKey:
174151
175152def _leaf (obj : Collection , split_path : SplitPath ) -> CollectionKey :
176153 """leaf() on an already-split path"""
177- at_path : list [Key ] = []
178- for key in split_path [:- 1 ]:
179- _contextual_validate_key_collection_type (at_path , obj , key )
154+ at_path : list [_PathPart ] = []
155+ for path_part in split_path [:- 1 ]:
156+ _check_collection_for_path_part (at_path , path_part , obj )
180157 try :
181- obj = obj [key ]
158+ obj = obj [path_part .key ]
159+ at_path .append (path_part )
182160 except LookupError :
183- raise PathLookupError (f'{ join (at_path [: - 1 ] )} : could not find key/index { key !r} ' ) from None
184- leaf_key = split_path [- 1 ]
185- _contextual_validate_key_collection_type (at_path , obj , leaf_key )
186- return obj , leaf_key
161+ raise PathLookupError (f'{ join (at_path )} : could not find key/index { path_part . key !r} ' ) from None
162+ leaf_path_part = split_path [- 1 ]
163+ _check_collection_for_path_part (at_path , leaf_path_part , obj )
164+ return obj , leaf_path_part . key
187165
188166
189167def get (obj : Collection , path : str , default : Any = NO_DEFAULT ) -> Any :
@@ -196,7 +174,7 @@ def get(obj: Collection, path: str, default: Any = NO_DEFAULT) -> Any:
196174 return _get (obj , split (path ), default )
197175
198176
199- def _get (obj : Collection , split_path : str , default : Any = NO_DEFAULT ) -> Any :
177+ def _get (obj : Collection , split_path : SplitPath , default : Any = NO_DEFAULT ) -> Any :
200178 """get() on an already-split path"""
201179 if not split_path :
202180 return obj
@@ -253,7 +231,7 @@ def _iterate(obj: Collection,
253231 iter_index = None
254232 iter_point = None
255233 for index , part in enumerate (split_path ):
256- if isinstance ( part , _IterationPoint ) :
234+ if part . iterable :
257235 iter_index = index
258236 iter_point = part
259237 break
0 commit comments