Cache expensive introspection calls in pyi_generator for faster stub generation#6187
Conversation
…generation Extract repeated inspect.getsource, getfullargspec, inspect.signature, and module import calls into @cache-decorated helper functions. Replace inline exec() calls with explicit dict updates for resolving type hints. Convert all_props from list to set for O(1) membership checks. Before: 8 files reformatted, 112 files left unchanged real 4.12 user 5.37 sys 0.33 After: 8 files reformatted, 112 files left unchanged real 1.93 user 3.00 sys 0.32
Greptile SummaryThis PR optimizes Key changes:
Confidence Score: 4/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[PyiGenerator.scan_all] --> B[_scan_files]
B --> C{fork available\nand max_workers > 1?}
C -- No --> D[Serial loop\nover files]
D --> E[_scan_file per file]
C -- Yes --> F[Pre-import all modules\nin main process]
F --> G[ProcessPoolExecutor\nfork context]
G --> H[_scan_file per file\nin worker processes]
E --> I{is_init_file?}
H --> I
I -- Yes --> J[InitStubGenerator.visit\nast.parse _get_source module]
I -- No --> K[StubGenerator.visit\nast.parse _get_source module]
J --> L[_get_init_lazy_imports]
K --> M[_write_pyi_file]
L --> M
subgraph Cached Helpers
N[_get_source]
O[_get_full_argspec]
P[_get_signature_return_annotation]
Q[_get_module_star_imports]
R[_get_module_selected_imports]
S[_get_class_annotation_globals]
T[_get_class_event_triggers]
U[_get_class_prop_comments]
V[_get_parent_imports]
end
K -->|uses| N
K -->|uses| O
K -->|uses| P
K -->|uses| Q
K -->|uses| R
K -->|uses| S
K -->|uses| T
K -->|uses| U
K -->|uses| V
Last reviewed commit: 040a0d7 |
reflex/utils/pyi_generator.py
Outdated
| @cache | ||
| def _get_parent_imports(func: Callable) -> dict[str, tuple[str, ...]]: | ||
| imports_ = {"reflex.vars": {"Var"}} | ||
| module_dir = set(importlib.import_module(func.__module__).__dir__()) | ||
| for type_hint in inspect.get_annotations(func).values(): | ||
| try: | ||
| match = re.match(r"\w+\[([\w\d]+)\]", type_hint) | ||
| except TypeError: | ||
| continue | ||
| if match: | ||
| type_hint = match.group(1) | ||
| if type_hint in importlib.import_module(func.__module__).__dir__(): | ||
| imports_.setdefault(func.__module__, []).append(type_hint) | ||
| return imports_ | ||
| if type_hint in module_dir: | ||
| imports_.setdefault(func.__module__, set()).add(type_hint) | ||
| return { | ||
| module_name: tuple(sorted(imported_names)) | ||
| for module_name, imported_names in imports_.items() | ||
| } |
There was a problem hiding this comment.
Return type annotation conflicts with internal implementation
The declared return type is dict[str, tuple[str, ...]] and the final return statement does produce that type. However, the function first builds imports_ with set values:
imports_ = {"reflex.vars": {"Var"}} # value is a set
imports_.setdefault(func.__module__, set()).add(...) # value is a setPython's type checker will flag imports_ as dict[str, set[str]] throughout the function body, producing a mismatch between the internal variable type and the final dict comprehension that converts to tuples. Consider using an explicit intermediate type annotation to keep this clear:
imports_: dict[str, set[str]] = {"reflex.vars": {"Var"}}Pre-import modules sequentially to populate sys.modules, then fork worker processes for AST parsing, transformation, and file writing. Extract _write_pyi_file, _get_init_lazy_imports, and _scan_file to module-level functions. Remove disabled _scan_files_multiprocess. Reduces generation time from ~1.9s to ~1.45s (~24% faster).
Cached dicts returned by _get_module_star_imports, _get_module_selected_imports, _get_class_annotation_globals, and _get_parent_imports are shared across callers. Wrap them in MappingProxyType to prevent accidental mutation of cached values. Also remove redundant first ruff format pass. 117 files reformatted, 3 files left unchanged real 1.57 user 3.93 sys 0.67
|
Parallelization on my machine is 1.4 vs 1.9 seconds. I don't know if the complexity is worth it and windows is not gonna support it because |
|
i think the half second is worth it, and windows is already slow so... |

Extract repeated inspect.getsource, getfullargspec, inspect.signature, and module import calls into @cache-decorated helper functions. Replace inline exec() calls with explicit dict updates for resolving type hints. Convert all_props from list to set for O(1) membership checks.
/usr/bin/time -p uv run python3 -m reflex.utils.pyi_generatorBefore:
8 files reformatted, 112 files left unchanged
real 4.12
user 5.37
sys 0.33
After:
8 files reformatted, 112 files left unchanged
real 1.93
user 3.00
sys 0.32
After multiprocess forking:
117 files reformatted, 3 files left unchanged
real 1.57
user 3.86
sys 0.67
All Submissions:
Type of change
Please delete options that are not relevant.
New Feature Submission:
Changes To Core Features:
closes #6183