فهرست منبع

[REF-2682] Foreach over dict uses Tuple arg value (#3160)

* test_foreach: assert on arg _var_type

* [REF-2682] Foreach over dict uses Tuple arg value

When iterating over a Var with _var_type dict, the resulting arg value
_var_type should be Tuple[key, value] so it can be correctly used with other
var operations.

Fix #3157

* Correct _var_type for iteration over Tuple of multiple types

The arg value when iterating over a tuple could be any of the possible values
mentioned in the annotation.

When only one type is used, the Union collapses to the base type, at least in py3.11

* Add comments
Masen Furer 1 سال پیش
والد
کامیت
0a8aaea599
2فایلهای تغییر یافته به همراه37 افزوده شده و 8 حذف شده
  1. 9 6
      reflex/components/tags/iter_tag.py
  2. 28 2
      tests/components/core/test_foreach.py

+ 9 - 6
reflex/components/tags/iter_tag.py

@@ -2,7 +2,7 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import inspect
 import inspect
-from typing import TYPE_CHECKING, Any, Callable, List, Type
+from typing import TYPE_CHECKING, Any, Callable, List, Tuple, Type, Union, get_args
 
 
 from reflex.components.tags.tag import Tag
 from reflex.components.tags.tag import Tag
 from reflex.vars import BaseVar, Var
 from reflex.vars import BaseVar, Var
@@ -33,11 +33,14 @@ class IterTag(Tag):
             The type of the iterable var.
             The type of the iterable var.
         """
         """
         try:
         try:
-            return (
-                self.iterable._var_type
-                if self.iterable._var_type.mro()[0] == dict
-                else self.iterable._var_type.__args__[0]
-            )
+            if self.iterable._var_type.mro()[0] == dict:
+                # Arg is a tuple of (key, value).
+                return Tuple[get_args(self.iterable._var_type)]  # type: ignore
+            elif self.iterable._var_type.mro()[0] == tuple:
+                # Arg is a union of any possible values in the tuple.
+                return Union[get_args(self.iterable._var_type)]  # type: ignore
+            else:
+                return get_args(self.iterable._var_type)[0]
         except Exception:
         except Exception:
             return Any
             return Any
 
 

+ 28 - 2
tests/components/core/test_foreach.py

@@ -1,10 +1,11 @@
-from typing import Dict, List, Set, Tuple
+from typing import Dict, List, Set, Tuple, Union
 
 
 import pytest
 import pytest
 
 
 from reflex.components import box, foreach, text, theme
 from reflex.components import box, foreach, text, theme
 from reflex.components.core import Foreach
 from reflex.components.core import Foreach
 from reflex.state import BaseState
 from reflex.state import BaseState
+from reflex.vars import Var
 
 
 try:
 try:
     # When pydantic v2 is installed
     # When pydantic v2 is installed
@@ -39,29 +40,36 @@ class ForEachState(BaseState):
     )
     )
     colors_set: Set[str] = {"red", "green"}
     colors_set: Set[str] = {"red", "green"}
     bad_annotation_list: list = [["red", "orange"], ["yellow", "blue"]]
     bad_annotation_list: list = [["red", "orange"], ["yellow", "blue"]]
+    color_index_tuple: Tuple[int, str] = (0, "red")
 
 
 
 
 def display_color(color):
 def display_color(color):
+    assert color._var_type == str
     return box(text(color))
     return box(text(color))
 
 
 
 
 def display_color_name(color):
 def display_color_name(color):
+    assert color._var_type == Dict[str, str]
     return box(text(color["name"]))
     return box(text(color["name"]))
 
 
 
 
 def display_shade(color):
 def display_shade(color):
+    assert color._var_type == Dict[str, List[str]]
     return box(text(color["shades"][0]))
     return box(text(color["shades"][0]))
 
 
 
 
 def display_primary_colors(color):
 def display_primary_colors(color):
+    assert color._var_type == Tuple[str, str]
     return box(text(color[0]), text(color[1]))
     return box(text(color[0]), text(color[1]))
 
 
 
 
 def display_color_with_shades(color):
 def display_color_with_shades(color):
+    assert color._var_type == Tuple[str, List[str]]
     return box(text(color[0]), text(color[1][0]))
     return box(text(color[0]), text(color[1][0]))
 
 
 
 
 def display_nested_color_with_shades(color):
 def display_nested_color_with_shades(color):
+    assert color._var_type == Tuple[str, Dict[str, List[Dict[str, str]]]]
     return box(text(color[0]), text(color[1]["red"][0]["shade"]))
     return box(text(color[0]), text(color[1]["red"][0]["shade"]))
 
 
 
 
@@ -70,21 +78,31 @@ def show_shade(item):
 
 
 
 
 def display_nested_color_with_shades_v2(color):
 def display_nested_color_with_shades_v2(color):
+    assert color._var_type == Tuple[str, Dict[str, List[Dict[str, str]]]]
     return box(text(foreach(color[1], show_shade)))
     return box(text(foreach(color[1], show_shade)))
 
 
 
 
 def display_color_tuple(color):
 def display_color_tuple(color):
+    assert color._var_type == str
     return box(text(color, "tuple"))
     return box(text(color, "tuple"))
 
 
 
 
 def display_colors_set(color):
 def display_colors_set(color):
+    assert color._var_type == str
     return box(text(color, "set"))
     return box(text(color, "set"))
 
 
 
 
-def display_nested_list_element(element: str, index: int):
+def display_nested_list_element(element: Var[str], index: Var[int]):
+    assert element._var_type == List[str]
+    assert index._var_type == int
     return box(text(element[index]))
     return box(text(element[index]))
 
 
 
 
+def display_color_index_tuple(color):
+    assert color._var_type == Union[int, str]
+    return box(text(color, "index_tuple"))
+
+
 seen_index_vars = set()
 seen_index_vars = set()
 
 
 
 
@@ -171,6 +189,14 @@ seen_index_vars = set()
                 "iterable_type": "list",
                 "iterable_type": "list",
             },
             },
         ),
         ),
+        (
+            ForEachState.color_index_tuple,
+            display_color_index_tuple,
+            {
+                "iterable_state": "for_each_state.color_index_tuple",
+                "iterable_type": "tuple",
+            },
+        ),
     ],
     ],
 )
 )
 def test_foreach_render(state_var, render_fn, render_dict):
 def test_foreach_render(state_var, render_fn, render_dict):