Upgrade dev tooling and fix docstring style - #6893
Conversation
uv.lock: ruff 0.15.12 -> 0.16.2, pyright 1.1.408 -> 1.1.411, typer 0.25.1 -> 0.27.1. ruff 0.16.3 published inside the 7 day exclude-newer window, so 0.16.2 is the newest resolvable release. ruff 0.16 (preview rules are enabled repo-wide): - D421 property-docstring-starts-with-verb: reword 53 property docstrings from "Get the X." / "Return the X." to "The X.", per the Google style guide the repo already follows. - PT003: drop the redundant scope="function" from five pytest_asyncio.fixture calls, which 0.16 now recognizes. - ASYNC119, RUF075: ignored. Both fire on deliberate patterns -- background handlers hold `async with self` across `yield`, and the state managers skip write-back when the `with` body raises. - RUF105, RUF201: ignored. Both are stylistic; RUF105's own docs call it opinionated, and migrating 190 `noqa` comments to `ruff: ignore` would drop them for other tooling and for older ruff. RUF201 would leave the config half codes, half names, since prefix selectors have no name form. pyright 1.1.411 narrows `x is None` on an `Any` value to `Any | None`, where it previously stayed `Any`. That surfaced two dead guards, both removed: `cls is not None` in `_isinstance()` (the line above already returns for None) and `instruction.argval is not None` in the dependency-tracking scanner, where the check moves inside the branch so it stops leaking None into the sibling branches. Its bundled typeshed also types `inspect.isgenerator`/`isasyncgen` as yielding `object`, so `chain_updates()` now declares `events: Any`, matching the runtime validation it delegates to. Also: annotate `Field.__init__`'s computed default as FIELD_TYPE, cast untyped `sass.compile()` to str, and import `ImportVar`/`unionize` from the modules that define them rather than re-exporting them through `reflex_base.vars.base`. typer 0.27 vendors its own copy of click, so `typer.main.get_command()` no longer returns a nominal `click.Command`. The objects stay structurally compatible (`reflex cloud` and the 289 hosting CLI tests pass), so the conversion is cast at its two call sites; the six test modules that duplicated the Typer-to-click preamble now share an `as_click_command()` helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLumacyNrCZDqvyptaVxkE
Greptile SummaryThis PR upgrades the locked Ruff, Pyright, and Typer development tools and resolves the resulting lint and type-check diagnostics.
Confidence Score: 5/5The PR appears safe to merge because no changed-code-triggered blocking or independently actionable non-blocking issue remains. The behaviorally relevant edits are type-only casts, annotations, and equivalent control-flow refactors, while the dependency lock changes are limited to the intended development tools.
|
| Filename | Overview |
|---|---|
| uv.lock | Updates only the locked Pyright, Ruff, and Typer versions; reported vulnerable dependency versions were already present on the base revision. |
| pyproject.toml | Adds documented Ruff ignores corresponding to intentional framework and state-manager behavior. |
| reflex/reflex.py | Adds a type-only cast for Typerβs vendored-Click command before preserving the existing cloud command registration. |
| reflex/compiler/compiler.py | Casts the untyped libsass filename compilation result to str without changing runtime behavior. |
| packages/reflex-base/src/reflex_base/event/processor/base_state_processor.py | Widens chain_updates typing to match its existing runtime validation while leaving event routing unchanged. |
| packages/reflex-base/src/reflex_base/vars/dep_tracking.py | Rewrites the IMPORT_NAME null guard for type narrowing while preserving the previous no-op behavior. |
| packages/reflex-base/src/reflex_base/vars/base.py | Adds a type annotation for computed field defaults and updates property docstrings without changing value construction. |
| tests/units/reflex_cli/v2/utils.py | Centralizes the repeated Typer-to-command adaptation used by hosting CLI tests. |
Reviews (1): Last reviewed commit: "Upgrade ruff, pyright, and typer; fix th..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
1 issue found across 59 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid β if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/reflex-base/src/reflex_base/vars/base.py">
<violation number="1" location="packages/reflex-base/src/reflex_base/vars/base.py:3522">
P3: The `default_value: FIELD_TYPE` annotation is unsound for the branch where `get_default_value_for_type` returns `None` for a non-optional type (e.g. `Field[SomeCustomClass]` where the class is not one of `TYPES_THAT_HAS_DEFAULT_VALUE`, a Mapping, Literal, or dataframe). In that case `default_value` is `None`, but `FIELD_TYPE` bound to the non-optional custom class excludes `None`, so the value does not inhabit the annotated type. The widening `annotated_type = annotated_type | None` runs after the annotation and rebinds the local `annotated_type` variable only; it does not make `None` assignable to the `FIELD_TYPE` that the earlier annotation references. The comment's invariant ("the value always inhabits FIELD_TYPE") therefore does not hold, and since the value is typed through `Any` from `get_default_value_for_type`, pyright silently trusts the lie β masking the fact that these fields can hold a `None` default contrary to their claimed non-None `FIELD_TYPE`. Apply the widening before typing the value, or drop the `FIELD_TYPE` assertion.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| default_value = types.get_default_value_for_type(annotated_type) | ||
| # `annotated_type` is widened to include None below when the | ||
| # computed default is None, so the value always inhabits FIELD_TYPE. | ||
| default_value: FIELD_TYPE = types.get_default_value_for_type( |
There was a problem hiding this comment.
P3: The default_value: FIELD_TYPE annotation is unsound for the branch where get_default_value_for_type returns None for a non-optional type (e.g. Field[SomeCustomClass] where the class is not one of TYPES_THAT_HAS_DEFAULT_VALUE, a Mapping, Literal, or dataframe). In that case default_value is None, but FIELD_TYPE bound to the non-optional custom class excludes None, so the value does not inhabit the annotated type. The widening annotated_type = annotated_type | None runs after the annotation and rebinds the local annotated_type variable only; it does not make None assignable to the FIELD_TYPE that the earlier annotation references. The comment's invariant ("the value always inhabits FIELD_TYPE") therefore does not hold, and since the value is typed through Any from get_default_value_for_type, pyright silently trusts the lie β masking the fact that these fields can hold a None default contrary to their claimed non-None FIELD_TYPE. Apply the widening before typing the value, or drop the FIELD_TYPE assertion.
Prompt for AI agents
Check if this issue is valid β if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/vars/base.py, line 3522:
<comment>The `default_value: FIELD_TYPE` annotation is unsound for the branch where `get_default_value_for_type` returns `None` for a non-optional type (e.g. `Field[SomeCustomClass]` where the class is not one of `TYPES_THAT_HAS_DEFAULT_VALUE`, a Mapping, Literal, or dataframe). In that case `default_value` is `None`, but `FIELD_TYPE` bound to the non-optional custom class excludes `None`, so the value does not inhabit the annotated type. The widening `annotated_type = annotated_type | None` runs after the annotation and rebinds the local `annotated_type` variable only; it does not make `None` assignable to the `FIELD_TYPE` that the earlier annotation references. The comment's invariant ("the value always inhabits FIELD_TYPE") therefore does not hold, and since the value is typed through `Any` from `get_default_value_for_type`, pyright silently trusts the lie β masking the fact that these fields can hold a `None` default contrary to their claimed non-None `FIELD_TYPE`. Apply the widening before typing the value, or drop the `FIELD_TYPE` assertion.</comment>
<file context>
@@ -3517,7 +3517,11 @@ def __init__(
- default_value = types.get_default_value_for_type(annotated_type)
+ # `annotated_type` is widened to include None below when the
+ # computed default is None, so the value always inhabits FIELD_TYPE.
+ default_value: FIELD_TYPE = types.get_default_value_for_type(
+ annotated_type
+ )
</file context>
Type of change
Changes To Core Features:
Description
This PR upgrades locked dev tooling and addresses linting issues introduced by the new versions:
Tooling upgrades:
ruff0.15.12 β 0.16.2pyright1.1.408 β 1.1.411typer0.25.1 β 0.27.1Key changes:
Property docstring style (ruff D421): Updated all property docstrings from imperative form ("Get the ...", "Return the ...") to noun phrases ("The ..."). This applies across:
reflex/app.pyreflex/compiler/compiler.pyreflex/experimental/client_state.pyreflex/istate/packages/reflex-base/src/reflex_base/(vars, event, config, registry, plugins)packages/reflex-components-core/src/reflex_components_core/packages/reflex-docgen/src/reflex_docgen/packages/reflex-components-internal/src/reflex_components_internal/Type safety improvements:
cast()calls inreflex/compiler/compiler.pyandreflex/reflex.pyfor untypedlibsassandtyperAPIspackages/reflex-base/src/reflex_base/vars/base.pyfordefault_valuewith explanatory commentEventSpecimport from TYPE_CHECKING block inpackages/reflex-base/src/reflex_base/event/processor/base_state_processor.pychain_updates()parameter type fromEventSpec | list[EventSpec] | NonetoAnywith updated docstring explaining runtime validationCode quality fixes:
packages/reflex-base/src/reflex_base/utils/types.py(removed redundantcls is not Nonecheck)packages/reflex-base/src/reflex_base/vars/datetime.py(movedImportVarto correct location)packages/reflex-base/src/reflex_base/vars/dep_tracking.py(split compound condition for clarity)Test infrastructure:
tests/units/reflex_cli/v2/utils.pywithas_click_command()helper to reduce duplication across CLI testsscope="function"parameter frompytest_asyncio.fixturedecorators (already set byloop_scope)Linting configuration:
ASYNC119to ignore list (background event handlers holdasync with selfacrossyieldby design)RUF075to ignore list (state managers deliberately skip write-back whenwithbody raises)RUF105andRUF201to ignore list (preservenoqacomment readability)Test Plan
Existing unit tests pass. The changes are primarily:
All changes maintain backward compatibility and improve code quality without altering runtime behavior.
https://claude.ai/code/session_01RLumacyNrCZDqvyptaVxkE