diff --git a/dpath/__init__.py b/dpath/__init__.py index c717314..9f56e6b 100644 --- a/dpath/__init__.py +++ b/dpath/__init__.py @@ -30,7 +30,7 @@ _DEFAULT_SENTINEL = object() -def _split_path(path: Path, separator: Optional[str]) -> Union[List[PathSegment], PathSegment]: +def _split_path(path: Path, separator: Optional[str] = "/") -> Union[List[PathSegment], PathSegment]: """ Given a path and separator, return a tuple of segments. If path is already a non-leaf thing, return it. @@ -45,16 +45,6 @@ def _split_path(path: Path, separator: Optional[str]) -> Union[List[PathSegment] else: split_segments = path.lstrip(separator).split(separator) - if options.CONVERT_INT_LIKE_SEGMENTS: - # Attempt to convert integer segments into actual integers. - final = [] - for segment in split_segments: - try: - final.append(int(segment)) - except ValueError: - final.append(segment) - split_segments = final - return split_segments diff --git a/dpath/options.py b/dpath/options.py index 91b4290..41f35c4 100644 --- a/dpath/options.py +++ b/dpath/options.py @@ -1,2 +1 @@ ALLOW_EMPTY_STRING_KEYS = False -CONVERT_INT_LIKE_SEGMENTS = True diff --git a/dpath/segments.py b/dpath/segments.py index d87a7b2..faa763f 100644 --- a/dpath/segments.py +++ b/dpath/segments.py @@ -309,7 +309,7 @@ def set( ) -> MutableMapping: """ Set the value in obj at the place indicated by segments. If creator is not - None (default __default_creator__), then call the creator function to + None (default _default_creator), then call the creator function to create any missing path components. set(obj, segments, value) -> obj @@ -320,13 +320,18 @@ def set( # For everything except the last value, walk down the path and # create if creator is set. for (i, segment) in enumerate(segments[:-1]): + + # If segment is non-int but supposed to be a sequence index + if isinstance(segment, str) and isinstance(current, Sequence) and segment.isdigit(): + segment = int(segment) + try: # Optimistically try to get the next value. This makes the # code agnostic to whether current is a list or a dict. # Unfortunately, for our use, 'x in thing' for lists checks # values, not keys whereas dicts check keys. current[segment] - except (KeyError, IndexError): + except: if creator is not None: creator(current, segments, i, hints) else: @@ -336,10 +341,16 @@ def set( if i != length - 1 and leaf(current): raise PathNotFound(f"Path: {segments}[{i}]") - if isinstance(segments[-1], int): - extend(current, segments[-1]) + last_segment = segments[-1] + + # Resolve ambiguity of last segment + if isinstance(last_segment, str) and isinstance(current, Sequence) and last_segment.isdigit(): + last_segment = int(last_segment) - current[segments[-1]] = value + if isinstance(last_segment, int): + extend(current, last_segment) + + current[last_segment] = value return obj @@ -388,9 +399,11 @@ def view(obj, glob): view(obj, glob) -> obj' """ + def f(obj, pair, result): (segments, value) = pair if match(segments, glob): if not has(result, segments): set(result, segments, deepcopy(value), hints=types(obj, segments)) + return fold(obj, f, type(obj)()) diff --git a/dpath/version.py b/dpath/version.py index 127c148..5b0431e 100644 --- a/dpath/version.py +++ b/dpath/version.py @@ -1 +1 @@ -VERSION = "2.1.0" +VERSION = "2.1.1" diff --git a/tests/test_new.py b/tests/test_new.py index 6da31e7..15b21c6 100644 --- a/tests/test_new.py +++ b/tests/test_new.py @@ -42,6 +42,16 @@ def test_set_new_list(): assert dict['a'][0] is None +def test_set_list_with_dict_int_ambiguity(): + d = {"list": [{"root": {"1": {"k": None}}}]} + + dpath.new(d, "list/0/root/1/k", "new") + + expected = {"list": [{"root": {"1": {"k": "new"}}}]} + + assert d == expected + + def test_set_new_list_path_with_separator(): # This test kills many birds with one stone, forgive me dict = {