From 2ba73f7ff930a1cc3363851faad2cb9d7119c4f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= <thomas.brandeho@gmail.com>
Date: Thu, 13 Feb 2025 19:19:33 +0100
Subject: [PATCH 1/7] bump ruff to 0.9.6 (#4817)

---
 .pre-commit-config.yaml |  2 +-
 poetry.lock             | 40 ++++++++++++++++++++--------------------
 pyproject.toml          |  2 +-
 3 files changed, 22 insertions(+), 22 deletions(-)

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"

From 10c45b185c597d1c7828715d9360a5e92a8b7fd2 Mon Sep 17 00:00:00 2001
From: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
Date: Thu, 13 Feb 2025 12:44:27 -0800
Subject: [PATCH 2/7] adjust setter to include type annotation (#4726)

* adjust setter to include type annotation

* apparently this discovered some bugs

* remove some pyright ignores

* add str to int/float conversion

* dang it darglint
---
 benchmarks/test_benchmark_compile_pages.py | 32 ++++++++++++++++------
 reflex/state.py                            | 20 ++++++++++++--
 reflex/vars/base.py                        |  4 ++-
 tests/integration/test_background_task.py  | 10 +++++--
 4 files changed, 52 insertions(+), 14 deletions(-)

diff --git a/benchmarks/test_benchmark_compile_pages.py b/benchmarks/test_benchmark_compile_pages.py
index 149fc6130..6cf39f60c 100644
--- a/benchmarks/test_benchmark_compile_pages.py
+++ b/benchmarks/test_benchmark_compile_pages.py
@@ -46,10 +46,26 @@ def render_multiple_pages(app, num: int):
     class State(rx.State):
         """The app state."""
 
-        position: str
-        college: str
-        age: Tuple[int, int] = (18, 50)
-        salary: Tuple[int, int] = (0, 25000000)
+        position: rx.Field[str]
+        college: rx.Field[str]
+        age: rx.Field[Tuple[int, int]] = rx.field((18, 50))
+        salary: rx.Field[Tuple[int, int]] = rx.field((0, 25000000))
+
+        @rx.event
+        def set_position(self, value: str):
+            self.position = value
+
+        @rx.event
+        def set_college(self, value: str):
+            self.college = value
+
+        @rx.event
+        def set_age(self, value: list[int]):
+            self.age = (value[0], value[1])
+
+        @rx.event
+        def set_salary(self, value: list[int]):
+            self.salary = (value[0], value[1])
 
     comp1 = rx.center(
         rx.theme_panel(),
@@ -74,13 +90,13 @@ def render_multiple_pages(app, num: int):
                 rx.select(
                     ["C", "PF", "SF", "PG", "SG"],
                     placeholder="Select a position. (All)",
-                    on_change=State.set_position,  # pyright: ignore [reportAttributeAccessIssue]
+                    on_change=State.set_position,
                     size="3",
                 ),
                 rx.select(
                     college,
                     placeholder="Select a college. (All)",
-                    on_change=State.set_college,  # pyright: ignore [reportAttributeAccessIssue]
+                    on_change=State.set_college,
                     size="3",
                 ),
             ),
@@ -95,7 +111,7 @@ def render_multiple_pages(app, num: int):
                         default_value=[18, 50],
                         min=18,
                         max=50,
-                        on_value_commit=State.set_age,  # pyright: ignore [reportAttributeAccessIssue]
+                        on_value_commit=State.set_age,
                     ),
                     align_items="left",
                     width="100%",
@@ -110,7 +126,7 @@ def render_multiple_pages(app, num: int):
                         default_value=[0, 25000000],
                         min=0,
                         max=25000000,
-                        on_value_commit=State.set_salary,  # pyright: ignore [reportAttributeAccessIssue]
+                        on_value_commit=State.set_salary,
                     ),
                     align_items="left",
                     width="100%",
diff --git a/reflex/state.py b/reflex/state.py
index 92aaa4710..dceba7e3b 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:
diff --git a/reflex/vars/base.py b/reflex/vars/base.py
index a24db4010..593c60f3e 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
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",

From c6fb4e238d93c4383f1774055fcdc096f5f0fec6 Mon Sep 17 00:00:00 2001
From: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
Date: Thu, 13 Feb 2025 12:44:42 -0800
Subject: [PATCH 3/7] improve into component conversion (#4754)

* improve into component conversion

* correct the order of .State
---
 reflex/app.py               |  4 ++-
 reflex/compiler/compiler.py | 55 +++++++++++++++++++++++++++++--------
 reflex/state.py             |  3 ++
 3 files changed, 50 insertions(+), 12 deletions(-)

diff --git a/reflex/app.py b/reflex/app.py
index d290b8f49..67d4f5b91 100644
--- a/reflex/app.py
+++ b/reflex/app.py
@@ -591,7 +591,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,
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/state.py b/reflex/state.py
index dceba7e3b..77c352cfa 100644
--- a/reflex/state.py
+++ b/reflex/state.py
@@ -2475,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(
@@ -2486,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
 

From 40294a7c9e68f2355bc71e2ccb9be1473c11b533 Mon Sep 17 00:00:00 2001
From: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
Date: Thu, 13 Feb 2025 13:36:59 -0800
Subject: [PATCH 4/7] standarize filename from upload (#4734)

* standarize filename from upload

* all my friends hate fast api upload file

* make deprecated filename private

* lstrip the "/"

Co-authored-by: Masen Furer <m_github@0x26.net>

---------

Co-authored-by: Masen Furer <m_github@0x26.net>
---
 reflex/app.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 55 insertions(+), 3 deletions(-)

diff --git a/reflex/app.py b/reflex/app.py
index 67d4f5b91..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,
 )
@@ -1585,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:
@@ -1661,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,
                 )

From 6fb491471bc845ec8a556cb82509cf8bb084cbd3 Mon Sep 17 00:00:00 2001
From: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
Date: Thu, 13 Feb 2025 13:44:02 -0800
Subject: [PATCH 5/7] cache get_type_hints for environment (#4820)

---
 reflex/config.py | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

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}"

From aac61c69c2c2fdcb37f3cc67cab92623386c239f Mon Sep 17 00:00:00 2001
From: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
Date: Thu, 13 Feb 2025 15:40:01 -0800
Subject: [PATCH 6/7] actually get rid of callable var fr fr (#4821)

---
 reflex/components/core/upload.py              |  4 +-
 reflex/components/core/upload.pyi             |  4 +-
 reflex/components/radix/themes/color_mode.py  |  2 +-
 reflex/style.py                               |  3 +-
 reflex/vars/base.py                           | 55 -------------------
 tests/integration/test_upload.py              |  2 +-
 .../tests_playwright/test_appearance.py       |  2 +-
 7 files changed, 6 insertions(+), 66 deletions(-)

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/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 593c60f3e..a6786b18a 100644
--- a/reflex/vars/base.py
+++ b/reflex/vars/base.py
@@ -1903,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_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,

From b44bbc81a0a707ded71c9f0d0a9d4afb0c308bdd Mon Sep 17 00:00:00 2001
From: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
Date: Thu, 13 Feb 2025 15:54:10 -0800
Subject: [PATCH 7/7] import var perf improvements (#4813)

* import var perf improvements

* use tuples over iterator

* the only thing that matters

* maybe tuple map is faster than tuple list comprehension

* do it in one list comprehension
---
 reflex/components/component.py | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

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] = []