Skip to content

Implement rx input overrides - #1190

Open
philippjfr wants to merge 7 commits into
mainfrom
feat/rx-input-overrides
Open

Implement rx input overrides#1190
philippjfr wants to merge 7 commits into
mainfrom
feat/rx-input-overrides

Conversation

@philippjfr

@philippjfr philippjfr commented Sep 11, 2026

Copy link
Copy Markdown
Member

When building complex reactive graphs you sometimes want to test assumptions or temporarily freeze specific values. To achieve this you have to fully rebuild the rx graph from scratch right now, this PR instead offers a mechanism to register overrides on a node that supersede the actual inputs.

This PR adds .rx.overrides, a mutable mapping on a node that addresses the inputs it was wired with, by keyword name or by positional index:

import param

fx = param.rx(2)
value = param.rx(100).rx.pipe(lambda price, fx: price * fx, fx=fx)
value.rx.value  # 200

value.rx.overrides['fx'] = 1
value.rx.value  # 100

fx.rx.value = 5
value.rx.value  # still 100, the override masks the update

value.rx.overrides['fx'] = None  # None unmasks
value.rx.value  # 500

The interposition is local to this node's consumption of the input, so every other consumer of fx keeps seeing the live value and is neither notified nor recomputed. Because the override replaces the input before it is resolved, it lands ahead of every guard the node applies to its inputs, and masks an input that failed, one that has not resolved yet and one that skipped, without the node having to be wired with process_failures=True:

broken = param.rx(0, error_mode="propagate").rx.pipe(lambda divisor: 1 / divisor)
total = param.rx(7).rx.pipe(lambda value, extra: value + extra, extra=broken)
isinstance(total.rx.value, param.ReactiveError)  # True

total.rx.overrides['extra'] = 5
total.rx.value  # 12

Overrides are node-local, like .rx.meta: a derived node has its own, empty mapping, and only an input the node was wired with can be overridden, so an unknown key raises KeyError naming the inputs that exist. Overriding is a plain replacement; a consumer that needs to merge an override into the live value computes the merged value itself.

AI Disclosure

Tool & Model: Kilo Code + amazon-bedrock/us.anthropic.claude-opus-5
Usage: Used to spike the invalidation options and measure them, implement the feature, add tests, run the test suite and type checkers, and draft this description.

  • I have tested all AI-generated content in my PR.
  • I take responsibility for all AI-generated content in my PR.

Checklist

  • Tests added and are passing
  • Added documentation

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.69697% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.62%. Comparing base (b14e89b) to head (c7ce27a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
param/reactive.py 94.65% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1190      +/-   ##
==========================================
+ Coverage   87.27%   87.62%   +0.34%     
==========================================
  Files           9        9              
  Lines        5550     5679     +129     
==========================================
+ Hits         4844     4976     +132     
+ Misses        706      703       -3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hoxbro hoxbro 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.

Is it expected for an overridden parameter to still trigger events? I would imagine if you have made something static it does not make sense to trigger something downstream, could be wrong though.

import param

a = param.rx(2)
b = param.rx(10) * a 
c = param.rx(10) * b

b_events, c_events = [], []
b.rx.watch(b_events.append)
c.rx.watch(c_events.append)

b.rx.overrides[0] = 1 

a.rx.value = 5
a.rx.value = 9

print("b:", b.rx.value, "c:", c.rx.value) # b: 10 c: 100
print("b_events:", b_events)  # b_events: [10, 10, 10]
print("c_events:", c_events)  # c_events: [100, 100, 100]

Comment thread param/reactive.py Outdated
Comment thread param/reactive.py Outdated
Comment thread param/reactive.py Outdated
Comment thread doc/reference/param/reactive.md Outdated
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