Feature: add partial pattern matching (partial=True) for callbacks#3765
Feature: add partial pattern matching (partial=True) for callbacks#3765i-murray wants to merge 6 commits into
Conversation
Allow pattern-matching callbacks to match components whose IDs contain a superset of the pattern's keys. Opt-in per dependency via partial=True on Input, Output, and State. - Python: partial flag on DashDependency, subset-aware _id_matches() - JS/TS: addPartialPattern, partialIdMatch, resolvePartialDeps, updated getWatchedKeys, getCallbacksByInput, getCallbackByOutput, getUnfilteredLayoutCallbacks, resolveDeps, isMultiValued - Literal-only partial patterns are implicitly multi-valued - hasPartialPatterns guard for zero-cost when unused - 16 unit tests + 4 integration tests
|
If, instead of Additionally, if this feature is integrated, the relevant Dash documentation Pattern-Matching Callbacks would need to be updated. |
|
I like this feature, but yea |



Feature: Partial Pattern Matching (
partial=True) for CallbacksDescription
Dash's pattern-matching callbacks currently require the pattern and component IDs to have exactly the same set of keys. This means a pattern like
{"type": "btn"}will not match a component with ID{"type": "btn", "index": 1}— even though the component clearly has the key and value the pattern cares about.This PR adds an opt-in
partial=Trueflag onInput,Output, andStatethat relaxes the key-matching constraint: a pattern's keys only need to be a subset of the component's keys. Extra keys on the component are ignored.Closes #3764
Usage
This fires for any component whose ID contains
"type": "btn", regardless of other keys:{"type": "btn", "index": 1}{"type": "btn", "page": "home", "section": "main"}{"type": "btn", "index": 2, "tab": "first"}Works with wildcards:
Design choices
Input/Output/Stateindependently opts in, so partial and non-partial deps can coexist in the same callback.{"type": "btn"}withpartial=Truecan match multiple components, so the callback receives a list (same asALL). Patterns withMATCHstill resolve one-at-a-time.hasPartialPatternsboolean guard on the callback graph ensures apps withoutpartial=Truepay no additional overhead. All new loops are skipped entirely.Contributor Checklist
partialattribute toDashDependency,Input,Output,State(Python)_id_matches()for subset key matching whenpartial=Truepartialflag viato_dict()and propagate throughinsert_callback()addPartialPattern()andpartialIdMatch()to frontend (dependencies.js)computeGraphs()to detect partial flags and sethasPartialPatternsgetWatchedKeys(),getCallbackByOutput(),getUnfilteredLayoutCallbacks()with partial pattern loopsresolvePartialDeps()and updategetCallbacksByInput()independencies_ts.tsisMultiValued()for implicit multi-valued partial patternsrequestedCallbacks.tstype compatibility for updatedisMultiValuedsignaturehasPartialPatternsguard to all new loops for zero-cost when unusedtests/unit/test_partial_matching.py— subset matching, wildcards, serialization, bidirectional checks, negative casestests/integration/callbacks/test_partial_wildcards.py— basic partial match, ALL collection, literal filtering, mixed key setsOptionals
CHANGELOG.mdPerformance
Partial matching requires cross-keyStr searches in several hot-path functions that were previously$O(1)$ hash lookups:
getWatchedKeys()getCallbacksByInput()getCallbackByOutput()getUnfilteredLayoutCallbacks()Where$K$ = number of distinct keyStr entries in the pattern index, $p$ = number of keys in the pattern (typically 1–5), and $N$ = number of components in a layout chunk.
hasPartialPatternsguardTo ensure zero cost for apps that don't use$O(D)$ time (where $D$ = total dependencies). Every new loop is guarded:
partial=True,computeGraphs()sets ahasPartialPatternsboolean on the graph in