Skip to content

Commit 26f688f

Browse files
committed
Merge remote-tracking branch 'origin/main' into release/reflex-0.8.20
2 parents 899bf65 + 4ee713f commit 26f688f

File tree

8 files changed

+107
-49
lines changed

8 files changed

+107
-49
lines changed

.github/codeql-config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1+
paths:
2+
- .github
3+
- reflex
4+
- reflex/.templates
15
paths-ignore:
26
- "**/tests/**"

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969

7070
# Initializes the CodeQL tools for scanning.
7171
- name: Initialize CodeQL
72-
uses: github/codeql-action/init@v3
72+
uses: github/codeql-action/init@v4
7373
with:
7474
languages: ${{ matrix.language }}
7575
config-file: .github/codeql-config.yml
@@ -98,6 +98,6 @@ jobs:
9898
exit 1
9999
100100
- name: Perform CodeQL Analysis
101-
uses: github/codeql-action/analyze@v3
101+
uses: github/codeql-action/analyze@v4
102102
with:
103103
category: "/language:${{matrix.language}}"

reflex/.templates/web/utils/state.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -618,19 +618,13 @@ export const connect = async (
618618
window.addEventListener("unload", disconnectTrigger);
619619
if (socket.current.rehydrate) {
620620
socket.current.rehydrate = false;
621-
const events = initialEvents();
622-
if (events.length > 0) {
623-
// On reconnect, we only hydrate, do not re-run on_load events.
624-
const hydrate_event = initialEvents()[0];
625-
hydrate_event.payload.is_reconnect = true;
626-
queueEvents(
627-
[hydrate_event],
628-
socket,
629-
true,
630-
navigate,
631-
() => params.current,
632-
);
633-
}
621+
queueEvents(
622+
initialEvents(),
623+
socket,
624+
true,
625+
navigate,
626+
() => params.current,
627+
);
634628
}
635629
// Drain any initial events from the queue.
636630
while (event_queue.length > 0 && !event_processing) {

reflex/app.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -529,19 +529,6 @@ def _setup_state(self) -> None:
529529

530530
# Set up the Socket.IO AsyncServer.
531531
if not self.sio:
532-
if (
533-
config.transport == "polling"
534-
and (tier := prerequisites.get_user_tier()) != "enterprise"
535-
):
536-
console.error(
537-
"The 'polling' transport is only available for Enterprise users. "
538-
+ (
539-
"Please upgrade your plan to use this feature."
540-
if tier != "anonymous"
541-
else "Please log in with `reflex login` to use this feature."
542-
)
543-
)
544-
raise SystemExit(1)
545532
self.sio = AsyncServer(
546533
async_mode="asgi",
547534
cors_allowed_origins=(

reflex/istate/proxy.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -553,18 +553,17 @@ def __getattr__(self, __name: str) -> Any:
553553
value = wrapt.FunctionWrapper(value, self._mark_dirty)
554554

555555
if __name in self.__wrap_mutable_attrs__:
556-
# Wrap methods that may return mutable objects tied to the state.
556+
# Wrap special methods that may return mutable objects tied to the state.
557557
value = wrapt.FunctionWrapper(
558558
value,
559559
self._wrap_recursive_decorator, # pyright: ignore[reportArgumentType]
560560
)
561561

562562
if (
563-
isinstance(self.__wrapped__, Base)
564-
and __name not in NEVER_WRAP_BASE_ATTRS
565-
and hasattr(value, "__func__")
566-
):
567-
# Wrap methods called on Base subclasses, which might do _anything_
563+
not isinstance(self.__wrapped__, Base)
564+
or __name not in NEVER_WRAP_BASE_ATTRS
565+
) and hasattr(value, "__func__"):
566+
# Wrap methods which might do _anything_
568567
return wrapt.FunctionWrapper(
569568
functools.partial(value.__func__, self), # pyright: ignore [reportFunctionMemberAccess, reportAttributeAccessIssue]
570569
self._wrap_recursive_decorator, # pyright: ignore[reportArgumentType]

reflex/middleware/hydrate_middleware.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,11 @@ async def preprocess(
3535
if event.name != get_hydrate_event(state):
3636
return None
3737

38-
# In reconnect mode, don't reset client storage or call on_load.
39-
is_reconnect = event.payload.get("is_reconnect", False)
40-
if not is_reconnect:
41-
# Clear client storage, to respect clearing cookies
42-
state._reset_client_storage()
43-
44-
# Mark state as not hydrated (until on_loads are complete)
45-
setattr(state, constants.CompileVars.IS_HYDRATED, False)
38+
# Clear client storage, to respect clearing cookies
39+
state._reset_client_storage()
40+
41+
# Mark state as not hydrated (until on_loads are complete)
42+
setattr(state, constants.CompileVars.IS_HYDRATED, False)
4643

4744
# Get the initial state.
4845
delta = await _resolve_delta(state.dict())

reflex/utils/token_manager.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,16 +229,22 @@ async def enumerate_tokens(self) -> AsyncIterator[str]:
229229
if not cursor:
230230
break
231231

232-
def _handle_socket_record_del(self, token: str) -> None:
232+
async def _handle_socket_record_del(
233+
self, token: str, expired: bool = False
234+
) -> None:
233235
"""Handle deletion of a socket record from Redis.
234236
235237
Args:
236238
token: The client token whose record was deleted.
239+
expired: Whether the deletion was due to expiration.
237240
"""
238241
if (
239242
socket_record := self.token_to_socket.pop(token, None)
240-
) is not None and socket_record.instance_id != self.instance_id:
243+
) is not None and socket_record.instance_id == self.instance_id:
241244
self.sid_to_token.pop(socket_record.sid, None)
245+
if expired:
246+
# Keep the record alive as long as this process is alive and not deleted.
247+
await self.link_token_to_sid(token, socket_record.sid)
242248

243249
async def _subscribe_socket_record_updates(self) -> None:
244250
"""Subscribe to Redis keyspace notifications for socket record updates."""
@@ -262,7 +268,10 @@ async def _subscribe_socket_record_updates(self) -> None:
262268

263269
event = message["data"].decode()
264270
if event in ("del", "expired", "evicted"):
265-
self._handle_socket_record_del(token)
271+
await self._handle_socket_record_del(
272+
token,
273+
expired=(event == "expired"),
274+
)
266275
elif event == "set":
267276
await self._get_token_owner(token, refresh=True)
268277

tests/units/test_state.py

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,6 +2055,22 @@ class ModelDC:
20552055
foo: str = "bar"
20562056
ls: list[dict] = dataclasses.field(default_factory=list)
20572057

2058+
def set_foo(self, val: str):
2059+
"""Set the attribute foo.
2060+
2061+
Args:
2062+
val: The value to set.
2063+
"""
2064+
self.foo = val
2065+
2066+
def double_foo(self) -> str:
2067+
"""Concatenate foo with foo.
2068+
2069+
Returns:
2070+
foo + foo
2071+
"""
2072+
return self.foo + self.foo
2073+
20582074

20592075
@pytest.mark.asyncio
20602076
async def test_state_proxy(
@@ -3806,12 +3822,44 @@ class ModelV1(BaseModelV1):
38063822

38073823
foo: str = "bar"
38083824

3825+
def set_foo(self, val: str):
3826+
"""Set the attribute foo.
3827+
3828+
Args:
3829+
val: The value to set.
3830+
"""
3831+
self.foo = val
3832+
3833+
def double_foo(self) -> str:
3834+
"""Concatenate foo with foo.
3835+
3836+
Returns:
3837+
foo + foo
3838+
"""
3839+
return self.foo + self.foo
3840+
38093841

38103842
class ModelV2(BaseModelV2):
38113843
"""A pydantic BaseModel v2."""
38123844

38133845
foo: str = "bar"
38143846

3847+
def set_foo(self, val: str):
3848+
"""Set the attribute foo.
3849+
3850+
Args:
3851+
val: The value to set.
3852+
"""
3853+
self.foo = val
3854+
3855+
def double_foo(self) -> str:
3856+
"""Concatenate foo with foo.
3857+
3858+
Returns:
3859+
foo + foo
3860+
"""
3861+
return self.foo + self.foo
3862+
38153863

38163864
class PydanticState(rx.State):
38173865
"""A state with pydantic BaseModel vars."""
@@ -3828,26 +3876,46 @@ def test_mutable_models():
38283876
state.v1.foo = "baz"
38293877
assert state.dirty_vars == {"v1"}
38303878
state.dirty_vars.clear()
3879+
state.v1.set_foo("quuc")
3880+
assert state.dirty_vars == {"v1"}
3881+
state.dirty_vars.clear()
3882+
assert state.v1.double_foo() == "quucquuc"
3883+
assert state.dirty_vars == set()
3884+
state.v1.copy(update={"foo": "larp"})
3885+
assert state.dirty_vars == set()
38313886

38323887
assert isinstance(state.v2, MutableProxy)
38333888
state.v2.foo = "baz"
38343889
assert state.dirty_vars == {"v2"}
38353890
state.dirty_vars.clear()
3891+
state.v2.set_foo("quuc")
3892+
assert state.dirty_vars == {"v2"}
3893+
state.dirty_vars.clear()
3894+
assert state.v2.double_foo() == "quucquuc"
3895+
assert state.dirty_vars == set()
3896+
state.v2.model_copy(update={"foo": "larp"})
3897+
assert state.dirty_vars == set()
38363898

38373899
assert isinstance(state.dc, MutableProxy)
38383900
state.dc.foo = "baz"
38393901
assert state.dirty_vars == {"dc"}
38403902
state.dirty_vars.clear()
38413903
assert state.dirty_vars == set()
3904+
state.dc.set_foo("quuc")
3905+
assert state.dirty_vars == {"dc"}
3906+
state.dirty_vars.clear()
3907+
assert state.dirty_vars == set()
3908+
assert state.dc.double_foo() == "quucquuc"
3909+
assert state.dirty_vars == set()
38423910
state.dc.ls.append({"hi": "reflex"})
38433911
assert state.dirty_vars == {"dc"}
38443912
state.dirty_vars.clear()
38453913
assert state.dirty_vars == set()
3846-
assert dataclasses.asdict(state.dc) == {"foo": "baz", "ls": [{"hi": "reflex"}]}
3847-
assert dataclasses.astuple(state.dc) == ("baz", [{"hi": "reflex"}])
3914+
assert dataclasses.asdict(state.dc) == {"foo": "quuc", "ls": [{"hi": "reflex"}]}
3915+
assert dataclasses.astuple(state.dc) == ("quuc", [{"hi": "reflex"}])
38483916
# creating a new instance shouldn't mark the state dirty
3849-
assert dataclasses.replace(state.dc, foo="quuc") == ModelDC(
3850-
foo="quuc", ls=[{"hi": "reflex"}]
3917+
assert dataclasses.replace(state.dc, foo="larp") == ModelDC(
3918+
foo="larp", ls=[{"hi": "reflex"}]
38513919
)
38523920
assert state.dirty_vars == set()
38533921

0 commit comments

Comments
 (0)