Skip to content

fix(config): normalize Config.plugins entries with clear errors (#6440)#6445

Open
ChinmayShringi wants to merge 2 commits intoreflex-dev:mainfrom
ChinmayShringi:fix/normalize-plugins-list-6440
Open

fix(config): normalize Config.plugins entries with clear errors (#6440)#6445
ChinmayShringi wants to merge 2 commits intoreflex-dev:mainfrom
ChinmayShringi:fix/normalize-plugins-list-6440

Conversation

@ChinmayShringi
Copy link
Copy Markdown

@ChinmayShringi ChinmayShringi commented May 3, 2026

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

closes #6440

Description

Passing a Plugin class instead of an instance to rx.Config(plugins=...) previously failed deep in the compiler with:

TypeError: Plugin.get_stylesheet_paths() missing 1 required positional argument: 'self'

This PR adds Config._normalize_plugins() (mirroring the existing _normalize_disable_plugins()) that runs in _post_init and:

  • Auto-instantiates bare Plugin subclasses so plugins=[SitemapPlugin] behaves like plugins=[SitemapPlugin()]. This matches the lenient handling already in place for disable_plugins.
  • Raises ConfigError naming the offending value for non-Plugin entries (e.g. a stray string), instead of failing later with a confusing stylesheet-compilation traceback.
  • Raises ConfigError naming the class when a Plugin subclass's __init__ requires arguments, so the user knows to pass an instance.

The issue author proposed two options; I went with option 2 (auto-instantiate) since that is already the semantic of disable_plugins and avoids breaking ergonomic call sites that omit ().

Repro before/after

import reflex as rx
config = rx.Config(
    app_name="repro_sitemap",
    plugins=[rx.plugins.SitemapPlugin, rx.plugins.TailwindV4Plugin()],
)

Before: TypeError: Plugin.get_stylesheet_paths() missing 1 required positional argument: 'self' deep inside compiler/_compile_root_stylesheet.

After: Config builds successfully with both plugins instantiated.

rx.Config(app_name="repro", plugins=["not-a-plugin"])
# ConfigError: reflex.Config.plugins must contain Plugin instances, but got
# 'not-a-plugin' of type str. Pass an instance, e.g. plugins=[SitemapPlugin()].

Test plan

  • uv run pytest tests/units/test_config.py (82 passed, 4 new cases under TestPluginsNormalization)
  • uv run ruff check packages/reflex-base/src/reflex_base/config.py tests/units/test_config.py
  • uv run pyright packages/reflex-base/src/reflex_base/config.py tests/units/test_config.py (0 errors)

New tests cover:

  1. Plugin instance passthrough.
  2. Plugin class auto-instantiation.
  3. Non-Plugin value raises ConfigError mentioning reflex.Config.plugins.
  4. Plugin subclass requiring constructor args raises ConfigError naming the class.

…6440)

Passing a Plugin class instead of an instance to rx.Config(plugins=...)
previously failed deep in the compiler with:
  TypeError: Plugin.get_stylesheet_paths() missing 1 required positional argument: 'self'

Now Config._normalize_plugins() runs in _post_init and:
- auto-instantiates bare Plugin subclasses (matches disable_plugins
  semantics), so plugins=[SitemapPlugin] behaves like plugins=[SitemapPlugin()]
- raises ConfigError naming the offending entry for non-Plugin values
- raises ConfigError naming the class for Plugin subclasses whose
  __init__ requires arguments

Closes reflex-dev#6440
@ChinmayShringi ChinmayShringi requested a review from a team as a code owner May 3, 2026 17:54
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 3, 2026

Greptile Summary

Adds Config._normalize_plugins() to auto-instantiate bare Plugin subclasses and raise ConfigError for invalid plugins entries, fixing the confusing deep TypeError from issue #6440. The implementation mirrors the existing _normalize_disable_plugins pattern and is well-tested with 4 new cases.

Confidence Score: 4/5

Safe to merge; the fix is well-scoped and the only concern is a P2 edge case in error messaging.

Only P2 findings present — broad TypeError catch could produce a misleading error message in rare internal-TypeError scenarios, but the chained cause preserves the real error. No logic errors, no security issues, tests cover all new branches.

No files require special attention beyond the minor TypeError handling note in config.py.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/config.py Adds _normalize_plugins() called in _post_init, mirrors _normalize_disable_plugins. TypeError catch for bad constructors is slightly broad but chained cause preserves real error.
tests/units/test_config.py Adds TestPluginsNormalization with 4 focused cases covering instance passthrough, class auto-instantiation, invalid value, and class requiring args. Coverage is complete for the new code path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[_post_init called] --> B[update_from_env]
    B --> C[_normalize_plugins]
    C --> D{For each entry in plugins}
    D --> E{isinstance entry Plugin?}
    E -->|yes| F[keep as-is]
    E -->|no| G{issubclass entry Plugin?}
    G -->|yes| H[call entry]
    H -->|success| I[auto-instantiated instance]
    H -->|TypeError| J[raise ConfigError: requires constructor args]
    G -->|no| K[raise ConfigError: not a Plugin entry]
    F --> L[append to normalized]
    I --> L
    L --> D
    D -->|done| M[self.plugins = normalized]
    M --> N[_normalize_disable_plugins]
    N --> O[_add_builtin_plugins]
Loading

Reviews (1): Last reviewed commit: "fix(config): normalize plugins entries w..." | Re-trigger Greptile

Comment on lines +403 to +411
try:
normalized.append(entry())
except TypeError as exc:
msg = (
f"reflex.Config.plugins entry {entry.__name__!r} is a Plugin "
f"class that requires constructor arguments; pass an instance "
f"instead, e.g. plugins=[{entry.__name__}(...)]."
)
raise ConfigError(msg) from exc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 TypeError catch is too broad

TypeError can be raised inside a Plugin's __init__ for reasons unrelated to missing arguments (e.g. an internal type mismatch). When that happens the user sees "requires constructor arguments; pass an instance instead" even though the real failure is a bug inside __init__. The from exc chain surfaces the real cause, but the primary message is misleading.

A tighter guard would inspect inspect.signature before calling the constructor:

import inspect

elif isinstance(entry, type) and issubclass(entry, Plugin):
    sig = inspect.signature(entry.__init__)
    required = [
        p for p in list(sig.parameters.values())[1:]  # skip 'self'
        if p.default is inspect.Parameter.empty
        and p.kind not in (p.VAR_POSITIONAL, p.VAR_KEYWORD)
    ]
    if required:
        msg = (
            f"reflex.Config.plugins entry {entry.__name__!r} is a Plugin "
            f"class that requires constructor arguments; pass an instance "
            f"instead, e.g. plugins=[{entry.__name__}(...)]."
        )
        raise ConfigError(msg)
    normalized.append(entry())

This raises ConfigError before attempting construction, so any unrelated TypeError from inside __init__ propagates as-is rather than being swallowed into the misleading message.

Comment on lines +407 to +409
f"reflex.Config.plugins entry {entry.__name__!r} is a Plugin "
f"class that requires constructor arguments; pass an instance "
f"instead, e.g. plugins=[{entry.__name__}(...)]."
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

instead of saying "class that requires constructor arguments;", just mention that the plugin could not be instantiated and may require arguments.

i dont really like the complex suggestion from greptile

Copy link
Copy Markdown
Author

@ChinmayShringi ChinmayShringi May 4, 2026

Choose a reason for hiding this comment

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

Done in 75a2159, reworded to "could not be instantiated and may require arguments" so we don't pre-diagnose the cause when the underlying TypeError could be from something else inside init. Skipped the inspect.signature suggestion as you preferred.

Comment thread tests/units/test_config.py Outdated
Comment on lines +563 to +564
class TestPluginsNormalization:
"""Tests for the plugins config option normalization (issue #6440)."""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

in this repo, we don't structure pytest cases in classes that don't have any shared fixtures or data. just define the test functions here at the module level.

Copy link
Copy Markdown
Author

@ChinmayShringi ChinmayShringi May 4, 2026

Choose a reason for hiding this comment

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

Done in 75a2159, flattened TestPluginsNormalization into four module-level test functions. The class had no shared fixtures or data, so it was just unnecessary nesting.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 4, 2026

Merging this PR will not alter performance

✅ 17 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing ChinmayShringi:fix/normalize-plugins-list-6440 (f8f9b80) with main (70ab07e)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

- Reword TypeError message to be less prescriptive about cause
- Flatten TestPluginsNormalization class into module-level test
  functions per repo convention
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.

Error message when passing Plugin class in rx.Config is terrible

2 participants