Skip to content

Commit 0875a3e

Browse files
adhami3310masenf
authored andcommitted
use getattr when given str in getitem (#4761)
* use getattr when given str in getitem * stronger checking and tests * switch ordering * use safe issubclass * calculate origin differently
1 parent 88a44f4 commit 0875a3e

File tree

2 files changed

+32
-4
lines changed

2 files changed

+32
-4
lines changed

reflex/vars/object.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@
2222

2323
from reflex.utils import types
2424
from reflex.utils.exceptions import VarAttributeError
25-
from reflex.utils.types import GenericType, get_attribute_access_type, get_origin
25+
from reflex.utils.types import (
26+
GenericType,
27+
get_attribute_access_type,
28+
get_origin,
29+
safe_issubclass,
30+
)
2631

2732
from .base import (
2833
CachedVarOperation,
@@ -187,10 +192,14 @@ def __getitem__(self, key: Var | Any) -> Var:
187192
Returns:
188193
The item from the object.
189194
"""
195+
from .sequence import LiteralStringVar
196+
190197
if not isinstance(key, (StringVar, str, int, NumberVar)) or (
191198
isinstance(key, NumberVar) and key._is_strict_float()
192199
):
193200
raise_unsupported_operand_types("[]", (type(self), type(key)))
201+
if isinstance(key, str) and isinstance(Var.create(key), LiteralStringVar):
202+
return self.__getattr__(key)
194203
return ObjectItemOperation.create(self, key).guess_type()
195204

196205
# NoReturn is used here to catch when key value is Any
@@ -260,12 +269,12 @@ def __getattr__(self, name: str) -> Var:
260269
if types.is_optional(var_type):
261270
var_type = get_args(var_type)[0]
262271

263-
fixed_type = var_type if isclass(var_type) else get_origin(var_type)
272+
fixed_type = get_origin(var_type) or var_type
264273

265274
if (
266-
(isclass(fixed_type) and not issubclass(fixed_type, Mapping))
275+
is_typeddict(fixed_type)
276+
or (isclass(fixed_type) and not safe_issubclass(fixed_type, Mapping))
267277
or (fixed_type in types.UnionTypes)
268-
or is_typeddict(fixed_type)
269278
):
270279
attribute_type = get_attribute_access_type(var_type, name)
271280
if attribute_type is None:

tests/integration/test_var_operations.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010

1111
def VarOperations():
1212
"""App with var operations."""
13+
from typing import TypedDict
14+
1315
import reflex as rx
1416
from reflex.vars.base import LiteralVar
1517
from reflex.vars.sequence import ArrayVar
1618

1719
class Object(rx.Base):
1820
name: str = "hello"
1921

22+
class Person(TypedDict):
23+
name: str
24+
age: int
25+
2026
class VarOperationState(rx.State):
2127
int_var1: rx.Field[int] = rx.field(10)
2228
int_var2: rx.Field[int] = rx.field(5)
@@ -34,6 +40,9 @@ class VarOperationState(rx.State):
3440
dict1: rx.Field[dict[int, int]] = rx.field({1: 2})
3541
dict2: rx.Field[dict[int, int]] = rx.field({3: 4})
3642
html_str: rx.Field[str] = rx.field("<div>hello</div>")
43+
people: rx.Field[list[Person]] = rx.field(
44+
[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
45+
)
3746

3847
app = rx.App(_state=rx.State)
3948

@@ -619,6 +628,15 @@ def index():
619628
),
620629
id="dict_in_foreach3",
621630
),
631+
rx.box(
632+
rx.foreach(
633+
VarOperationState.people,
634+
lambda person: rx.text.span(
635+
"Hello " + person["name"], person["age"] + 3
636+
),
637+
),
638+
id="typed_dict_in_foreach",
639+
),
622640
)
623641

624642

@@ -826,6 +844,7 @@ def test_var_operations(driver, var_operations: AppHarness):
826844
("dict_in_foreach1", "a1b2"),
827845
("dict_in_foreach2", "12"),
828846
("dict_in_foreach3", "1234"),
847+
("typed_dict_in_foreach", "Hello Alice33Hello Bob28"),
829848
]
830849

831850
for tag, expected in tests:

0 commit comments

Comments
 (0)