diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0bad7b996..743f5f31a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ fail_fast: true repos: - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.9.3 + rev: v0.9.6 hooks: - id: ruff-format args: [reflex, tests] diff --git a/poetry.lock b/poetry.lock index b96749316..032bd2d4a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2415,31 +2415,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.9.3" +version = "0.9.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"}, - {file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"}, - {file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"}, - {file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"}, - {file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"}, - {file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"}, - {file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"}, - {file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"}, - {file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"}, + {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, + {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, + {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"}, + {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"}, + {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"}, + {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"}, + {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"}, + {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"}, + {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"}, ] [[package]] @@ -3188,4 +3188,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.10, <4.0" -content-hash = "3b7e6e6e872c68f951f191d85a7d76fe1dd86caf32e2143a53a3152a3686fc7f" +content-hash = "7ae644e1c5b910f4fd0d8ab0b530818077a96e5d329b2be1269e967c6b0b3d25" diff --git a/pyproject.toml b/pyproject.toml index c192a1139..67611a867 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,7 @@ dill = ">=0.3.8" toml = ">=0.10.2,<1.0" pytest-asyncio = ">=0.24.0" pytest-cov = ">=4.0.0,<7.0" -ruff = "0.9.3" +ruff = "0.9.6" pandas = ">=2.1.1,<3.0" pillow = ">=10.0.0,<12.0" plotly = ">=5.13.0,<6.0" diff --git a/reflex/app.py b/reflex/app.py index d290b8f49..2c8e889fc 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -22,6 +22,7 @@ from typing import ( TYPE_CHECKING, Any, AsyncIterator, + BinaryIO, Callable, Coroutine, Dict, @@ -35,12 +36,15 @@ from typing import ( 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.responses import JSONResponse, StreamingResponse from fastapi.staticfiles import StaticFiles from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn 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.view import ModelView @@ -231,6 +235,53 @@ class OverlayFragment(Fragment): 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( frozen=True, ) @@ -591,7 +642,9 @@ class App(MiddlewareMixin, LifespanMixin): Returns: 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( self, @@ -1583,7 +1636,7 @@ def upload(app: App): The upload function. """ - async def upload_file(request: Request, files: List[UploadFile]): + async def upload_file(request: Request, files: List[FastAPIUploadFile]): """Upload a file. Args: @@ -1659,7 +1712,8 @@ def upload(app: App): file_copies.append( UploadFile( file=content_copy, - filename=file.filename, + path=Path(file.filename.lstrip("/")) if file.filename else None, + _deprecated_filename=file.filename, size=file.size, headers=file.headers, ) diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 7cd87fb71..667a477e8 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -4,7 +4,7 @@ from __future__ import annotations from datetime import datetime 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.compiler import templates, utils @@ -545,7 +545,47 @@ def purge_web_pages_dir(): 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( @@ -568,12 +608,7 @@ def compile_unevaluated_page( The compiled component and whether state should be enabled. """ # Generate the component if it is a callable. - 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 = into_component(page.component) component._add_style_recursive(style or {}, theme) @@ -678,10 +713,8 @@ class ExecutorSafeFunctions: The route, compiled component, and compiled 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) @classmethod diff --git a/reflex/components/component.py b/reflex/components/component.py index d27bddf78..005f7791d 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -51,13 +51,7 @@ from reflex.event import ( ) from reflex.style import Style, format_as_emotion from reflex.utils import format, imports, types -from reflex.utils.imports import ( - ImmutableParsedImportDict, - ImportDict, - ImportVar, - ParsedImportDict, - parse_imports, -) +from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports from reflex.vars import VarData from reflex.vars.base import ( CachedVarOperation, @@ -1208,7 +1202,7 @@ class Component(BaseComponent, ABC): Returns: True if the dependency should be transpiled. """ - return ( + return bool(self.transpile_packages) and ( dep 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 {} # Collect imports from Vars used directly by this component. - var_datas = [var._get_all_var_data() for var in self._get_vars()] - var_imports: List[ImmutableParsedImportDict] = [ - var_data.imports for var_data in var_datas if var_data is not None + var_imports = [ + var_data.imports + for var in self._get_vars() + if (var_data := var._get_all_var_data()) is not None ] added_import_dicts: list[ParsedImportDict] = [] diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index 897b89608..6c86d3c44 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -29,7 +29,7 @@ from reflex.event import ( from reflex.utils import format from reflex.utils.imports import ImportVar 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 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: """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: """Get the list of selected files. diff --git a/reflex/components/core/upload.pyi b/reflex/components/core/upload.pyi index 6ed96a15e..d1ddceb4d 100644 --- a/reflex/components/core/upload.pyi +++ b/reflex/components/core/upload.pyi @@ -13,14 +13,12 @@ from reflex.event import CallableEventSpec, EventSpec, EventType from reflex.style import Style from reflex.utils.imports import ImportVar from reflex.vars import VarData -from reflex.vars.base import CallableVar, Var +from reflex.vars.base import Var DEFAULT_UPLOAD_ID: str upload_files_context_var_data: VarData -@CallableVar def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ... -@CallableVar def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ... @CallableEventSpec def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ... diff --git a/reflex/components/radix/themes/color_mode.py b/reflex/components/radix/themes/color_mode.py index d9b7c0b02..0718aaac9 100644 --- a/reflex/components/radix/themes/color_mode.py +++ b/reflex/components/radix/themes/color_mode.py @@ -144,7 +144,7 @@ class ColorModeIconButton(IconButton): if allow_system: - def color_mode_item(_color_mode: str): + def color_mode_item(_color_mode: Literal["light", "dark", "system"]): return dropdown_menu.item( _color_mode.title(), on_click=set_color_mode(_color_mode) ) diff --git a/reflex/config.py b/reflex/config.py index 233087938..33009b3bc 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -10,6 +10,7 @@ import os import sys import threading import urllib.parse +from functools import lru_cache from importlib.util import find_spec from pathlib import Path from types import ModuleType @@ -408,6 +409,19 @@ class EnvVar(Generic[T]): 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] """Descriptor for environment variables.""" @@ -434,7 +448,9 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration] """ self.name = name - def __get__(self, instance: Any, owner: Any): + def __get__( + self, instance: EnvironmentVariables, owner: type[EnvironmentVariables] + ): """Get the EnvVar instance. Args: @@ -444,7 +460,7 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration] Returns: 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 if self.internal: env_name = f"__{env_name}" diff --git a/reflex/state.py b/reflex/state.py index 92aaa4710..77c352cfa 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1742,6 +1742,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): Yields: 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 @@ -1779,12 +1782,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): hinted_args, (Base, BaseModelV1, BaseModelV2) ): 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) - if isinstance(value, list) and ( + elif isinstance(value, list) and ( hinted_args is tuple or hinted_args is Tuple ): 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. try: @@ -2459,6 +2475,8 @@ class ComponentState(State, mixin=True): Returns: 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 state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}" component_state = type( @@ -2470,6 +2488,7 @@ class ComponentState(State, mixin=True): # Save a reference to the dynamic state for pickle/unpickle. setattr(reflex.istate.dynamic, state_cls_name, component_state) component = component_state.get_component(*children, **props) + component = into_component(component) component.State = component_state return component diff --git a/reflex/style.py b/reflex/style.py index 192835ca3..1d818ed06 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError from reflex.utils.imports import ImportVar from reflex.utils.types import get_origin 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.object import ObjectVar @@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var: ).guess_type() -@CallableVar def set_color_mode( new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None, ) -> Var[EventChain]: diff --git a/reflex/vars/base.py b/reflex/vars/base.py index a24db4010..a6786b18a 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -951,7 +951,7 @@ class Var(Generic[VAR_TYPE]): """ actual_name = self._var_field_name - def setter(state: BaseState, value: Any): + def setter(state: Any, value: Any): """Get the setter for the var. Args: @@ -969,6 +969,8 @@ class Var(Generic[VAR_TYPE]): else: setattr(state, actual_name, value) + setter.__annotations__["value"] = self._var_type + setter.__qualname__ = self._get_setter_name() 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") DICT_KEY = TypeVar("DICT_KEY") diff --git a/tests/integration/test_background_task.py b/tests/integration/test_background_task.py index f312f8122..91a1b5ae1 100644 --- a/tests/integration/test_background_task.py +++ b/tests/integration/test_background_task.py @@ -20,7 +20,11 @@ def BackgroundTask(): class State(rx.State): counter: 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) async def handle_event(self): @@ -125,8 +129,8 @@ def BackgroundTask(): rx.input( id="iterations", placeholder="Iterations", - value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue] - on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue] + value=State.iterations.to_string(), + on_change=State.set_iterations, ), rx.button( "Delayed Increment", diff --git a/tests/integration/test_upload.py b/tests/integration/test_upload.py index e20b1cd6d..471382570 100644 --- a/tests/integration/test_upload.py +++ b/tests/integration/test_upload.py @@ -87,7 +87,7 @@ def UploadFile(): ), rx.box( rx.foreach( - rx.selected_files, + rx.selected_files(), lambda f: rx.text(f, as_="p"), ), id="selected_files", diff --git a/tests/integration/tests_playwright/test_appearance.py b/tests/integration/tests_playwright/test_appearance.py index d325b183f..0b1440ed1 100644 --- a/tests/integration/tests_playwright/test_appearance.py +++ b/tests/integration/tests_playwright/test_appearance.py @@ -61,7 +61,7 @@ def ColorToggleApp(): rx.icon(tag="moon", size=20), value="dark", ), - on_change=set_color_mode, + on_change=set_color_mode(), variant="classic", radius="large", value=color_mode,