Skip to content

Commit 2eae6c1

Browse files
authored
Merge pull request #181 from dpath-maintainers/179-int-ambiguity-solution-force-creates-dicts-when-no-pre-existing-dict-exists-breaking-previous-functionality
Better int ambiguity resolution in default creator
2 parents 312a42c + 013b3a7 commit 2eae6c1

File tree

4 files changed

+37
-10
lines changed

4 files changed

+37
-10
lines changed

dpath/segments.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from copy import deepcopy
22
from fnmatch import fnmatchcase
3-
from typing import List, Sequence, Tuple, Iterator, Any, Union, Optional, MutableMapping
3+
from typing import Sequence, Tuple, Iterator, Any, Union, Optional, MutableMapping, MutableSequence
44

55
from dpath import options
66
from dpath.exceptions import InvalidGlob, InvalidKeyName, PathNotFound
@@ -81,7 +81,7 @@ def walk(obj, location=()):
8181
yield found
8282

8383

84-
def get(obj, segments):
84+
def get(obj, segments: Path):
8585
"""
8686
Return the value at the path indicated by segments.
8787
@@ -92,6 +92,9 @@ def get(obj, segments):
9292
if leaf(current):
9393
raise PathNotFound(f"Path: {segments}[{i}]")
9494

95+
if isinstance(current, Sequence) and isinstance(segment, str) and segment.isdecimal():
96+
segment = int(segment)
97+
9598
current = current[segment]
9699
return current
97100

@@ -254,7 +257,7 @@ def match(segments: Path, glob: Glob):
254257
return False
255258

256259

257-
def extend(thing: List, index: int, value=None):
260+
def extend(thing: MutableSequence, index: int, value=None):
258261
"""
259262
Extend a sequence like thing such that it contains at least index +
260263
1 many elements. The extension values will be None (default).
@@ -280,7 +283,7 @@ def extend(thing: List, index: int, value=None):
280283

281284

282285
def _default_creator(
283-
current: Union[MutableMapping, List],
286+
current: Union[MutableMapping, Sequence],
284287
segments: Sequence[PathSegment],
285288
i: int,
286289
hints: Sequence[Tuple[PathSegment, type]] = ()
@@ -294,7 +297,10 @@ def _default_creator(
294297
segment = segments[i]
295298
length = len(segments)
296299

297-
if isinstance(segment, int):
300+
if isinstance(current, Sequence):
301+
segment = int(segment)
302+
303+
if isinstance(current, MutableSequence):
298304
extend(current, segment)
299305

300306
# Infer the type from the hints provided.
@@ -308,7 +314,7 @@ def _default_creator(
308314
else:
309315
segment_next = None
310316

311-
if isinstance(segment_next, int):
317+
if isinstance(segment_next, int) or (isinstance(segment_next, str) and segment_next.isdecimal()):
312318
current[segment] = []
313319
else:
314320
current[segment] = {}
@@ -336,7 +342,7 @@ def set(
336342
for (i, segment) in enumerate(segments[:-1]):
337343

338344
# If segment is non-int but supposed to be a sequence index
339-
if isinstance(segment, str) and isinstance(current, Sequence) and segment.isdigit():
345+
if isinstance(segment, str) and isinstance(current, Sequence) and segment.isdecimal():
340346
segment = int(segment)
341347

342348
try:
@@ -358,7 +364,7 @@ def set(
358364
last_segment = segments[-1]
359365

360366
# Resolve ambiguity of last segment
361-
if isinstance(last_segment, str) and isinstance(current, Sequence) and last_segment.isdigit():
367+
if isinstance(last_segment, str) and isinstance(current, Sequence) and last_segment.isdecimal():
362368
last_segment = int(last_segment)
363369

364370
if isinstance(last_segment, int):

dpath/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class MergeType(IntFlag):
4646
replaces the destination in this situation."""
4747

4848

49-
PathSegment = Union[int, str]
49+
PathSegment = Union[int, str, bytes]
5050
"""Type alias for dict path segments where integers are explicitly casted."""
5151

5252
Filter = Callable[[Any], bool]

dpath/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "2.1.3"
1+
VERSION = "2.1.4"

tests/test_new.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ def test_set_list_with_dict_int_ambiguity():
5252
assert d == expected
5353

5454

55+
def test_int_segment_list_type_check():
56+
d = {}
57+
dpath.new(d, "a/b/0/c/0", "hello")
58+
assert 'b' in d.get("a", {})
59+
assert isinstance(d["a"]["b"], list)
60+
assert len(d["a"]["b"]) == 1
61+
assert 'c' in d["a"]["b"][0]
62+
assert isinstance(d["a"]["b"][0]["c"], list)
63+
assert len(d["a"]["b"][0]["c"]) == 1
64+
65+
66+
def test_int_segment_dict_type_check():
67+
d = {"a": {"b": {"0": {}}}}
68+
dpath.new(d, "a/b/0/c/0", "hello")
69+
assert "b" in d.get("a", {})
70+
assert isinstance(d["a"]["b"], dict)
71+
assert '0' in d["a"]["b"]
72+
assert 'c' in d["a"]["b"]["0"]
73+
assert isinstance(d["a"]["b"]["0"]["c"], list)
74+
75+
5576
def test_set_new_list_path_with_separator():
5677
# This test kills many birds with one stone, forgive me
5778
dict = {

0 commit comments

Comments
 (0)