Skip to content

Add include_regex_paths to DeepDiff - #620

Open
ChrisJr404 wants to merge 1 commit into
qlustered:devfrom
ChrisJr404:feature/include-regex-paths
Open

ChrisJr404 wants to merge 1 commit into
qlustered:devfrom
ChrisJr404:feature/include-regex-paths

Conversation

@ChrisJr404

@ChrisJr404 ChrisJr404 commented Aug 24, 2026 •

Copy link
Copy Markdown

Closes #395.

This adds include_regex_paths, the regex counterpart to exclude_regex_paths. You give it one or more regexes (raw strings or compiled objects) and only the paths that match end up in the report, so it lines up with the exact include_paths and the glob include_glob_paths we already have.

>>> from deepdiff import DeepDiff
>>> t1 = [{'a': 1, 'b': 2}, {'c': 4, 'b': 5}]
>>> t2 = [{'a': 9, 'b': 3}, {'c': 4, 'b': 8}]
>>> DeepDiff(t1, t2, include_regex_paths=r"root\[\d+\]\['b'\]")
{'values_changed': {"root[0]['b']": {'new_value': 3, 'old_value': 2}, "root[1]['b']": {'new_value': 8, 'old_value': 5}}}

Like exclude_regex_paths it runs re.search on the path, and a regex that matches a parent keeps that whole subtree the same way excluding a parent drops it. When the option is not set nothing changes, so existing behavior stays the same. I added tests, a changelog line, and docs right next to the exclude_regex_paths section.

This is the counterpart of exclude_regex_paths for issue qlustered#395. You pass one
or more regexes and only the paths that match, or that live under a matching
path, end up in the report. A match on a parent keeps its whole subtree the
same way excluding a parent drops it.

@seperman seperman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review: #620 Add include_regex_paths to DeepDiff

Summary: This adds a new include_regex_paths parameter, which is compiled the same way as exclude_regex_paths. It is applied at report time: _skip_report_for_include_regex keeps a change only if its path, or an ancestor's path, matches one of the regexes (re.search). It also adds docs, a CHANGELOG line and tests, and implements the feature asked for in #395. The API shape matches exclude_regex_paths: str, list or compiled patterns. Because the filter only applies at report time, a few cases behave differently from include_paths/include_glob_paths; details below.

Overlap with other PRs

  • No other PR implements #395. (#389, merged 2023, also comes up in a search for that issue, but it changed include_paths substring matching and is unrelated.)
  • Textual conflicts: only CHANGELOG.md, with #630 and #618 (trivial). deepdiff/diff.py and diff_doc.rst auto-merge with #618, #630, #634 and #636.

Security

No security concerns found in static review. The patterns are supplied by the caller and compiled via the existing convert_item_or_items_into_compiled_regexes_else_none. ReDoS exposure is the same as for exclude_regex_paths: a caller-supplied regex run against paths built from data keys. The ancestor walk does run each pattern against every ancestor path of every reported change, so a slow pattern costs more here than with exclude_regex_paths.

Correctness

  • Changes reported at an ancestor level are silently dropped. Some code paths report a change at a level above the included path. One is the threshold_to_diff_deeper shortcut in _diff_dict, which reports values_changed for the whole dict (deepdiff/diff.py:784-797). Type changes and unpaired ignore_order items do the same. Their paths don't match a regex aimed at a deeper path, so they are filtered out. Example from reading the code: DeepDiff({'a': {'b': 1}}, {'a': {'c': 1}}, include_regex_paths=r"\['b'\]"). root['a'] has key union 2 and intersection 0, so values_changed is reported at root['a'] and then filtered, and the result is {}. include_paths=["root['a']['b']"] instead prunes keys via _skip_this_key, so the union is 1 and dictionary_item_removed root['a']['b'] is reported. Possible fixes: drop or relax the threshold shortcut when include_regex_paths is set, or at least document threshold_to_diff_deeper=0 as the way to use this option.
  • None paths. level.path() returns None for unrepresentable steps: non-subscriptable iterables (NonSubscriptableIterableRelationship), or dict keys whose repr can't be evaluated back with literal_eval. regex.search(up_path) at diff.py:521 would then raise TypeError. Suggest skipping None paths (if up_path is not None and any(...)). _skip_this has the same latent issue for exclude_regex_paths, but there it only triggers when that option is used.
  • ignore_order distance sub-diffs. _get_rough_distance_of_hashed_objs (diff.py:1393-1411) builds a child DeepDiff with _parameters, so the include filter runs against item-relative paths (root, root['x'], …). For example, in test_include_regex_paths_with_ignore_order the 'b'/'c' sub-diff reports at root. That doesn't match root\['letters'\], so the distance comes out as 0 and the items get paired. The expected values_changed in that test seems to depend on this. When there are several candidates, pairing becomes order-based. include_glob_paths shares this behaviour, so it may be acceptable, but it is worth knowing. Similarly, include_regex_paths isn't in DEEPHASH_PARAM_KEYS (diff.py:111). Unlike include_paths/include_glob_paths, item hashes under ignore_order therefore still cover non-included content.
  • Multiprocessing. Subtree workers apply the filter against subtree-relative paths, and _dispatch_subtree_jobs re-applies only _skip_this (diff.py:1744). With multiprocessing=True + ignore_order=True, results could differ from a serial run. include_paths/include_glob_paths have the same gap today. One option is to treat include_regex_paths as unsafe in _subtree_parallel_safe.
  • Question on combination semantics: with include_paths (or include_glob_paths) together with include_regex_paths, the result is the intersection, because _skip_this prunes first and the regex filter runs after. Is that intended, rather than a union? The docs don't say.
  • Unlike include_paths, this doesn't prune traversal, so there is no speed-up. That is reasonable for regexes, but worth a sentence in the docs.

Tests

  • The new tests (tests/test_diff_include_regex_paths.py) cover str, list and compiled inputs, ancestor matching, added/removed, the default, and ignore_order. All of them would fail on dev, which raises ValueError for the unknown kwarg.
  • Missing: the threshold case above, combination with include_paths/exclude_regex_paths, custom_report_result filtering, and an unrepresentable-path case.
  • No CI run is shown for this branch.

Suggestions

  • Handle or document the ancestor-level drop (threshold shortcut), and guard None paths.
  • Minor: the list of valid parameters in the ValueError message (diff.py:224-233) isn't updated, though the glob options are missing there too.

AI-assisted static review; the code was not executed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants