Skip to content

fix: Validate that reactive and render decorated functions have no required parameters#2200

Merged
schloerke merged 6 commits intoposit-dev:mainfrom
mvanhorn:osc/1819-validate-decorator-params
Mar 26, 2026
Merged

fix: Validate that reactive and render decorated functions have no required parameters#2200
schloerke merged 6 commits intoposit-dev:mainfrom
mvanhorn:osc/1819-validate-decorator-params

Conversation

@mvanhorn
Copy link
Contributor

Summary

Adds runtime validation to @reactive.calc, @reactive.effect, and @render.* decorators to raise a clear TypeError when the decorated function has required parameters. These decorators expect zero-argument functions.

Why this matters

Users who forget to remove parameters from their decorated functions get confusing runtime errors later (e.g., missing argument errors during reactive flush). This validation catches the mistake at decoration time with a clear message like: @reactive.calc expected a function with no required parameters, but my_func() has required parameter(s): x, y.

Per #1819, this is a common mistake for users who don't enable type checking.

Changes

  • shiny/_utils.py: Added validate_no_params(fn, decorator_name) utility using inspect.signature(). Accepts functions with *args, **kwargs, or default-valued parameters.
  • shiny/reactive/_reactives.py: Added validation to Calc_.__init__ and Effect_.__init__
  • shiny/render/renderer/_renderer.py: Added validation to Renderer.__call__
  • tests/pytest/test_reactives.py: 6 tests covering calc/effect with required params, no params, and default params
  • tests/pytest/test_renderer.py: 3 tests covering render.text with the same patterns

Testing

Added 9 unit tests. Run with pytest tests/pytest/test_reactives.py tests/pytest/test_renderer.py.

Fixes #1819

This contribution was developed with AI assistance (Claude Code).

…quired parameters

Add runtime validation to @reactive.calc, @reactive.effect, and @render.*
decorators that raises TypeError when the decorated function has required
parameters. Reactive and render functions should take no arguments.

Closes posit-dev#1819

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator

@schloerke schloerke left a comment

Choose a reason for hiding this comment

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

@mvanhorn Thank you (and Claude)! 😅

Request:

  • If default parameters are supplied, could we also display a "anti-pattern" warning?
    • This will help steer users away from adding parameters that will never be leveraged... Or to wrap their functions in lambda to clearly define the contract.
    • In the future, we could turn it into an error. 1 year?
  • Add a CHANGELOG.md entry

schloerke and others added 5 commits March 26, 2026 14:13
Add a UserWarning in validate_no_params when functions have parameters
with default values, since Shiny never supplies arguments to reactive
or render decorated functions. Also add comprehensive unit tests for
validate_no_params covering all logic paths (skip_names, var args,
error/warning messages) and a CHANGELOG entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a `stacklevel` keyword argument so callers with deeper call stacks
can point warnings at the correct user code frame. Also add a
`_shiny_params_validated` flag on validated functions to prevent
duplicate warnings when decorators are stacked (e.g., @reactive.poll
internally uses @reactive.calc via @functools.wraps).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass stacklevel=5 from Calc_.__init__ and Effect_.__init__ so
  warnings point to user code, not internal create_calc/create_effect.
- Pass stacklevel=5 from Renderer.__call__ to account for the extra
  frame through subclass __init__ -> super().__init__ -> self(_fn).
- Use f"render.{type(self).__name__}" so error/warning messages say
  @render.text instead of @text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend validate_no_params coverage to all decorators that expect
zero-argument functions:
- reactive.poll (in wrapper)
- reactive.file_reader (in wrapper)
- reactive.event (in decorator)
- render.download (in __call__, which overrides Renderer.__call__
  and bypasses the base class validation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…edup

- Stacklevel tests for all 7 callsites verifying warnings point to
  the correct user file
- Render prefix tests for error and warning messages
- fn.__name__ in warning suggestion text
- Validation tests (reject/accept/warn) for reactive.event,
  reactive.poll, reactive.file_reader, and render.download
- Stacked decorator tests verifying exactly one warning when
  @calc/@effect is combined with @event, and when @poll/@file_reader
  internally chains through @reactive.calc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@schloerke
Copy link
Collaborator

** Implementing my requests. Yay Claude

@schloerke
Copy link
Collaborator

Thank you, @mvanhorn !

@schloerke schloerke enabled auto-merge (squash) March 26, 2026 21:49
@schloerke schloerke merged commit 9b8d02f into posit-dev:main Mar 26, 2026
67 checks passed
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.

Verify no arguments are given to reactive and render decorators

2 participants