-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Cache Var.to()/guess_type() registry lookups #6742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e884352
b0cc850
750bdb8
af2b18c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `Var.to()` and `Var.guess_type()` resolve their target Var subclass through cached registry lookups instead of scanning the full registry with `safe_issubclass` on every call. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `Var.to()` and `Var.guess_type()` resolve their target Var subclass through cached registry lookups instead of scanning the full registry with `safe_issubclass` on every call (~70% of the cost of constructing a var operation). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -113,6 +113,93 @@ class VarSubclassEntry: | |
| _var_literal_subclasses: list[tuple[type[LiteralVar], VarSubclassEntry]] = [] | ||
|
|
||
|
|
||
| @functools.cache | ||
| def _var_subclass_for_conversion(python_type: GenericType) -> VarSubclassEntry | None: | ||
| """Find the registry entry ``Var.to`` maps a python type to. | ||
|
|
||
| Later-registered entries take priority, matching the reversed scan the | ||
| cache replaces. The registry only grows at import time; registration | ||
| clears this cache (see ``Var.__init_subclass__``). | ||
|
|
||
| Args: | ||
| python_type: The (origin-normalized) python type to look up. | ||
|
|
||
| Returns: | ||
| The matching entry, or ``None`` if no entry matches. | ||
| """ | ||
| for var_subclass in reversed(_var_subclasses): | ||
| if python_type in var_subclass.python_types or safe_issubclass( | ||
| python_type, var_subclass.python_types | ||
| ): | ||
| return var_subclass | ||
| return None | ||
|
|
||
|
|
||
| @functools.cache | ||
| def _var_subclass_matching_python_types( | ||
| python_types: tuple[GenericType, ...], | ||
| ) -> VarSubclassEntry | None: | ||
| """Find the registry entry whose python types cover all ``python_types``. | ||
|
|
||
| Used by ``Var.guess_type`` with the (origin-normalized) inner types of | ||
| the var type — a 1-tuple for plain types, the union members otherwise. | ||
| Later-registered entries take priority; registration clears this cache. | ||
|
|
||
| Args: | ||
| python_types: The python types that must all match one entry. | ||
|
|
||
| Returns: | ||
| The matching entry, or ``None`` if no entry matches. | ||
| """ | ||
| for var_subclass in reversed(_var_subclasses): | ||
| if all( | ||
| safe_issubclass(python_type, var_subclass.python_types) | ||
| for python_type in python_types | ||
| ): | ||
| return var_subclass | ||
| return None | ||
|
|
||
|
|
||
| @functools.cache | ||
| def _var_subclass_for_var_output(output: type) -> VarSubclassEntry | None: | ||
| """Find the registry entry for a ``Var``-subclass conversion target. | ||
|
|
||
| Later-registered entries take priority; registration clears this cache. | ||
|
|
||
| Args: | ||
| output: The ``Var`` subclass passed to ``Var.to``. | ||
|
|
||
| Returns: | ||
| The matching entry, or ``None`` if no entry matches. | ||
| """ | ||
| for var_subclass in reversed(_var_subclasses): | ||
| if safe_issubclass(output, var_subclass.var_subclass): | ||
| return var_subclass | ||
| return None | ||
|
|
||
|
|
||
| def _clear_var_subclass_lookup_caches() -> None: | ||
| """Drop cached registry lookups after a new Var subclass registers.""" | ||
| _var_subclass_for_conversion.cache_clear() | ||
| _var_subclass_matching_python_types.cache_clear() | ||
| _var_subclass_for_var_output.cache_clear() | ||
|
|
||
|
|
||
| def _register_var_subclass_entry(entry: VarSubclassEntry) -> None: | ||
| """Register a Var subclass entry and invalidate cached lookups. | ||
|
|
||
| Every append to ``_var_subclasses`` must go through here — including | ||
| manual registrations like ``ReflexURLVar`` — since a bare append would | ||
| leave previously cached lookups returning stale results for types the | ||
| new entry claims. | ||
|
|
||
| Args: | ||
| entry: The entry to append to the registry. | ||
| """ | ||
| _var_subclasses.append(entry) | ||
| _clear_var_subclass_lookup_caches() | ||
|
|
||
|
|
||
| _AppWrap = TypeVar("_AppWrap", bound="BaseComponent") | ||
|
|
||
|
|
||
|
|
@@ -586,7 +673,9 @@ class ToVarOperation(ToOperation, cls): | |
| ) | ||
| ToVarOperation.__name__ = new_to_var_operation_name | ||
|
|
||
| _var_subclasses.append(VarSubclassEntry(cls, ToVarOperation, python_types)) | ||
| _register_var_subclass_entry( | ||
| VarSubclassEntry(cls, ToVarOperation, python_types) | ||
| ) | ||
|
|
||
| def __post_init__(self): | ||
| """Post-initialize the var. | ||
|
|
@@ -913,11 +1002,9 @@ def to( | |
| fixed_output_type = get_origin(output) or output | ||
|
|
||
| # If the first argument is a python type, we map it to the corresponding Var type. | ||
| for var_subclass in _var_subclasses[::-1]: | ||
| if fixed_output_type in var_subclass.python_types or safe_issubclass( | ||
| fixed_output_type, var_subclass.python_types | ||
| ): | ||
| return self.to(var_subclass.var_subclass, output) | ||
| conversion_entry = _var_subclass_for_conversion(fixed_output_type) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a regression, so no fallback added: every one of these call sites is preceded by Generated by Claude Code |
||
| if conversion_entry is not None: | ||
| return self.to(conversion_entry.var_subclass, output) | ||
|
|
||
| if fixed_output_type is None: | ||
| return get_to_operation(NoneVar).create(self) # pyright: ignore [reportReturnType] | ||
|
|
@@ -927,16 +1014,16 @@ def to( | |
| return self.to(ObjectVar, output) | ||
|
|
||
| if isinstance(output, type): | ||
| for var_subclass in _var_subclasses[::-1]: | ||
| if safe_issubclass(output, var_subclass.var_subclass): | ||
| current_var_type = self._var_type | ||
| if current_var_type is Any: | ||
| new_var_type = var_type | ||
| else: | ||
| new_var_type = var_type or current_var_type | ||
| return var_subclass.to_var_subclass.create( # pyright: ignore [reportReturnType] | ||
| value=self, _var_type=new_var_type | ||
| ) | ||
| output_entry = _var_subclass_for_var_output(output) | ||
| if output_entry is not None: | ||
| current_var_type = self._var_type | ||
| if current_var_type is Any: | ||
| new_var_type = var_type | ||
| else: | ||
| new_var_type = var_type or current_var_type | ||
| return output_entry.to_var_subclass.create( # pyright: ignore [reportReturnType] | ||
| value=self, _var_type=new_var_type | ||
| ) | ||
|
|
||
| # If we can't determine the first argument, we just replace the _var_type. | ||
| if not safe_issubclass(output, Var) or var_type is None: | ||
|
|
@@ -1003,12 +1090,9 @@ def guess_type(self) -> Var: | |
| for inner_type in non_optional_inner_types | ||
| ] | ||
|
|
||
| for var_subclass in _var_subclasses[::-1]: | ||
| if all( | ||
| safe_issubclass(t, var_subclass.python_types) | ||
| for t in fixed_inner_types | ||
| ): | ||
| return self.to(var_subclass.var_subclass, self._var_type) | ||
| union_entry = _var_subclass_matching_python_types(tuple(fixed_inner_types)) | ||
| if union_entry is not None: | ||
| return self.to(union_entry.var_subclass, self._var_type) | ||
|
|
||
| if can_use_in_object_var(var_type): | ||
| return self.to(ObjectVar, self._var_type) | ||
|
|
@@ -1026,9 +1110,9 @@ def guess_type(self) -> Var: | |
| if fixed_type is None: | ||
| return self.to(None) | ||
|
|
||
| for var_subclass in _var_subclasses[::-1]: | ||
| if safe_issubclass(fixed_type, var_subclass.python_types): | ||
| return self.to(var_subclass.var_subclass, self._var_type) | ||
| guessed_entry = _var_subclass_matching_python_types((fixed_type,)) | ||
| if guessed_entry is not None: | ||
| return self.to(guessed_entry.var_subclass, self._var_type) | ||
|
|
||
| if can_use_in_object_var(fixed_type): | ||
| return self.to(ObjectVar, self._var_type) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.