Skip to content

Commit 488a388

Browse files
authored
hide __getattribute__ from type checking (#6056)
* hide __getattribute__ from type checking * ok precommit * remove pyright ignores for isinstance checks * fix precommit for test
1 parent fcb937c commit 488a388

20 files changed

+213
-141
lines changed

reflex/istate/proxy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ def _mark_dirty(
758758
Raises:
759759
ImmutableStateError: if the StateProxy is not mutable.
760760
"""
761-
if not self._self_state._is_mutable():
761+
if not self._self_state._is_mutable(): # pyright: ignore[reportAttributeAccessIssue]
762762
msg = (
763763
"Background task StateProxy is immutable outside of a context "
764764
"manager. Use `async with self` to modify state."

reflex/istate/shared.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ async def _link_to(self, token: str) -> Self:
190190
if not token:
191191
msg = "Cannot link shared state to empty token."
192192
raise ReflexRuntimeError(msg)
193+
if not isinstance(self, SharedState):
194+
msg = "Can only link SharedState instances."
195+
raise RuntimeError(msg)
193196
if self._linked_to == token:
194197
return self # already linked to this token
195198
if self._linked_to and self._linked_to != token:
@@ -215,6 +218,10 @@ async def _unlink(self):
215218
"""
216219
from reflex.istate.manager import get_state_manager
217220

221+
if not isinstance(self, SharedState):
222+
msg = "Can only unlink SharedState instances."
223+
raise ReflexRuntimeError(msg)
224+
218225
state_name = self.get_full_name()
219226
if (
220227
not self._reflex_internal_links
@@ -272,6 +279,9 @@ async def _internal_patch_linked_state(
272279
_substate_key(token, type(self))
273280
)
274281
linked_state = await linked_root_state.get_state(type(self))
282+
if not isinstance(linked_state, SharedState):
283+
msg = f"Linked state for token {token} is not a SharedState."
284+
raise ReflexRuntimeError(msg)
275285
# Avoid unnecessary dirtiness of shared state when there are no changes.
276286
if type(self) not in self._held_locks[token]:
277287
self._held_locks[token][type(self)] = linked_state

reflex/state.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,7 +1350,7 @@ def _check_overwritten_dynamic_args(cls, args: list[str]):
13501350
for substate in cls.get_substates():
13511351
substate._check_overwritten_dynamic_args(args)
13521352

1353-
def __getattribute__(self, name: str) -> Any:
1353+
def _get_attribute(self, name: str) -> Any:
13541354
"""Get the state var.
13551355
13561356
If the var is inherited, get the var from the parent state.
@@ -1408,6 +1408,9 @@ def __getattribute__(self, name: str) -> Any:
14081408

14091409
return value
14101410

1411+
if not TYPE_CHECKING:
1412+
__getattribute__ = _get_attribute
1413+
14111414
def __setattr__(self, name: str, value: Any):
14121415
"""Set the attribute.
14131416

tests/integration/test_client_storage.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ def set_sub_sub(var: str, value: str):
669669
# Ensure the state is gone (not hydrated)
670670
async def poll_for_not_hydrated():
671671
state = await client_side.get_state(_substate_key(token or "", state_name))
672+
assert isinstance(state, State)
672673
return not state.is_hydrated
673674

674675
assert await AppHarness._poll_for_async(poll_for_not_hydrated)
@@ -723,30 +724,30 @@ async def get_sub_state():
723724

724725
async def poll_for_c1_set():
725726
sub_state = await get_sub_state()
726-
return sub_state.c1 == "c1 post expire"
727+
return sub_state.c1 == "c1 post expire" # pyright: ignore[reportAttributeAccessIssue]
727728

728729
assert await AppHarness._poll_for_async(poll_for_c1_set)
729730
sub_state = await get_sub_state()
730-
assert sub_state.c1 == "c1 post expire"
731-
assert sub_state.c2 == "c2 value"
732-
assert sub_state.c3 == ""
733-
assert sub_state.c4 == "c4 value"
734-
assert sub_state.c5 == "c5 value"
735-
assert sub_state.c6 == "c6 value"
736-
assert sub_state.c7 == "c7 value"
737-
assert sub_state.l1 == "l1 value"
738-
assert sub_state.l2 == "l2 value"
739-
assert sub_state.l3 == "l3 value"
740-
assert sub_state.l4 == "l4 value"
741-
assert sub_state.s1 == "s1 value"
742-
assert sub_state.s2 == "s2 value"
743-
assert sub_state.s3 == "s3 value"
731+
assert sub_state.c1 == "c1 post expire" # pyright: ignore[reportAttributeAccessIssue]
732+
assert sub_state.c2 == "c2 value" # pyright: ignore[reportAttributeAccessIssue]
733+
assert sub_state.c3 == "" # pyright: ignore[reportAttributeAccessIssue]
734+
assert sub_state.c4 == "c4 value" # pyright: ignore[reportAttributeAccessIssue]
735+
assert sub_state.c5 == "c5 value" # pyright: ignore[reportAttributeAccessIssue]
736+
assert sub_state.c6 == "c6 value" # pyright: ignore[reportAttributeAccessIssue]
737+
assert sub_state.c7 == "c7 value" # pyright: ignore[reportAttributeAccessIssue]
738+
assert sub_state.l1 == "l1 value" # pyright: ignore[reportAttributeAccessIssue]
739+
assert sub_state.l2 == "l2 value" # pyright: ignore[reportAttributeAccessIssue]
740+
assert sub_state.l3 == "l3 value" # pyright: ignore[reportAttributeAccessIssue]
741+
assert sub_state.l4 == "l4 value" # pyright: ignore[reportAttributeAccessIssue]
742+
assert sub_state.s1 == "s1 value" # pyright: ignore[reportAttributeAccessIssue]
743+
assert sub_state.s2 == "s2 value" # pyright: ignore[reportAttributeAccessIssue]
744+
assert sub_state.s3 == "s3 value" # pyright: ignore[reportAttributeAccessIssue]
744745
sub_sub_state = sub_state.substates[
745746
client_side.get_state_name("_client_side_sub_sub_state")
746747
]
747-
assert sub_sub_state.c1s == "c1s value"
748-
assert sub_sub_state.l1s == "l1s value"
749-
assert sub_sub_state.s1s == "s1s value"
748+
assert sub_sub_state.c1s == "c1s value" # pyright: ignore[reportAttributeAccessIssue]
749+
assert sub_sub_state.l1s == "l1s value" # pyright: ignore[reportAttributeAccessIssue]
750+
assert sub_sub_state.s1s == "s1s value" # pyright: ignore[reportAttributeAccessIssue]
750751

751752
# clear the cookie jar and local storage, ensure state reset to default
752753
driver.delete_all_cookies()

tests/integration/test_component_state.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ async def test_component_state_app(component_state_app: AppHarness):
167167
a_state = root_state.substates[a_state_name]
168168
b_state = root_state.substates[b_state_name]
169169
assert a_state._backend_vars != a_state.backend_vars
170-
assert a_state._be == a_state._backend_vars["_be"] == 3
171-
assert b_state._be is None
170+
assert a_state._be == a_state._backend_vars["_be"] == 3 # pyright: ignore[reportAttributeAccessIssue]
171+
assert b_state._be is None # pyright: ignore[reportAttributeAccessIssue]
172172
assert b_state._backend_vars["_be"] is None
173173

174174
assert count_b.text == "0"
@@ -183,7 +183,7 @@ async def test_component_state_app(component_state_app: AppHarness):
183183
a_state = root_state.substates[a_state_name]
184184
b_state = root_state.substates[b_state_name]
185185
assert b_state._backend_vars != b_state.backend_vars
186-
assert b_state._be == b_state._backend_vars["_be"] == 2
186+
assert b_state._be == b_state._backend_vars["_be"] == 2 # pyright: ignore[reportAttributeAccessIssue]
187187

188188
# Check locally-defined substate style
189189
count_c = driver.find_element(By.ID, "count-c")

tests/integration/test_computed_vars.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ async def test_computed_vars(
203203
token = f"{token}_{full_state_name}"
204204
state = (await computed_vars.get_state(token)).substates[state_name]
205205
assert state is not None
206-
assert state.count1_backend == 0
207-
assert state._count1_backend == 0
206+
assert state.count1_backend == 0 # pyright: ignore[reportAttributeAccessIssue]
207+
assert state._count1_backend == 0 # pyright: ignore[reportAttributeAccessIssue]
208208

209209
# test that backend var is not rendered
210210
count1_backend = driver.find_element(By.ID, "count1_backend")
@@ -259,9 +259,9 @@ async def test_computed_vars(
259259
)
260260
state = (await computed_vars.get_state(token)).substates[state_name]
261261
assert state is not None
262-
assert state.count1_backend == 1
262+
assert state.count1_backend == 1 # pyright: ignore[reportAttributeAccessIssue]
263263
assert count1_backend.text == ""
264-
assert state._count1_backend == 1
264+
assert state._count1_backend == 1 # pyright: ignore[reportAttributeAccessIssue]
265265
assert count1_backend_.text == ""
266266

267267
mark_dirty.click()

tests/integration/test_dynamic_routes.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class DynamicState(rx.State):
2323

2424
@rx.event
2525
def on_load(self):
26-
page_data = f"{self.router.page.path}-{self.page_id or 'no page id'}"
26+
page_data = f"{self.router.page.path}-{self.page_id or 'no page id'}" # pyright: ignore[reportAttributeAccessIssue]
2727
print(f"on_load: {page_data}")
2828
self.order.append(page_data)
2929

@@ -43,7 +43,7 @@ def on_load_static(self):
4343
@rx.var
4444
def next_page(self) -> str:
4545
try:
46-
return str(int(self.page_id) + 1)
46+
return str(int(self.page_id) + 1) # pyright: ignore[reportAttributeAccessIssue]
4747
except ValueError:
4848
return "0"
4949

@@ -81,7 +81,7 @@ class ArgState(rx.State):
8181

8282
@rx.var(cache=False)
8383
def arg(self) -> int:
84-
return int(self.arg_str or 0)
84+
return int(self.arg_str or 0) # pyright: ignore[reportAttributeAccessIssue]
8585

8686
class ArgSubState(ArgState):
8787
@rx.var
@@ -90,7 +90,7 @@ def cached_arg(self) -> int:
9090

9191
@rx.var
9292
def cached_arg_str(self) -> str:
93-
return self.arg_str
93+
return self.arg_str # pyright: ignore[reportAttributeAccessIssue]
9494

9595
@rx.page(route="/arg/[arg_str]")
9696
def arg() -> rx.Component:
@@ -238,11 +238,11 @@ async def _backend_state():
238238
async def _check():
239239
return (await _backend_state()).substates[
240240
dynamic_state_name
241-
].order == exp_order
241+
].order == exp_order # pyright: ignore[reportAttributeAccessIssue]
242242

243243
await AppHarness._poll_for_async(_check, timeout=10)
244244
assert (
245-
list((await _backend_state()).substates[dynamic_state_name].order)
245+
list((await _backend_state()).substates[dynamic_state_name].order) # pyright: ignore[reportAttributeAccessIssue]
246246
== exp_order
247247
)
248248

tests/integration/test_event_actions.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,10 @@ def poll_for_order(
265265

266266
async def _poll_for_order(exp_order: list[str]):
267267
async def _check():
268-
return (await _backend_state(event_action, token)).order == exp_order
268+
return (await _backend_state(event_action, token)).order == exp_order # pyright: ignore[reportAttributeAccessIssue]
269269

270270
await AppHarness._poll_for_async(_check)
271-
assert (await _backend_state(event_action, token)).order == exp_order
271+
assert (await _backend_state(event_action, token)).order == exp_order # pyright: ignore[reportAttributeAccessIssue]
272272

273273
return _poll_for_order
274274

@@ -358,12 +358,12 @@ async def test_event_actions_throttle_debounce(
358358
# Wait until the debounce event shows up
359359
async def _debounce_received():
360360
state = await _backend_state(event_action, token)
361-
return state.order and state.order[-1] == "on_click_debounce"
361+
return state.order and state.order[-1] == "on_click_debounce" # pyright: ignore[reportAttributeAccessIssue]
362362

363363
await AppHarness._poll_for_async(_debounce_received)
364364

365365
# This test is inherently racy, so ensure the `on_click_throttle` event is fired approximately the expected number of times.
366-
final_event_order = (await _backend_state(event_action, token)).order
366+
final_event_order = (await _backend_state(event_action, token)).order # pyright: ignore[reportAttributeAccessIssue]
367367
n_on_click_throttle_received = final_event_order.count("on_click_throttle")
368368
print(
369369
f"Expected ~{exp_events} on_click_throttle events, received {n_on_click_throttle_received}"

tests/integration/test_event_chain.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,11 @@ async def test_event_chain_click(
462462

463463
async def _has_all_events():
464464
return len(
465-
(await event_chain.get_state(token)).substates[state_name].event_order
465+
(await event_chain.get_state(token)).substates[state_name].event_order # pyright: ignore[reportAttributeAccessIssue]
466466
) == len(exp_event_order)
467467

468468
await AppHarness._poll_for_async(_has_all_events)
469-
event_order = (await event_chain.get_state(token)).substates[state_name].event_order
469+
event_order = (await event_chain.get_state(token)).substates[state_name].event_order # pyright: ignore[reportAttributeAccessIssue]
470470
assert event_order == exp_event_order
471471

472472

@@ -515,13 +515,13 @@ async def test_event_chain_on_load(
515515

516516
async def _has_all_events():
517517
return len(
518-
(await event_chain.get_state(token)).substates[state_name].event_order
518+
(await event_chain.get_state(token)).substates[state_name].event_order # pyright: ignore[reportAttributeAccessIssue]
519519
) == len(exp_event_order)
520520

521521
await AppHarness._poll_for_async(_has_all_events)
522522
backend_state = (await event_chain.get_state(token)).substates[state_name]
523-
assert backend_state.event_order == exp_event_order
524-
assert backend_state.is_hydrated is True
523+
assert backend_state.event_order == exp_event_order # pyright: ignore[reportAttributeAccessIssue]
524+
assert backend_state.is_hydrated is True # pyright: ignore[reportAttributeAccessIssue]
525525

526526

527527
@pytest.mark.parametrize(
@@ -582,11 +582,11 @@ async def test_event_chain_on_mount(
582582

583583
async def _has_all_events():
584584
return len(
585-
(await event_chain.get_state(token)).substates[state_name].event_order
585+
(await event_chain.get_state(token)).substates[state_name].event_order # pyright: ignore[reportAttributeAccessIssue]
586586
) == len(exp_event_order)
587587

588588
await AppHarness._poll_for_async(_has_all_events)
589-
event_order = (await event_chain.get_state(token)).substates[state_name].event_order
589+
event_order = (await event_chain.get_state(token)).substates[state_name].event_order # pyright: ignore[reportAttributeAccessIssue]
590590
assert list(event_order) == exp_event_order
591591

592592

tests/integration/test_form_submit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ async def get_form_data():
232232
return (
233233
(await form_submit.get_state(f"{token}_{full_state_name}"))
234234
.substates[state_name]
235-
.form_data
235+
.form_data # pyright: ignore[reportAttributeAccessIssue]
236236
)
237237

238238
# wait for the form data to arrive at the backend

0 commit comments

Comments
 (0)