Repository: reflex-dev/reflex

Issue:
issubclass() arg 1 must be a class
**Describe the bug**
My code was worked before I got this error:
```
RuntimeError: Cannot populate parent states of reflexstate_state.cyber_zstate_auth_state.cyber_zpagesadminusers_admin_page_users_section_state
without redis. (All states should already be available -- this is likely a bug).
```

then I update reflex to version `0.6.5` and I got this error:
```
 Traceback (most recent call last):
  File "C:\Users\mhame\OneDrive\سطح المكتب\CyberZ\CyberZ.Website\.venv\lib\site-packages\reflex\app.py", line 1282, in process
    async for update in state._process(event):
  File "C:\Users\mhame\OneDrive\سطح المكتب\CyberZ\CyberZ.Website\.venv\lib\site-packages\reflex\state.py", line 1610, in _process
    async for update in self._process_event(
  File "C:\Users\mhame\OneDrive\سطح المكتب\CyberZ\CyberZ.Website\.venv\lib\site-packages\reflex\state.py", line 1738, in _process_event
    if issubclass(hinted_args, Model):
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class
```

**To Reproduce**
Steps to reproduce the behavior:
 - Code/Link to Repo:

**Screenshots**
<img width="859" alt="image" src="https://github.com/user-attachments/assets/afce3938-3465-41a4-bdf7-bb4715a1910f">

**Specifics (please complete the following information):**
 - Python Version: 3.10
 - Reflex Version: 0.6.5
 - OS: Windows
 - Browser (Optional): Microsoft Edge


Discussion:
<p><a href="https://linear.app/reflex-dev/issue/ENG-4137/issubclass-arg-1-must-be-a-class">ENG-4137 issubclass() arg 1 must be a class</a></p>
do you have code that reproduces this?
@masenf It's happening when I press on login

login.py
```
class LoginState(rx.State):
    email: rx.Field[str] = rx.field("")
    password: rx.Field[str] = rx.field("")

    alert_open: rx.Field[bool] = rx.field(False)
    alert_title: rx.Field[str] = rx.field("")
    alert_message: rx.Field[str] = rx.field("")

    _is_redirected: rx.Field[bool] = rx.field(False)
    _redirected_url: rx.Field[str] = rx.field("")

    def _create_token(self, user: Any) -> str:
        """Create a JWT token for the authenticated user.
        
        Args:
            user: The user object from the database
            
        Returns:
            str: The encoded JWT token
        """
        # Current timestamp for token claims
        current_time = int(time.time())
        
        # Create token payload
        token_data = {
            "sub": str(user.id),  # Subject (user ID)
            "email": user.email,
            "iat": current_time,  # Issued at
            "exp": current_time + EXPIRATION_TIME,  # Expires in 24 hours
            "aud": "authenticated"  # Audience claim
        }
        
        # Encode and return the token
        return jwt.encode(
            token_data,
            JWT_SECRET_KEY,
            algorithm=JWT_ALGORITHM
        )
    
    @rx.event
    async def login(self):
        is_valid_email, email_error = validate_email(self.email)
        if not is_valid_email:
            self.alert_open = True
            self.alert_title = "Invalid Email"
            self.alert_message = email_error
            return
    
        # Validate password
        is_valid_password, password_error = validate_password(self.password)
        if not is_valid_password:
            self.alert_open = True
            self.alert_title = "Invalid Password"
            self.alert_message = password_error
            return
        
        # Opens a new database session, ensuring proper resource management. 
        # The with block ensures that the session is closed properly after the operation, regardless of whether an exception occurs.
        with rx.session() as session:
            user = session.exec(
                Accounts.select().where(Accounts.email == self.email)
            ).first()
        
            if user and bcrypt.checkpw(
                self.password.encode("utf-8"), 
                user.password.encode("utf-8")
            ):
                self.password = ""

                auth_state = await self.get_state(AuthState)
                auth_state.auth_token = self._create_token(user)

                if not self._is_redirected:
                    return rx.redirect("/home")
                else:
                    self._redirected_url = ""
                    self._is_redirected = False
                    return rx.redirect(self._redirected_url)
            else:
                self.alert_open = True
                self.alert_title = "Login Failed"
                self.alert_message = "Invalid email or password. Please try again."

    @rx.event
    async def check_auth(self):
        auth_state = await self.get_state(AuthState)
        if auth_state.token_is_valid:
            return rx.redirect("/home")
```
models.accounts:
```
import reflex as rx

class Accounts(rx.Model, table=True):
    __table_args__ = {"schema": "accounts"}

    id
    email: str
    password: str
```
@masenf I'm sorry, but it's happening directly after the website start
minimal repro, python3.10

```python
import reflex as rx


class State(rx.State):
    def my_stinking_event_handler(self, foo: dict[str, str]):
        print(foo)


def index() -> rx.Component:
    return rx.button("Foo", on_click=State.my_stinking_event_handler({"foo": "bar"}))


app = rx.App()
app.add_page(index)
```



Patch:
diff --git a/reflex/state.py b/reflex/state.py
index 349dc59e973..95f7f64f68c 100644
--- a/reflex/state.py
+++ b/reflex/state.py
@@ -1748,7 +1748,11 @@ async def _process_event(
                 if value is None:
                     continue
                 hinted_args = value_inside_optional(hinted_args)
-            if isinstance(value, dict) and inspect.isclass(hinted_args):
+            if (
+                isinstance(value, dict)
+                and inspect.isclass(hinted_args)
+                and not types.is_generic_alias(hinted_args)  # py3.9-py3.10
+            ):
                 if issubclass(hinted_args, Model):
                     # Remove non-fields from the payload
                     payload[arg] = hinted_args(
@@ -1759,7 +1763,7 @@ async def _process_event(
                         }
                     )
                 elif dataclasses.is_dataclass(hinted_args) or issubclass(
-                    hinted_args, Base
+                    hinted_args, (Base, BaseModelV1, BaseModelV2)
                 ):
                     payload[arg] = hinted_args(**value)
             if isinstance(value, list) and (hinted_args is set or hinted_args is Set):


Test patch:
diff --git a/tests/units/test_state.py b/tests/units/test_state.py
index c8a52e6c0b7..45c021bd82c 100644
--- a/tests/units/test_state.py
+++ b/tests/units/test_state.py
@@ -10,7 +10,17 @@
 import sys
 import threading
 from textwrap import dedent
-from typing import Any, AsyncGenerator, Callable, Dict, List, Optional, Union
+from typing import (
+    Any,
+    AsyncGenerator,
+    Callable,
+    Dict,
+    List,
+    Optional,
+    Set,
+    Tuple,
+    Union,
+)
 from unittest.mock import AsyncMock, Mock
 
 import pytest
@@ -1828,12 +1838,11 @@ async def _coro_waiter():
 
 
 @pytest.fixture(scope="function")
-def mock_app(monkeypatch, state_manager: StateManager) -> rx.App:
-    """Mock app fixture.
+def mock_app_simple(monkeypatch) -> rx.App:
+    """Simple Mock app fixture.
 
     Args:
         monkeypatch: Pytest monkeypatch object.
-        state_manager: A state manager.
 
     Returns:
         The app, after mocking out prerequisites.get_app()
@@ -1844,7 +1853,6 @@ def mock_app(monkeypatch, state_manager: StateManager) -> rx.App:
 
     setattr(app_module, CompileVars.APP, app)
     app.state = TestState
-    app._state_manager = state_manager
     app.event_namespace.emit = AsyncMock()  # type: ignore
 
     def _mock_get_app(*args, **kwargs):
@@ -1854,6 +1862,21 @@ def _mock_get_app(*args, **kwargs):
     return app
 
 
+@pytest.fixture(scope="function")
+def mock_app(mock_app_simple: rx.App, state_manager: StateManager) -> rx.App:
+    """Mock app fixture.
+
+    Args:
+        mock_app_simple: A simple mock app.
+        state_manager: A state manager.
+
+    Returns:
+        The app, after mocking out prerequisites.get_app()
+    """
+    mock_app_simple._state_manager = state_manager
+    return mock_app_simple
+
+
 @pytest.mark.asyncio
 async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App):
     """Test that the state proxy works.
@@ -3506,3 +3529,106 @@ class SubMixin(Mixin, mixin=True):
 
     with pytest.raises(ReflexRuntimeError):
         SubMixin()
+
+
+class ReflexModel(rx.Model):
+    """A model for testing."""
+
+    foo: str
+
+
+class UpcastState(rx.State):
+    """A state for testing upcasting."""
+
+    passed: bool = False
+
+    def rx_model(self, m: ReflexModel):  # noqa: D102
+        assert isinstance(m, ReflexModel)
+        self.passed = True
+
+    def rx_base(self, o: Object):  # noqa: D102
+        assert isinstance(o, Object)
+        self.passed = True
+
+    def rx_base_or_none(self, o: Optional[Object]):  # noqa: D102
+        if o is not None:
+            assert isinstance(o, Object)
+        self.passed = True
+
+    def rx_basemodelv1(self, m: ModelV1):  # noqa: D102
+        assert isinstance(m, ModelV1)
+        self.passed = True
+
+    def rx_basemodelv2(self, m: ModelV2):  # noqa: D102
+        assert isinstance(m, ModelV2)
+        self.passed = True
+
+    def rx_dataclass(self, dc: ModelDC):  # noqa: D102
+        assert isinstance(dc, ModelDC)
+        self.passed = True
+
+    def py_set(self, s: set):  # noqa: D102
+        assert isinstance(s, set)
+        self.passed = True
+
+    def py_Set(self, s: Set):  # noqa: D102
+        assert isinstance(s, Set)
+        self.passed = True
+
+    def py_tuple(self, t: tuple):  # noqa: D102
+        assert isinstance(t, tuple)
+        self.passed = True
+
+    def py_Tuple(self, t: Tuple):  # noqa: D102
+        assert isinstance(t, tuple)
+        self.passed = True
+
+    def py_dict(self, d: dict[str, str]):  # noqa: D102
+        assert isinstance(d, dict)
+        self.passed = True
+
+    def py_list(self, ls: list[str]):  # noqa: D102
+        assert isinstance(ls, list)
+        self.passed = True
+
+    def py_Any(self, a: Any):  # noqa: D102
+        assert isinstance(a, list)
+        self.passed = True
+
+    def py_unresolvable(self, u: "Unresolvable"):  # noqa: D102, F821  # type: ignore
+        assert isinstance(u, list)
+        self.passed = True
+
+
+@pytest.mark.asyncio
+@pytest.mark.usefixtures("mock_app_simple")
+@pytest.mark.parametrize(
+    ("handler", "payload"),
+    [
+        (UpcastState.rx_model, {"m": {"foo": "bar"}}),
+        (UpcastState.rx_base, {"o": {"foo": "bar"}}),
+        (UpcastState.rx_base_or_none, {"o": {"foo": "bar"}}),
+        (UpcastState.rx_base_or_none, {"o": None}),
+        (UpcastState.rx_basemodelv1, {"m": {"foo": "bar"}}),
+        (UpcastState.rx_basemodelv2, {"m": {"foo": "bar"}}),
+        (UpcastState.rx_dataclass, {"dc": {"foo": "bar"}}),
+        (UpcastState.py_set, {"s": ["foo", "foo"]}),
+        (UpcastState.py_Set, {"s": ["foo", "foo"]}),
+        (UpcastState.py_tuple, {"t": ["foo", "foo"]}),
+        (UpcastState.py_Tuple, {"t": ["foo", "foo"]}),
+        (UpcastState.py_dict, {"d": {"foo": "bar"}}),
+        (UpcastState.py_list, {"ls": ["foo", "foo"]}),
+        (UpcastState.py_Any, {"a": ["foo"]}),
+        (UpcastState.py_unresolvable, {"u": ["foo"]}),
+    ],
+)
+async def test_upcast_event_handler_arg(handler, payload):
+    """Test that upcast event handler args work correctly.
+
+    Args:
+        handler: The handler to test.
+        payload: The payload to test.
+    """
+    state = UpcastState()
+    async for update in state._process_event(handler, state, payload):
+        assert update.delta == {UpcastState.get_full_name(): {"passed": True}}