Repository navigation
Add include_regex_paths to DeepDiff - #620
ChrisJr404 wants to merge 1 commit into
Conversation
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
left a comment
There was a problem hiding this comment.
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_pathssubstring matching and is unrelated.) - Textual conflicts: only
CHANGELOG.md, with #630 and #618 (trivial).deepdiff/diff.pyanddiff_doc.rstauto-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_deepershortcut in_diff_dict, which reportsvalues_changedfor the whole dict (deepdiff/diff.py:784-797). Type changes and unpairedignore_orderitems 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, sovalues_changedis reported atroot['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 anddictionary_item_removed root['a']['b']is reported. Possible fixes: drop or relax the threshold shortcut wheninclude_regex_pathsis set, or at least documentthreshold_to_diff_deeper=0as the way to use this option. Nonepaths.level.path()returnsNonefor unrepresentable steps: non-subscriptable iterables (NonSubscriptableIterableRelationship), or dict keys whose repr can't be evaluated back withliteral_eval.regex.search(up_path)atdiff.py:521would then raiseTypeError. Suggest skippingNonepaths (if up_path is not None and any(...))._skip_thishas the same latent issue forexclude_regex_paths, but there it only triggers when that option is used.ignore_orderdistance sub-diffs._get_rough_distance_of_hashed_objs(diff.py:1393-1411) builds a childDeepDiffwith_parameters, so the include filter runs against item-relative paths (root,root['x'], …). For example, intest_include_regex_paths_with_ignore_orderthe'b'/'c'sub-diff reports atroot. That doesn't matchroot\['letters'\], so the distance comes out as 0 and the items get paired. The expectedvalues_changedin that test seems to depend on this. When there are several candidates, pairing becomes order-based.include_glob_pathsshares this behaviour, so it may be acceptable, but it is worth knowing. Similarly,include_regex_pathsisn't inDEEPHASH_PARAM_KEYS(diff.py:111). Unlikeinclude_paths/include_glob_paths, item hashes underignore_ordertherefore still cover non-included content.- Multiprocessing. Subtree workers apply the filter against subtree-relative paths, and
_dispatch_subtree_jobsre-applies only_skip_this(diff.py:1744). Withmultiprocessing=True+ignore_order=True, results could differ from a serial run.include_paths/include_glob_pathshave the same gap today. One option is to treatinclude_regex_pathsas unsafe in_subtree_parallel_safe. - Question on combination semantics: with
include_paths(orinclude_glob_paths) together withinclude_regex_paths, the result is the intersection, because_skip_thisprunes 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, andignore_order. All of them would fail ondev, which raisesValueErrorfor the unknown kwarg. - Missing: the threshold case above, combination with
include_paths/exclude_regex_paths,custom_report_resultfiltering, 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
Nonepaths. - Minor: the list of valid parameters in the
ValueErrormessage (diff.py:224-233) isn't updated, though the glob options are missing there too.
AI-assisted static review; the code was not executed.
Closes #395.
This adds
include_regex_paths, the regex counterpart toexclude_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 exactinclude_pathsand the globinclude_glob_pathswe already have.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.