From 5f3eb6b3931ed3f236ba2bb13b49239829534413 Mon Sep 17 00:00:00 2001 From: Muneeb Sheikh Date: Sun, 11 Oct 2026 13:10:51 -0400 Subject: [PATCH] Remove cleared placeholders when deleting list suffixes --- dpath/__init__.py | 17 +++++++++++------ tests/test_delete.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/dpath/__init__.py b/dpath/__init__.py index 2bc9942..197d8ee 100644 --- a/dpath/__init__.py +++ b/dpath/__init__.py @@ -23,6 +23,7 @@ "Creator", ] +import builtins from collections.abc import MutableMapping, MutableSequence from typing import Union, List, Any, Callable, Optional @@ -79,6 +80,7 @@ def delete(obj: MutableMapping, glob: Glob, separator="/", afilter: Filter | Non found to delete. """ globlist = _split_path(glob, separator) + cleared_indices = {} def f(obj, pair, counter): (path_segments, value) = pair @@ -107,19 +109,22 @@ def f(obj, pair, counter): # truly removed without affecting the ordering of # remaining items. # - # Note: In order to achieve proper behavior we are - # relying on the reverse iteration of - # non-dictionaries from segments.kvs(). - # Otherwise we'd be unable to delete all the tails - # of a list and end up with None values when we - # don't need them. del parent[key] + # Earlier matches may have cleared positions that are + # now at the tail. Remove only those positions, keeping + # any original None values and indices of retained items. + cleared = cleared_indices.get(id(parent), builtins.set()) + while parent and len(parent) - 1 in cleared: + cleared.remove(len(parent) - 1) + del parent[-1] + else: # This key can't be removed completely because it # would affect the order of items that remain in our # result. parent[key] = None + cleared_indices.setdefault(id(parent), builtins.set()).add(int(key)) counter[0] += 1 diff --git a/tests/test_delete.py b/tests/test_delete.py index c7879b0..34d9f4d 100644 --- a/tests/test_delete.py +++ b/tests/test_delete.py @@ -54,3 +54,38 @@ def afilter(x): assert dict['a']['b'] == 0 assert dict['a']['c'] == 1 assert 'd' not in dict['a'] + + +def test_delete_all_list_elements(): + data = {'items': [0, 1, 2]} + + assert dpath.delete(data, 'items/*') == 3 + assert data == {'items': []} + + +def test_delete_list_suffix(): + data = {'items': [0, 1, 2]} + + assert dpath.delete(data, 'items/[12]') == 2 + assert data == {'items': [0]} + + +def test_delete_nested_list_elements(): + data = {'items': [[0, 1], [2, 3]]} + + assert dpath.delete(data, 'items/*/*') == 4 + assert data == {'items': [[], []]} + + +def test_delete_interior_list_element_keeps_indices(): + data = {'items': [0, 1, 2]} + + assert dpath.delete(data, 'items/1') == 1 + assert data == {'items': [0, None, 2]} + + +def test_delete_list_suffix_keeps_original_none(): + data = {'items': [None, 1, 2]} + + assert dpath.delete(data, 'items/[12]') == 2 + assert data == {'items': [None]}