From 5d80d98f110193fe218ec68394bec452eba7887a Mon Sep 17 00:00:00 2001 From: moomoohk <2220203+moomoohk@users.noreply.github.com> Date: Mon, 5 Dec 2022 12:43:12 +0200 Subject: [PATCH 1/5] Implement Placeholder class --- dpath/segments.py | 10 ++++++++-- dpath/types.py | 18 ++++++++++++++++++ tests/test_new.py | 9 +++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/dpath/segments.py b/dpath/segments.py index 7a48817..54d2eeb 100644 --- a/dpath/segments.py +++ b/dpath/segments.py @@ -4,7 +4,10 @@ from dpath import options from dpath.exceptions import InvalidGlob, InvalidKeyName, PathNotFound -from dpath.types import PathSegment, Creator, Hints, Glob, Path, SymmetricInt +from dpath.types import PathSegment, Creator, Hints, Glob, Path, SymmetricInt, Placeholder + + +PLACEHOLDER = Placeholder() def make_walkable(node) -> Iterator[Tuple[PathSegment, Any]]: @@ -254,7 +257,7 @@ def match(segments: Path, glob: Glob): return False -def extend(thing: List, index: int, value=None): +def extend(thing: List, index: int, value=PLACEHOLDER): """ Extend a sequence like thing such that it contains at least index + 1 many elements. The extension values will be None (default). @@ -351,6 +354,9 @@ def set( else: raise + if current[segment] == PLACEHOLDER and creator is not None: + creator(current, segments, i, hints) + current = current[segment] if i != length - 1 and leaf(current): raise PathNotFound(f"Path: {segments}[{i}]") diff --git a/dpath/types.py b/dpath/types.py index 7bf3d2d..3fe588a 100644 --- a/dpath/types.py +++ b/dpath/types.py @@ -33,6 +33,24 @@ def __str__(self): return str(int(self)) +class Placeholder: + """ + Placeholder object to pad out newly created lists. + Should be effectively equivalent to None. + """ + def __repr__(self): + return f"<{type(self).__name__}>" + + def __eq__(self, other): + if isinstance(other, Placeholder) or other is None: + return True + + return False + + def __hash__(self): + return hash(None) + + class MergeType(IntFlag): ADDITIVE = auto() """List objects are combined onto one long list (NOT a set). This is the default flag.""" diff --git a/tests/test_new.py b/tests/test_new.py index 15b21c6..52ff734 100644 --- a/tests/test_new.py +++ b/tests/test_new.py @@ -91,3 +91,12 @@ def mycreator(obj, pathcomp, nextpathcomp, hints): assert isinstance(d['a'], list) assert len(d['a']) == 3 assert d['a'][2] == 3 + + +def test_new_overwrite_placeholder(): + a = {} + dpath.new(a, ['b'], []) + dpath.new(a, ['b', 3], 5) + dpath.new(a, ['b', 1, "c"], 5) + + assert a["b"][1]["c"] == 5 From b4a074f3b2fc4ca6f38b55767640b26c554169ec Mon Sep 17 00:00:00 2001 From: moomoohk <2220203+moomoohk@users.noreply.github.com> Date: Mon, 5 Dec 2022 22:00:07 +0200 Subject: [PATCH 2/5] Implement functionality with option --- dpath/options.py | 1 + dpath/segments.py | 7 ++++--- tests/test_new.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/dpath/options.py b/dpath/options.py index 41f35c4..48827e7 100644 --- a/dpath/options.py +++ b/dpath/options.py @@ -1 +1,2 @@ ALLOW_EMPTY_STRING_KEYS = False +REPLACE_NONE_VALUES_IN_LISTS = False diff --git a/dpath/segments.py b/dpath/segments.py index 54d2eeb..bc9e5ed 100644 --- a/dpath/segments.py +++ b/dpath/segments.py @@ -257,7 +257,7 @@ def match(segments: Path, glob: Glob): return False -def extend(thing: List, index: int, value=PLACEHOLDER): +def extend(thing: List, index: int, value=None): """ Extend a sequence like thing such that it contains at least index + 1 many elements. The extension values will be None (default). @@ -342,6 +342,7 @@ def set( if isinstance(segment, str) and isinstance(current, Sequence) and segment.isdigit(): segment = int(segment) + create_current = False try: # Optimistically try to get the next value. This makes the # code agnostic to whether current is a list or a dict. @@ -350,11 +351,11 @@ def set( current[segment] except: if creator is not None: - creator(current, segments, i, hints) + create_current = True else: raise - if current[segment] == PLACEHOLDER and creator is not None: + if create_current or (options.REPLACE_NONE_VALUES_IN_LISTS and current[segment] is None): creator(current, segments, i, hints) current = current[segment] diff --git a/tests/test_new.py b/tests/test_new.py index 52ff734..4556287 100644 --- a/tests/test_new.py +++ b/tests/test_new.py @@ -1,4 +1,7 @@ +from nose2.tools.such import helper + import dpath +from dpath import options def test_set_new_separator(): @@ -94,6 +97,13 @@ def mycreator(obj, pathcomp, nextpathcomp, hints): def test_new_overwrite_placeholder(): + a = {} + dpath.new(a, ['b'], []) + dpath.new(a, ['b', 3], 5) + with helper.assertRaises(dpath.exceptions.PathNotFound): + dpath.new(a, ['b', 1, "c"], 5) + + options.REPLACE_NONE_VALUES_IN_LISTS = True a = {} dpath.new(a, ['b'], []) dpath.new(a, ['b', 3], 5) From 319b684da45e045bb6eadcf72b11a27f1be4ee3c Mon Sep 17 00:00:00 2001 From: moomoohk <2220203+moomoohk@users.noreply.github.com> Date: Mon, 5 Dec 2022 22:00:57 +0200 Subject: [PATCH 3/5] Remove placeholder class --- dpath/segments.py | 5 +---- dpath/types.py | 18 ------------------ tests/test_new.py | 2 +- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/dpath/segments.py b/dpath/segments.py index bc9e5ed..21aa2f6 100644 --- a/dpath/segments.py +++ b/dpath/segments.py @@ -4,10 +4,7 @@ from dpath import options from dpath.exceptions import InvalidGlob, InvalidKeyName, PathNotFound -from dpath.types import PathSegment, Creator, Hints, Glob, Path, SymmetricInt, Placeholder - - -PLACEHOLDER = Placeholder() +from dpath.types import PathSegment, Creator, Hints, Glob, Path, SymmetricInt def make_walkable(node) -> Iterator[Tuple[PathSegment, Any]]: diff --git a/dpath/types.py b/dpath/types.py index 3fe588a..7bf3d2d 100644 --- a/dpath/types.py +++ b/dpath/types.py @@ -33,24 +33,6 @@ def __str__(self): return str(int(self)) -class Placeholder: - """ - Placeholder object to pad out newly created lists. - Should be effectively equivalent to None. - """ - def __repr__(self): - return f"<{type(self).__name__}>" - - def __eq__(self, other): - if isinstance(other, Placeholder) or other is None: - return True - - return False - - def __hash__(self): - return hash(None) - - class MergeType(IntFlag): ADDITIVE = auto() """List objects are combined onto one long list (NOT a set). This is the default flag.""" diff --git a/tests/test_new.py b/tests/test_new.py index 4556287..4b8a5a2 100644 --- a/tests/test_new.py +++ b/tests/test_new.py @@ -96,7 +96,7 @@ def mycreator(obj, pathcomp, nextpathcomp, hints): assert d['a'][2] == 3 -def test_new_overwrite_placeholder(): +def test_new_overwrite_none_in_list(): a = {} dpath.new(a, ['b'], []) dpath.new(a, ['b', 3], 5) From cff529f91680d3983fc36652f933e22485067889 Mon Sep 17 00:00:00 2001 From: moomoohk <2220203+moomoohk@users.noreply.github.com> Date: Mon, 5 Dec 2022 22:06:03 +0200 Subject: [PATCH 4/5] Add documentation and bump version --- dpath/options.py | 2 ++ dpath/version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dpath/options.py b/dpath/options.py index 48827e7..a6b0654 100644 --- a/dpath/options.py +++ b/dpath/options.py @@ -1,2 +1,4 @@ ALLOW_EMPTY_STRING_KEYS = False REPLACE_NONE_VALUES_IN_LISTS = False +"""When creating a new subtree within a list, this option will cause dpath to treat None items as non-significant and +overwrite them. Default behavior will cause an exception to be thrown instead.""" diff --git a/dpath/version.py b/dpath/version.py index b777579..4260069 100644 --- a/dpath/version.py +++ b/dpath/version.py @@ -1 +1 @@ -VERSION = "2.1.2" +VERSION = "2.1.3" From 425fbabfa897d91e82088b952ec5e79eba191eb6 Mon Sep 17 00:00:00 2001 From: moomoohk <2220203+moomoohk@users.noreply.github.com> Date: Sat, 10 Dec 2022 17:53:16 +0200 Subject: [PATCH 5/5] Resolve flake8 issue --- dpath/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpath/options.py b/dpath/options.py index a6b0654..d9b4d68 100644 --- a/dpath/options.py +++ b/dpath/options.py @@ -1,4 +1,4 @@ ALLOW_EMPTY_STRING_KEYS = False REPLACE_NONE_VALUES_IN_LISTS = False -"""When creating a new subtree within a list, this option will cause dpath to treat None items as non-significant and +"""When creating a new subtree within a list, this option will cause dpath to treat None items as non-significant and \ overwrite them. Default behavior will cause an exception to be thrown instead."""