Merge branch 'main' into remove-some-benchmarks-from-CI

This commit is contained in:
Khaleel Al-Adhami 2025-02-13 16:15:37 -08:00
commit 29f3a77a48
16 changed files with 185 additions and 122 deletions

View File

@ -3,7 +3,7 @@ fail_fast: true
repos: repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.9.3 rev: v0.9.6
hooks: hooks:
- id: ruff-format - id: ruff-format
args: [reflex, tests] args: [reflex, tests]

40
poetry.lock generated
View File

@ -2415,31 +2415,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.9.3" version = "0.9.6"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"] groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [ files = [
{file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"},
{file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"},
{file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"},
{file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"},
{file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"},
{file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"},
{file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"},
{file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"},
{file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"},
] ]
[[package]] [[package]]
@ -3188,4 +3188,4 @@ type = ["pytest-mypy"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10, <4.0" python-versions = ">=3.10, <4.0"
content-hash = "3b7e6e6e872c68f951f191d85a7d76fe1dd86caf32e2143a53a3152a3686fc7f" content-hash = "7ae644e1c5b910f4fd0d8ab0b530818077a96e5d329b2be1269e967c6b0b3d25"

View File

@ -61,7 +61,7 @@ dill = ">=0.3.8"
toml = ">=0.10.2,<1.0" toml = ">=0.10.2,<1.0"
pytest-asyncio = ">=0.24.0" pytest-asyncio = ">=0.24.0"
pytest-cov = ">=4.0.0,<7.0" pytest-cov = ">=4.0.0,<7.0"
ruff = "0.9.3" ruff = "0.9.6"
pandas = ">=2.1.1,<3.0" pandas = ">=2.1.1,<3.0"
pillow = ">=10.0.0,<12.0" pillow = ">=10.0.0,<12.0"
plotly = ">=5.13.0,<6.0" plotly = ">=5.13.0,<6.0"

View File

@ -22,6 +22,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
AsyncIterator, AsyncIterator,
BinaryIO,
Callable, Callable,
Coroutine, Coroutine,
Dict, Dict,
@ -35,12 +36,15 @@ from typing import (
get_type_hints, get_type_hints,
) )
from fastapi import FastAPI, HTTPException, Request, UploadFile from fastapi import FastAPI, HTTPException, Request
from fastapi import UploadFile as FastAPIUploadFile
from fastapi.middleware import cors from fastapi.middleware import cors
from fastapi.responses import JSONResponse, StreamingResponse from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
from socketio import ASGIApp, AsyncNamespace, AsyncServer from socketio import ASGIApp, AsyncNamespace, AsyncServer
from starlette.datastructures import Headers
from starlette.datastructures import UploadFile as StarletteUploadFile
from starlette_admin.contrib.sqla.admin import Admin from starlette_admin.contrib.sqla.admin import Admin
from starlette_admin.contrib.sqla.view import ModelView from starlette_admin.contrib.sqla.view import ModelView
@ -231,6 +235,53 @@ class OverlayFragment(Fragment):
pass pass
@dataclasses.dataclass(frozen=True)
class UploadFile(StarletteUploadFile):
"""A file uploaded to the server.
Args:
file: The standard Python file object (non-async).
filename: The original file name.
size: The size of the file in bytes.
headers: The headers of the request.
"""
file: BinaryIO
path: Optional[Path] = dataclasses.field(default=None)
_deprecated_filename: Optional[str] = dataclasses.field(default=None)
size: Optional[int] = dataclasses.field(default=None)
headers: Headers = dataclasses.field(default_factory=Headers)
@property
def name(self) -> Optional[str]:
"""Get the name of the uploaded file.
Returns:
The name of the uploaded file.
"""
if self.path:
return self.path.name
@property
def filename(self) -> Optional[str]:
"""Get the filename of the uploaded file.
Returns:
The filename of the uploaded file.
"""
console.deprecate(
feature_name="UploadFile.filename",
reason="Use UploadFile.name instead.",
deprecation_version="0.7.1",
removal_version="0.8.0",
)
return self._deprecated_filename
@dataclasses.dataclass( @dataclasses.dataclass(
frozen=True, frozen=True,
) )
@ -591,7 +642,9 @@ class App(MiddlewareMixin, LifespanMixin):
Returns: Returns:
The generated component. The generated component.
""" """
return component if isinstance(component, Component) else component() from reflex.compiler.compiler import into_component
return into_component(component)
def add_page( def add_page(
self, self,
@ -1583,7 +1636,7 @@ def upload(app: App):
The upload function. The upload function.
""" """
async def upload_file(request: Request, files: List[UploadFile]): async def upload_file(request: Request, files: List[FastAPIUploadFile]):
"""Upload a file. """Upload a file.
Args: Args:
@ -1659,7 +1712,8 @@ def upload(app: App):
file_copies.append( file_copies.append(
UploadFile( UploadFile(
file=content_copy, file=content_copy,
filename=file.filename, path=Path(file.filename.lstrip("/")) if file.filename else None,
_deprecated_filename=file.filename,
size=file.size, size=file.size,
headers=file.headers, headers=file.headers,
) )

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple, Type, Union from typing import TYPE_CHECKING, Dict, Iterable, Optional, Sequence, Tuple, Type, Union
from reflex import constants from reflex import constants
from reflex.compiler import templates, utils from reflex.compiler import templates, utils
@ -545,7 +545,47 @@ def purge_web_pages_dir():
if TYPE_CHECKING: if TYPE_CHECKING:
from reflex.app import UnevaluatedPage from reflex.app import ComponentCallable, UnevaluatedPage
def _into_component_once(component: Component | ComponentCallable) -> Component | None:
"""Convert a component to a Component.
Args:
component: The component to convert.
Returns:
The converted component.
"""
if isinstance(component, Component):
return component
if isinstance(component, (Var, int, float, str)):
return Fragment.create(component)
if isinstance(component, Sequence):
return Fragment.create(*component)
return None
def into_component(component: Component | ComponentCallable) -> Component:
"""Convert a component to a Component.
Args:
component: The component to convert.
Returns:
The converted component.
Raises:
TypeError: If the component is not a Component.
"""
if (converted := _into_component_once(component)) is not None:
return converted
if (
callable(component)
and (converted := _into_component_once(component())) is not None
):
return converted
raise TypeError(f"Expected a Component, got {type(component)}")
def compile_unevaluated_page( def compile_unevaluated_page(
@ -568,12 +608,7 @@ def compile_unevaluated_page(
The compiled component and whether state should be enabled. The compiled component and whether state should be enabled.
""" """
# Generate the component if it is a callable. # Generate the component if it is a callable.
component = page.component component = into_component(page.component)
component = component if isinstance(component, Component) else component()
# unpack components that return tuples in an rx.fragment.
if isinstance(component, tuple):
component = Fragment.create(*component)
component._add_style_recursive(style or {}, theme) component._add_style_recursive(style or {}, theme)
@ -678,10 +713,8 @@ class ExecutorSafeFunctions:
The route, compiled component, and compiled page. The route, compiled component, and compiled page.
""" """
component, enable_state = compile_unevaluated_page( component, enable_state = compile_unevaluated_page(
route, cls.UNCOMPILED_PAGES[route] route, cls.UNCOMPILED_PAGES[route], cls.STATE, style, theme
) )
component = component if isinstance(component, Component) else component()
component._add_style_recursive(style, theme)
return route, component, compile_page(route, component, cls.STATE) return route, component, compile_page(route, component, cls.STATE)
@classmethod @classmethod

View File

@ -51,13 +51,7 @@ from reflex.event import (
) )
from reflex.style import Style, format_as_emotion from reflex.style import Style, format_as_emotion
from reflex.utils import format, imports, types from reflex.utils import format, imports, types
from reflex.utils.imports import ( from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
ImmutableParsedImportDict,
ImportDict,
ImportVar,
ParsedImportDict,
parse_imports,
)
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import ( from reflex.vars.base import (
CachedVarOperation, CachedVarOperation,
@ -1208,7 +1202,7 @@ class Component(BaseComponent, ABC):
Returns: Returns:
True if the dependency should be transpiled. True if the dependency should be transpiled.
""" """
return ( return bool(self.transpile_packages) and (
dep in self.transpile_packages dep in self.transpile_packages
or format.format_library_name(dep or "") in self.transpile_packages or format.format_library_name(dep or "") in self.transpile_packages
) )
@ -1291,9 +1285,10 @@ class Component(BaseComponent, ABC):
event_imports = Imports.EVENTS if self.event_triggers else {} event_imports = Imports.EVENTS if self.event_triggers else {}
# Collect imports from Vars used directly by this component. # Collect imports from Vars used directly by this component.
var_datas = [var._get_all_var_data() for var in self._get_vars()] var_imports = [
var_imports: List[ImmutableParsedImportDict] = [ var_data.imports
var_data.imports for var_data in var_datas if var_data is not None for var in self._get_vars()
if (var_data := var._get_all_var_data()) is not None
] ]
added_import_dicts: list[ParsedImportDict] = [] added_import_dicts: list[ParsedImportDict] = []

View File

@ -29,7 +29,7 @@ from reflex.event import (
from reflex.utils import format from reflex.utils import format
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import CallableVar, Var, get_unique_variable_name from reflex.vars.base import Var, get_unique_variable_name
from reflex.vars.sequence import LiteralStringVar from reflex.vars.sequence import LiteralStringVar
DEFAULT_UPLOAD_ID: str = "default" DEFAULT_UPLOAD_ID: str = "default"
@ -45,7 +45,6 @@ upload_files_context_var_data: VarData = VarData(
) )
@CallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
"""Get the file upload drop trigger. """Get the file upload drop trigger.
@ -75,7 +74,6 @@ def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
) )
@CallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var:
"""Get the list of selected files. """Get the list of selected files.

View File

@ -13,14 +13,12 @@ from reflex.event import CallableEventSpec, EventSpec, EventType
from reflex.style import Style from reflex.style import Style
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import CallableVar, Var from reflex.vars.base import Var
DEFAULT_UPLOAD_ID: str DEFAULT_UPLOAD_ID: str
upload_files_context_var_data: VarData upload_files_context_var_data: VarData
@CallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ... def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
@CallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ... def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
@CallableEventSpec @CallableEventSpec
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ... def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...

View File

@ -144,7 +144,7 @@ class ColorModeIconButton(IconButton):
if allow_system: if allow_system:
def color_mode_item(_color_mode: str): def color_mode_item(_color_mode: Literal["light", "dark", "system"]):
return dropdown_menu.item( return dropdown_menu.item(
_color_mode.title(), on_click=set_color_mode(_color_mode) _color_mode.title(), on_click=set_color_mode(_color_mode)
) )

View File

@ -10,6 +10,7 @@ import os
import sys import sys
import threading import threading
import urllib.parse import urllib.parse
from functools import lru_cache
from importlib.util import find_spec from importlib.util import find_spec
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
@ -408,6 +409,19 @@ class EnvVar(Generic[T]):
os.environ[self.name] = str_value os.environ[self.name] = str_value
@lru_cache()
def get_type_hints_environment(cls: type) -> dict[str, Any]:
"""Get the type hints for the environment variables.
Args:
cls: The class.
Returns:
The type hints.
"""
return get_type_hints(cls)
class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration] class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
"""Descriptor for environment variables.""" """Descriptor for environment variables."""
@ -434,7 +448,9 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
""" """
self.name = name self.name = name
def __get__(self, instance: Any, owner: Any): def __get__(
self, instance: EnvironmentVariables, owner: type[EnvironmentVariables]
):
"""Get the EnvVar instance. """Get the EnvVar instance.
Args: Args:
@ -444,7 +460,7 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
Returns: Returns:
The EnvVar instance. The EnvVar instance.
""" """
type_ = get_args(get_type_hints(owner)[self.name])[0] type_ = get_args(get_type_hints_environment(owner)[self.name])[0]
env_name = self.name env_name = self.name
if self.internal: if self.internal:
env_name = f"__{env_name}" env_name = f"__{env_name}"

View File

@ -1742,6 +1742,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Yields: Yields:
StateUpdate object StateUpdate object
Raises:
ValueError: If a string value is received for an int or float type and cannot be converted.
""" """
from reflex.utils import telemetry from reflex.utils import telemetry
@ -1779,12 +1782,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
hinted_args, (Base, BaseModelV1, BaseModelV2) hinted_args, (Base, BaseModelV1, BaseModelV2)
): ):
payload[arg] = hinted_args(**value) payload[arg] = hinted_args(**value)
if isinstance(value, list) and (hinted_args is set or hinted_args is Set): elif isinstance(value, list) and (hinted_args is set or hinted_args is Set):
payload[arg] = set(value) payload[arg] = set(value)
if isinstance(value, list) and ( elif isinstance(value, list) and (
hinted_args is tuple or hinted_args is Tuple hinted_args is tuple or hinted_args is Tuple
): ):
payload[arg] = tuple(value) payload[arg] = tuple(value)
elif isinstance(value, str) and (
hinted_args is int or hinted_args is float
):
try:
payload[arg] = hinted_args(value)
except ValueError:
raise ValueError(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
) from None
else:
console.warn(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}. A simple conversion was successful."
)
# Wrap the function in a try/except block. # Wrap the function in a try/except block.
try: try:
@ -2459,6 +2475,8 @@ class ComponentState(State, mixin=True):
Returns: Returns:
A new instance of the Component with an independent copy of the State. A new instance of the Component with an independent copy of the State.
""" """
from reflex.compiler.compiler import into_component
cls._per_component_state_instance_count += 1 cls._per_component_state_instance_count += 1
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}" state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
component_state = type( component_state = type(
@ -2470,6 +2488,7 @@ class ComponentState(State, mixin=True):
# Save a reference to the dynamic state for pickle/unpickle. # Save a reference to the dynamic state for pickle/unpickle.
setattr(reflex.istate.dynamic, state_cls_name, component_state) setattr(reflex.istate.dynamic, state_cls_name, component_state)
component = component_state.get_component(*children, **props) component = component_state.get_component(*children, **props)
component = into_component(component)
component.State = component_state component.State = component_state
return component return component

View File

@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError
from reflex.utils.imports import ImportVar from reflex.utils.imports import ImportVar
from reflex.utils.types import get_origin from reflex.utils.types import get_origin
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import CallableVar, LiteralVar, Var from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import FunctionVar from reflex.vars.function import FunctionVar
from reflex.vars.object import ObjectVar from reflex.vars.object import ObjectVar
@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var:
).guess_type() ).guess_type()
@CallableVar
def set_color_mode( def set_color_mode(
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None, new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
) -> Var[EventChain]: ) -> Var[EventChain]:

View File

@ -951,7 +951,7 @@ class Var(Generic[VAR_TYPE]):
""" """
actual_name = self._var_field_name actual_name = self._var_field_name
def setter(state: BaseState, value: Any): def setter(state: Any, value: Any):
"""Get the setter for the var. """Get the setter for the var.
Args: Args:
@ -969,6 +969,8 @@ class Var(Generic[VAR_TYPE]):
else: else:
setattr(state, actual_name, value) setattr(state, actual_name, value)
setter.__annotations__["value"] = self._var_type
setter.__qualname__ = self._get_setter_name() setter.__qualname__ = self._get_setter_name()
return setter return setter
@ -1901,61 +1903,6 @@ def _or_operation(a: Var, b: Var):
) )
@dataclasses.dataclass(
eq=False,
frozen=True,
slots=True,
)
class CallableVar(Var):
"""Decorate a Var-returning function to act as both a Var and a function.
This is used as a compatibility shim for replacing Var objects in the
API with functions that return a family of Var.
"""
fn: Callable[..., Var] = dataclasses.field(
default_factory=lambda: lambda: Var(_js_expr="undefined")
)
original_var: Var = dataclasses.field(
default_factory=lambda: Var(_js_expr="undefined")
)
def __init__(self, fn: Callable[..., Var]):
"""Initialize a CallableVar.
Args:
fn: The function to decorate (must return Var)
"""
original_var = fn()
super(CallableVar, self).__init__(
_js_expr=original_var._js_expr,
_var_type=original_var._var_type,
_var_data=VarData.merge(original_var._get_all_var_data()),
)
object.__setattr__(self, "fn", fn)
object.__setattr__(self, "original_var", original_var)
def __call__(self, *args: Any, **kwargs: Any) -> Var:
"""Call the decorated function.
Args:
*args: The args to pass to the function.
**kwargs: The kwargs to pass to the function.
Returns:
The Var returned from calling the function.
"""
return self.fn(*args, **kwargs)
def __hash__(self) -> int:
"""Calculate the hash of the object.
Returns:
The hash of the object.
"""
return hash((type(self).__name__, self.original_var))
RETURN_TYPE = TypeVar("RETURN_TYPE") RETURN_TYPE = TypeVar("RETURN_TYPE")
DICT_KEY = TypeVar("DICT_KEY") DICT_KEY = TypeVar("DICT_KEY")

View File

@ -20,7 +20,11 @@ def BackgroundTask():
class State(rx.State): class State(rx.State):
counter: int = 0 counter: int = 0
_task_id: int = 0 _task_id: int = 0
iterations: int = 10 iterations: rx.Field[int] = rx.field(10)
@rx.event
def set_iterations(self, value: str):
self.iterations = int(value)
@rx.event(background=True) @rx.event(background=True)
async def handle_event(self): async def handle_event(self):
@ -125,8 +129,8 @@ def BackgroundTask():
rx.input( rx.input(
id="iterations", id="iterations",
placeholder="Iterations", placeholder="Iterations",
value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue] value=State.iterations.to_string(),
on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue] on_change=State.set_iterations,
), ),
rx.button( rx.button(
"Delayed Increment", "Delayed Increment",

View File

@ -87,7 +87,7 @@ def UploadFile():
), ),
rx.box( rx.box(
rx.foreach( rx.foreach(
rx.selected_files, rx.selected_files(),
lambda f: rx.text(f, as_="p"), lambda f: rx.text(f, as_="p"),
), ),
id="selected_files", id="selected_files",

View File

@ -61,7 +61,7 @@ def ColorToggleApp():
rx.icon(tag="moon", size=20), rx.icon(tag="moon", size=20),
value="dark", value="dark",
), ),
on_change=set_color_mode, on_change=set_color_mode(),
variant="classic", variant="classic",
radius="large", radius="large",
value=color_mode, value=color_mode,