Skip to content

cache get_type_hints for heavy init subclass stuff - improve import performance#6118

Merged
adhami3310 merged 4 commits intoreflex-dev:mainfrom
benedikt-bartscher:perf/cache-get-type-hints
Feb 13, 2026
Merged

cache get_type_hints for heavy init subclass stuff - improve import performance#6118
adhami3310 merged 4 commits intoreflex-dev:mainfrom
benedikt-bartscher:perf/cache-get-type-hints

Conversation

@benedikt-bartscher
Copy link
Contributor

@benedikt-bartscher benedikt-bartscher commented Feb 7, 2026

For one of my apps this reduces the app import time over 50%

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 7, 2026

Merging this PR will not alter performance

✅ 8 untouched benchmarks


Comparing benedikt-bartscher:perf/cache-get-type-hints (99431e7) with main (480d93b)

Open in CodSpeed

@benedikt-bartscher benedikt-bartscher marked this pull request as ready for review February 7, 2026 13:59
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 7, 2026

Greptile Overview

Greptile Summary

This PR adds caching to the _get_type_hints() method using @functools.cache, significantly improving import performance (over 50% reduction for the author's app). The changes are well-structured:

  • Added @functools.cache decorator to BaseState._get_type_hints() to cache type hints per class
  • Updated _check_overridden_basevars() to use the cached method instead of calling get_type_hints() directly
  • Added proper type annotation (type[BaseState]) to is_backend_base_variable() function signature
  • Removed defensive checking in is_backend_base_variable() since all callers are guaranteed to pass BaseState subclasses

The caching strategy is sound because:

  • Type hints are static after class definition and don't change at runtime
  • The pattern follows existing usage of @functools.cache with classmethods in the codebase (e.g., Component.get_props())
  • Each class gets its own cache entry, so inheritance works correctly
  • The method already handles dynamic substates properly via __original_module__

Confidence Score: 5/5

  • This PR is safe to merge with no identified risks
  • The changes are simple, focused, and follow established patterns in the codebase. Caching get_type_hints() is safe because type hints are immutable after class definition. The removal of defensive checking is justified by type annotations ensuring only BaseState subclasses are passed. No edge cases or potential bugs identified.
  • No files require special attention

Important Files Changed

Filename Overview
reflex/state.py Added @functools.cache decorator to _get_type_hints() method and updated _check_overridden_basevars() to use cached result - improves performance with no side effects
reflex/utils/types.py Added type annotation for BaseState, removed defensive check in is_backend_base_variable(), and simplified to always call cls._get_type_hints() - safe given all callers pass BaseState subclasses

Sequence Diagram

sequenceDiagram
    participant App as Application Code
    participant BS as BaseState
    participant Cache as functools.cache
    participant GTH as get_type_hints
    participant Types as types.is_backend_base_variable

    Note over App,Types: Initial State Class Import/Initialization
    
    App->>BS: Define State subclass
    BS->>BS: _check_overridden_basevars()
    BS->>Cache: Call _get_type_hints()
    Cache->>Cache: Check if cached for this class
    alt Not Cached
        Cache->>GTH: get_type_hints(cls, localns)
        GTH-->>Cache: Return type hints dict
        Cache->>Cache: Store in cache
    end
    Cache-->>BS: Return cached hints
    
    Note over App,Types: Backend Variable Check Flow
    
    App->>BS: Access backend_vars during init
    BS->>Types: is_backend_base_variable(name, mixin_cls)
    Types->>BS: Call cls._get_type_hints()
    BS->>Cache: Retrieve from cache
    Cache-->>BS: Return cached hints (no recomputation)
    BS-->>Types: Return hints
    Types-->>BS: Return bool result
    
    Note over Cache: Performance Improvement: Subsequent calls skip get_type_hints computation
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@benedikt-bartscher benedikt-bartscher force-pushed the perf/cache-get-type-hints branch from 35a2450 to a83bdf9 Compare February 7, 2026 20:55
adhami3310
adhami3310 previously approved these changes Feb 12, 2026
@adhami3310
Copy link
Member

in a followup, we should also add

diff --git a/reflex/state.py b/reflex/state.py
index 89698cfa..b87f2e4a 100644
--- a/reflex/state.py
+++ b/reflex/state.py
@@ -569,14 +569,14 @@ class BaseState(EvenMoreBasicBaseState):
 
         new_backend_vars = {
             name: value if not isinstance(value, Field) else value.default_value()
-            for mixin_cls in [*cls._mixins(), cls]
+            for mixin_cls in (*cls._mixins(), cls)
             for name, value in list(mixin_cls.__dict__.items())
             if types.is_backend_base_variable(name, mixin_cls)
         }
         # Add annotated backend vars that may not have a default value.
         new_backend_vars.update({
             name: cls._get_var_default(name, annotation_value)
-            for mixin_cls in [*cls._mixins(), cls]
+            for mixin_cls in (*cls._mixins(), cls)
             for name, annotation_value in mixin_cls._get_type_hints().items()
             if name not in new_backend_vars
             and types.is_backend_base_variable(name, mixin_cls)
@@ -764,7 +764,7 @@ class BaseState(EvenMoreBasicBaseState):
         return getattr(cls, unique_var_name)
 
     @classmethod
-    def _mixins(cls) -> list[type]:
+    def _mixins(cls) -> list[type[BaseState]]:
         """Get the mixin classes of the state.
 
         Returns:

to improve type safety around those functions that call this

Copy link
Collaborator

@masenf masenf left a comment

Choose a reason for hiding this comment

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

do you get the same performance improvements by pulling the get_type_hints out of the for loop without adding the functools cache?

@benedikt-bartscher
Copy link
Contributor Author

do you get the same performance improvements by pulling the get_type_hints out of the for loop without adding the functools cache?

no, 4,103s total vs 7,244s total

@benedikt-bartscher
Copy link
Contributor Author

@adhami3310 thanks for your input, i just migrated it to tuple and applied stricter typing

@adhami3310 adhami3310 merged commit 88155a7 into reflex-dev:main Feb 13, 2026
45 of 47 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.

3 participants