reflex 0.8.15a1__py3-none-any.whl → 0.8.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/.templates/web/utils/state.js +68 -8
- reflex/app.py +45 -51
- reflex/app_mixins/lifespan.py +12 -5
- reflex/base.py +1 -0
- reflex/compiler/compiler.py +4 -6
- reflex/compiler/templates.py +25 -31
- reflex/compiler/utils.py +6 -5
- reflex/components/base/body.pyi +1 -195
- reflex/components/base/link.pyi +1 -407
- reflex/components/base/meta.pyi +1 -405
- reflex/components/base/script.pyi +1 -237
- reflex/components/component.py +41 -46
- reflex/components/core/auto_scroll.pyi +1 -195
- reflex/components/core/banner.pyi +1 -391
- reflex/components/core/breakpoints.py +14 -18
- reflex/components/core/html.pyi +1 -197
- reflex/components/core/match.py +2 -2
- reflex/components/core/sticky.py +11 -15
- reflex/components/core/sticky.pyi +0 -788
- reflex/components/core/upload.py +1 -3
- reflex/components/core/window_events.pyi +6 -5
- reflex/components/datadisplay/code.pyi +1 -0
- reflex/components/datadisplay/dataeditor.py +4 -6
- reflex/components/datadisplay/shiki_code_block.py +106 -110
- reflex/components/dynamic.py +4 -6
- reflex/components/el/elements/__init__.py +5 -7
- reflex/components/el/elements/__init__.pyi +5 -7
- reflex/components/el/elements/base.py +1 -1
- reflex/components/el/elements/base.pyi +1 -195
- reflex/components/el/elements/forms.py +7 -9
- reflex/components/el/elements/forms.pyi +12 -3112
- reflex/components/el/elements/inline.pyi +0 -5481
- reflex/components/el/elements/media.pyi +0 -10280
- reflex/components/el/elements/metadata.pyi +1 -835
- reflex/components/el/elements/other.pyi +1 -1365
- reflex/components/el/elements/scripts.pyi +1 -625
- reflex/components/el/elements/sectioning.pyi +1 -2911
- reflex/components/el/elements/tables.pyi +1 -1973
- reflex/components/el/elements/typography.pyi +1 -3125
- reflex/components/lucide/icon.py +4 -4
- reflex/components/lucide/icon.pyi +0 -4
- reflex/components/markdown/markdown.py +15 -19
- reflex/components/markdown/markdown.pyi +1 -0
- reflex/components/moment/moment.pyi +0 -49
- reflex/components/props.py +3 -3
- reflex/components/radix/primitives/accordion.py +4 -6
- reflex/components/radix/primitives/accordion.pyi +0 -14
- reflex/components/radix/primitives/base.pyi +0 -5
- reflex/components/radix/primitives/dialog.py +2 -0
- reflex/components/radix/primitives/dialog.pyi +1 -233
- reflex/components/radix/primitives/drawer.pyi +0 -18
- reflex/components/radix/primitives/form.pyi +30 -632
- reflex/components/radix/primitives/progress.pyi +0 -10
- reflex/components/radix/primitives/slider.pyi +0 -10
- reflex/components/radix/themes/color_mode.pyi +1 -284
- reflex/components/radix/themes/components/alert_dialog.pyi +0 -207
- reflex/components/radix/themes/components/aspect_ratio.pyi +0 -2
- reflex/components/radix/themes/components/avatar.pyi +0 -80
- reflex/components/radix/themes/components/badge.pyi +1 -270
- reflex/components/radix/themes/components/button.pyi +1 -274
- reflex/components/radix/themes/components/callout.pyi +0 -1197
- reflex/components/radix/themes/components/card.pyi +1 -209
- reflex/components/radix/themes/components/checkbox.pyi +0 -261
- reflex/components/radix/themes/components/checkbox_cards.pyi +1 -96
- reflex/components/radix/themes/components/checkbox_group.pyi +1 -80
- reflex/components/radix/themes/components/context_menu.pyi +13 -321
- reflex/components/radix/themes/components/data_list.pyi +1 -107
- reflex/components/radix/themes/components/dialog.pyi +1 -210
- reflex/components/radix/themes/components/dropdown_menu.pyi +0 -209
- reflex/components/radix/themes/components/hover_card.pyi +1 -246
- reflex/components/radix/themes/components/icon_button.pyi +1 -195
- reflex/components/radix/themes/components/inset.pyi +0 -252
- reflex/components/radix/themes/components/popover.pyi +1 -234
- reflex/components/radix/themes/components/progress.pyi +1 -84
- reflex/components/radix/themes/components/radio.pyi +1 -72
- reflex/components/radix/themes/components/radio_cards.pyi +1 -123
- reflex/components/radix/themes/components/scroll_area.pyi +1 -11
- reflex/components/radix/themes/components/select.pyi +1 -376
- reflex/components/radix/themes/components/separator.pyi +0 -77
- reflex/components/radix/themes/components/skeleton.pyi +0 -30
- reflex/components/radix/themes/components/slider.py +3 -5
- reflex/components/radix/themes/components/spinner.pyi +0 -5
- reflex/components/radix/themes/components/switch.pyi +0 -89
- reflex/components/radix/themes/components/table.pyi +0 -1453
- reflex/components/radix/themes/components/text_area.pyi +7 -282
- reflex/components/radix/themes/components/text_field.pyi +6 -392
- reflex/components/radix/themes/components/tooltip.pyi +0 -42
- reflex/components/radix/themes/layout/box.pyi +1 -195
- reflex/components/radix/themes/layout/center.pyi +0 -194
- reflex/components/radix/themes/layout/container.pyi +0 -178
- reflex/components/radix/themes/layout/flex.pyi +0 -194
- reflex/components/radix/themes/layout/grid.pyi +0 -194
- reflex/components/radix/themes/layout/list.pyi +0 -978
- reflex/components/radix/themes/layout/section.pyi +0 -194
- reflex/components/radix/themes/layout/spacer.pyi +0 -194
- reflex/components/radix/themes/layout/stack.pyi +0 -582
- reflex/components/radix/themes/typography/blockquote.pyi +0 -196
- reflex/components/radix/themes/typography/code.pyi +0 -194
- reflex/components/radix/themes/typography/heading.pyi +0 -194
- reflex/components/radix/themes/typography/link.pyi +0 -237
- reflex/components/radix/themes/typography/text.pyi +0 -1360
- reflex/components/react_router/dom.pyi +0 -237
- reflex/components/recharts/cartesian.py +12 -18
- reflex/components/recharts/general.py +12 -18
- reflex/constants/installer.py +5 -5
- reflex/custom_components/custom_components.py +6 -5
- reflex/environment.py +30 -7
- reflex/event.py +14 -12
- reflex/experimental/client_state.py +11 -12
- reflex/istate/data.py +8 -10
- reflex/istate/manager/__init__.py +3 -0
- reflex/istate/manager/disk.py +151 -5
- reflex/model.py +1 -1
- reflex/plugins/_screenshot.py +2 -2
- reflex/plugins/shared_tailwind.py +9 -14
- reflex/reflex.py +7 -9
- reflex/state.py +30 -37
- reflex/style.py +6 -6
- reflex/testing.py +54 -30
- reflex/utils/codespaces.py +31 -2
- reflex/utils/compat.py +1 -0
- reflex/utils/decorator.py +3 -3
- reflex/utils/format.py +18 -22
- reflex/utils/prerequisites.py +1 -1
- reflex/utils/pyi_generator.py +51 -57
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +1 -1
- reflex/utils/templates.py +4 -4
- reflex/utils/types.py +11 -4
- reflex/vars/base.py +26 -29
- reflex/vars/color.py +6 -8
- reflex/vars/dep_tracking.py +5 -3
- reflex/vars/function.py +3 -3
- reflex/vars/object.py +9 -13
- reflex/vars/sequence.py +18 -24
- {reflex-0.8.15a1.dist-info → reflex-0.8.16.dist-info}/METADATA +1 -1
- {reflex-0.8.15a1.dist-info → reflex-0.8.16.dist-info}/RECORD +140 -140
- {reflex-0.8.15a1.dist-info → reflex-0.8.16.dist-info}/WHEEL +0 -0
- {reflex-0.8.15a1.dist-info → reflex-0.8.16.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.15a1.dist-info → reflex-0.8.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -529,6 +529,14 @@ export const connect = async (
|
|
|
529
529
|
navigate,
|
|
530
530
|
params,
|
|
531
531
|
) => {
|
|
532
|
+
// Socket already allocated, just reconnect it if needed.
|
|
533
|
+
if (socket.current) {
|
|
534
|
+
if (!socket.current.connected) {
|
|
535
|
+
socket.current.reconnect();
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
532
540
|
// Get backend URL object from the endpoint.
|
|
533
541
|
const endpoint = getBackendURL(EVENTURL);
|
|
534
542
|
const on_hydrated_queue = [];
|
|
@@ -540,7 +548,9 @@ export const connect = async (
|
|
|
540
548
|
protocols: [reflexEnvironment.version],
|
|
541
549
|
autoUnref: false,
|
|
542
550
|
query: { token: getToken() },
|
|
551
|
+
reconnection: false, // Reconnection will be handled manually.
|
|
543
552
|
});
|
|
553
|
+
socket.current.wait_connect = !socket.current.connected;
|
|
544
554
|
// Ensure undefined fields in events are sent as null instead of removed
|
|
545
555
|
socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v);
|
|
546
556
|
socket.current.io.decoder.tryParse = (str) => {
|
|
@@ -550,6 +560,18 @@ export const connect = async (
|
|
|
550
560
|
return false;
|
|
551
561
|
}
|
|
552
562
|
};
|
|
563
|
+
// Set up a reconnect helper function
|
|
564
|
+
socket.current.reconnect = () => {
|
|
565
|
+
if (
|
|
566
|
+
socket.current &&
|
|
567
|
+
!socket.current.connected &&
|
|
568
|
+
!socket.current.wait_connect
|
|
569
|
+
) {
|
|
570
|
+
socket.current.wait_connect = true;
|
|
571
|
+
socket.current.io.opts.query = { token: getToken() }; // Update token for reconnect.
|
|
572
|
+
socket.current.connect();
|
|
573
|
+
}
|
|
574
|
+
};
|
|
553
575
|
|
|
554
576
|
function checkVisibility() {
|
|
555
577
|
if (document.visibilityState === "visible") {
|
|
@@ -565,7 +587,7 @@ export const connect = async (
|
|
|
565
587
|
);
|
|
566
588
|
} else if (!socket.current.connected) {
|
|
567
589
|
console.log("Socket is disconnected, attempting to reconnect ");
|
|
568
|
-
socket.current.
|
|
590
|
+
socket.current.reconnect();
|
|
569
591
|
} else {
|
|
570
592
|
console.log("Socket is reconnected ");
|
|
571
593
|
}
|
|
@@ -588,6 +610,7 @@ export const connect = async (
|
|
|
588
610
|
|
|
589
611
|
// Once the socket is open, hydrate the page.
|
|
590
612
|
socket.current.on("connect", async () => {
|
|
613
|
+
socket.current.wait_connect = false;
|
|
591
614
|
setConnectErrors([]);
|
|
592
615
|
window.addEventListener("pagehide", pagehideHandler);
|
|
593
616
|
window.addEventListener("beforeunload", disconnectTrigger);
|
|
@@ -599,16 +622,33 @@ export const connect = async (
|
|
|
599
622
|
});
|
|
600
623
|
|
|
601
624
|
socket.current.on("connect_error", (error) => {
|
|
602
|
-
|
|
625
|
+
socket.current.wait_connect = false;
|
|
626
|
+
let n_connect_errors = 0;
|
|
627
|
+
setConnectErrors((connectErrors) => {
|
|
628
|
+
const new_errors = [...connectErrors.slice(-9), error];
|
|
629
|
+
n_connect_errors = new_errors.length;
|
|
630
|
+
return new_errors;
|
|
631
|
+
});
|
|
632
|
+
window.setTimeout(() => {
|
|
633
|
+
if (socket.current && !socket.current.connected) {
|
|
634
|
+
socket.current.reconnect();
|
|
635
|
+
}
|
|
636
|
+
}, 200 * n_connect_errors); // Incremental backoff
|
|
603
637
|
});
|
|
604
638
|
|
|
605
639
|
// When the socket disconnects reset the event_processing flag
|
|
606
|
-
socket.current.on("disconnect", () => {
|
|
607
|
-
socket.current =
|
|
640
|
+
socket.current.on("disconnect", (reason, details) => {
|
|
641
|
+
socket.current.wait_connect = false;
|
|
642
|
+
const try_reconnect =
|
|
643
|
+
reason !== "io server disconnect" && reason !== "io client disconnect";
|
|
608
644
|
event_processing = false;
|
|
609
645
|
window.removeEventListener("unload", disconnectTrigger);
|
|
610
646
|
window.removeEventListener("beforeunload", disconnectTrigger);
|
|
611
647
|
window.removeEventListener("pagehide", pagehideHandler);
|
|
648
|
+
if (try_reconnect) {
|
|
649
|
+
// Attempt to reconnect transient non-intentional disconnects.
|
|
650
|
+
socket.current.reconnect();
|
|
651
|
+
}
|
|
612
652
|
});
|
|
613
653
|
|
|
614
654
|
// On each received message, queue the updates and events.
|
|
@@ -785,6 +825,7 @@ export const useEventLoop = (
|
|
|
785
825
|
const [searchParams] = useSearchParams();
|
|
786
826
|
const [connectErrors, setConnectErrors] = useState([]);
|
|
787
827
|
const params = useRef(paramsR);
|
|
828
|
+
const mounted = useRef(false);
|
|
788
829
|
|
|
789
830
|
useEffect(() => {
|
|
790
831
|
const { "*": splat, ...remainingParams } = paramsR;
|
|
@@ -796,11 +837,16 @@ export const useEventLoop = (
|
|
|
796
837
|
}, [paramsR]);
|
|
797
838
|
|
|
798
839
|
const ensureSocketConnected = useCallback(async () => {
|
|
840
|
+
if (!mounted.current) {
|
|
841
|
+
// During hot reload, some components may still have a reference to
|
|
842
|
+
// addEvents, so avoid reconnecting the socket of an unmounted event loop.
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
799
845
|
// only use websockets if state is present and backend is not disabled (reflex cloud).
|
|
800
846
|
if (
|
|
801
847
|
Object.keys(initialState).length > 1 &&
|
|
802
848
|
!isBackendDisabled() &&
|
|
803
|
-
!socket.current
|
|
849
|
+
!socket.current?.connected
|
|
804
850
|
) {
|
|
805
851
|
// Initialize the websocket connection.
|
|
806
852
|
await connect(
|
|
@@ -813,13 +859,23 @@ export const useEventLoop = (
|
|
|
813
859
|
() => params.current,
|
|
814
860
|
);
|
|
815
861
|
}
|
|
816
|
-
}, [
|
|
862
|
+
}, [
|
|
863
|
+
socket,
|
|
864
|
+
dispatch,
|
|
865
|
+
setConnectErrors,
|
|
866
|
+
client_storage,
|
|
867
|
+
navigate,
|
|
868
|
+
params,
|
|
869
|
+
mounted,
|
|
870
|
+
]);
|
|
817
871
|
|
|
818
872
|
// Function to add new events to the event queue.
|
|
819
873
|
const addEvents = useCallback((events, args, event_actions) => {
|
|
820
874
|
const _events = events.filter((e) => e !== undefined && e !== null);
|
|
821
|
-
|
|
822
|
-
|
|
875
|
+
if (!event_actions?.temporal) {
|
|
876
|
+
// Reconnect socket if needed for non-temporal events.
|
|
877
|
+
ensureSocketConnected();
|
|
878
|
+
}
|
|
823
879
|
|
|
824
880
|
if (!(args instanceof Array)) {
|
|
825
881
|
args = [args];
|
|
@@ -914,12 +970,16 @@ export const useEventLoop = (
|
|
|
914
970
|
// Handle socket connect/disconnect.
|
|
915
971
|
useEffect(() => {
|
|
916
972
|
// Initialize the websocket connection.
|
|
973
|
+
mounted.current = true;
|
|
917
974
|
ensureSocketConnected();
|
|
918
975
|
|
|
919
976
|
// Cleanup function.
|
|
920
977
|
return () => {
|
|
978
|
+
mounted.current = false;
|
|
921
979
|
if (socket.current) {
|
|
922
980
|
socket.current.disconnect();
|
|
981
|
+
socket.current.off();
|
|
982
|
+
socket.current = null;
|
|
923
983
|
}
|
|
924
984
|
};
|
|
925
985
|
}, []);
|
reflex/app.py
CHANGED
|
@@ -11,6 +11,7 @@ import functools
|
|
|
11
11
|
import inspect
|
|
12
12
|
import io
|
|
13
13
|
import json
|
|
14
|
+
import operator
|
|
14
15
|
import sys
|
|
15
16
|
import time
|
|
16
17
|
import traceback
|
|
@@ -118,6 +119,7 @@ from reflex.utils.exec import (
|
|
|
118
119
|
should_prerender_routes,
|
|
119
120
|
)
|
|
120
121
|
from reflex.utils.imports import ImportVar
|
|
122
|
+
from reflex.utils.misc import run_in_thread
|
|
121
123
|
from reflex.utils.token_manager import TokenManager
|
|
122
124
|
from reflex.utils.types import ASGIApp, Message, Receive, Scope, Send
|
|
123
125
|
|
|
@@ -849,7 +851,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
849
851
|
|
|
850
852
|
# Setup dynamic args for the route.
|
|
851
853
|
# this state assignment is only required for tests using the deprecated state kwarg for App
|
|
852
|
-
state = self._state
|
|
854
|
+
state = self._state or State
|
|
853
855
|
state.setup_dynamic_args(get_route_args(route))
|
|
854
856
|
|
|
855
857
|
self._load_events[route] = (
|
|
@@ -969,14 +971,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
969
971
|
|
|
970
972
|
if admin_dash and admin_dash.models:
|
|
971
973
|
# Build the admin dashboard
|
|
972
|
-
admin = (
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
engine=Model.get_db_engine(),
|
|
977
|
-
title="Reflex Admin Dashboard",
|
|
978
|
-
logo_url="https://reflex.dev/Reflex.svg",
|
|
979
|
-
)
|
|
974
|
+
admin = admin_dash.admin or Admin(
|
|
975
|
+
engine=Model.get_db_engine(),
|
|
976
|
+
title="Reflex Admin Dashboard",
|
|
977
|
+
logo_url="https://reflex.dev/Reflex.svg",
|
|
980
978
|
)
|
|
981
979
|
|
|
982
980
|
for model in admin_dash.models:
|
|
@@ -1009,21 +1007,21 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1009
1007
|
page_imports = {i for i in page_imports if i not in pinned}
|
|
1010
1008
|
|
|
1011
1009
|
frontend_packages = get_config().frontend_packages
|
|
1012
|
-
|
|
1010
|
+
filtered_frontend_packages = []
|
|
1013
1011
|
for package in frontend_packages:
|
|
1014
1012
|
if package in page_imports:
|
|
1015
1013
|
console.warn(
|
|
1016
1014
|
f"React packages and their dependencies are inferred from Component.library and Component.lib_dependencies, remove `{package}` from `frontend_packages`"
|
|
1017
1015
|
)
|
|
1018
1016
|
continue
|
|
1019
|
-
|
|
1020
|
-
page_imports.update(
|
|
1017
|
+
filtered_frontend_packages.append(package)
|
|
1018
|
+
page_imports.update(filtered_frontend_packages)
|
|
1021
1019
|
js_runtimes.install_frontend_packages(page_imports, get_config())
|
|
1022
1020
|
|
|
1023
1021
|
def _app_root(self, app_wrappers: dict[tuple[int, str], Component]) -> Component:
|
|
1024
1022
|
for component in tuple(app_wrappers.values()):
|
|
1025
1023
|
app_wrappers.update(component._get_all_app_wrap_components())
|
|
1026
|
-
order = sorted(app_wrappers, key=
|
|
1024
|
+
order = sorted(app_wrappers, key=operator.itemgetter(0), reverse=True)
|
|
1027
1025
|
root = copy.deepcopy(app_wrappers[order[0]])
|
|
1028
1026
|
|
|
1029
1027
|
def reducer(parent: Component, key: tuple[int, str]) -> Component:
|
|
@@ -1095,7 +1093,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1095
1093
|
sticky_badge._add_style_recursive({})
|
|
1096
1094
|
return sticky_badge
|
|
1097
1095
|
|
|
1098
|
-
self.app_wraps[
|
|
1096
|
+
self.app_wraps[0, "StickyBadge"] = lambda _: memoized_badge()
|
|
1099
1097
|
|
|
1100
1098
|
def _apply_decorated_pages(self):
|
|
1101
1099
|
"""Add @rx.page decorated pages to the app."""
|
|
@@ -1190,13 +1188,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1190
1188
|
|
|
1191
1189
|
if self.theme is not None:
|
|
1192
1190
|
# If a theme component was provided, wrap the app with it
|
|
1193
|
-
app_wrappers[
|
|
1191
|
+
app_wrappers[20, "Theme"] = self.theme
|
|
1194
1192
|
|
|
1195
1193
|
# Get the env mode.
|
|
1196
1194
|
config = get_config()
|
|
1197
1195
|
|
|
1198
1196
|
if config.react_strict_mode:
|
|
1199
|
-
app_wrappers[
|
|
1197
|
+
app_wrappers[200, "StrictMode"] = StrictMode.create()
|
|
1200
1198
|
|
|
1201
1199
|
if not should_compile and not dry_run:
|
|
1202
1200
|
with console.timing("Evaluate Pages (Backend)"):
|
|
@@ -1251,7 +1249,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1251
1249
|
+ "\n".join(
|
|
1252
1250
|
f"{route}: {time * 1000:.1f}ms"
|
|
1253
1251
|
for route, time in sorted(
|
|
1254
|
-
performance_metrics, key=
|
|
1252
|
+
performance_metrics, key=operator.itemgetter(1), reverse=True
|
|
1255
1253
|
)[:10]
|
|
1256
1254
|
)
|
|
1257
1255
|
)
|
|
@@ -1295,7 +1293,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1295
1293
|
|
|
1296
1294
|
toast_provider = Fragment.create(memoized_toast_provider())
|
|
1297
1295
|
|
|
1298
|
-
app_wrappers[
|
|
1296
|
+
app_wrappers[44, "ToasterProvider"] = toast_provider
|
|
1299
1297
|
|
|
1300
1298
|
# Add the app wraps to the app.
|
|
1301
1299
|
for key, app_wrap in chain(
|
|
@@ -1427,12 +1425,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1427
1425
|
plugin.pre_compile(
|
|
1428
1426
|
add_save_task=_submit_work_without_advancing,
|
|
1429
1427
|
add_modify_task=(
|
|
1430
|
-
lambda *args, plugin=plugin: modify_files_tasks.append(
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
)
|
|
1435
|
-
)
|
|
1428
|
+
lambda *args, plugin=plugin: modify_files_tasks.append((
|
|
1429
|
+
plugin.__class__.__module__ + plugin.__class__.__name__,
|
|
1430
|
+
*args,
|
|
1431
|
+
))
|
|
1436
1432
|
),
|
|
1437
1433
|
unevaluated_pages=list(self._unevaluated_pages.values()),
|
|
1438
1434
|
)
|
|
@@ -1552,7 +1548,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1552
1548
|
if not self._api:
|
|
1553
1549
|
return
|
|
1554
1550
|
|
|
1555
|
-
|
|
1551
|
+
def all_routes(_request: Request) -> Response:
|
|
1556
1552
|
return JSONResponse(list(self._unevaluated_pages.keys()))
|
|
1557
1553
|
|
|
1558
1554
|
self._api.add_route(
|
|
@@ -1663,21 +1659,21 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1663
1659
|
strict=True,
|
|
1664
1660
|
):
|
|
1665
1661
|
if hasattr(handler_fn, "__name__"):
|
|
1666
|
-
|
|
1662
|
+
fn_name_ = handler_fn.__name__
|
|
1667
1663
|
else:
|
|
1668
|
-
|
|
1664
|
+
fn_name_ = type(handler_fn).__name__
|
|
1669
1665
|
|
|
1670
1666
|
if isinstance(handler_fn, functools.partial):
|
|
1671
|
-
msg = f"Provided custom {handler_domain} exception handler `{
|
|
1667
|
+
msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` is a partial function. Please provide a named function instead."
|
|
1672
1668
|
raise ValueError(msg)
|
|
1673
1669
|
|
|
1674
1670
|
if not callable(handler_fn):
|
|
1675
|
-
msg = f"Provided custom {handler_domain} exception handler `{
|
|
1671
|
+
msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` is not a function."
|
|
1676
1672
|
raise ValueError(msg)
|
|
1677
1673
|
|
|
1678
1674
|
# Allow named functions only as lambda functions cannot be introspected
|
|
1679
|
-
if
|
|
1680
|
-
msg = f"Provided custom {handler_domain} exception handler `{
|
|
1675
|
+
if fn_name_ == "<lambda>":
|
|
1676
|
+
msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` is a lambda function. Please use a named function instead."
|
|
1681
1677
|
raise ValueError(msg)
|
|
1682
1678
|
|
|
1683
1679
|
# Check if the function has the necessary annotations and types in the right order
|
|
@@ -1690,18 +1686,18 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1690
1686
|
|
|
1691
1687
|
for required_arg_index, required_arg in enumerate(handler_spec):
|
|
1692
1688
|
if required_arg not in arg_annotations:
|
|
1693
|
-
msg = f"Provided custom {handler_domain} exception handler `{
|
|
1689
|
+
msg = f"Provided custom {handler_domain} exception handler `{fn_name_}` does not take the required argument `{required_arg}`"
|
|
1694
1690
|
raise ValueError(msg)
|
|
1695
1691
|
if list(arg_annotations.keys())[required_arg_index] != required_arg:
|
|
1696
1692
|
msg = (
|
|
1697
|
-
f"Provided custom {handler_domain} exception handler `{
|
|
1693
|
+
f"Provided custom {handler_domain} exception handler `{fn_name_}` has the wrong argument order."
|
|
1698
1694
|
f"Expected `{required_arg}` as the {required_arg_index + 1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
|
|
1699
1695
|
)
|
|
1700
1696
|
raise ValueError(msg)
|
|
1701
1697
|
|
|
1702
1698
|
if not issubclass(arg_annotations[required_arg], Exception):
|
|
1703
1699
|
msg = (
|
|
1704
|
-
f"Provided custom {handler_domain} exception handler `{
|
|
1700
|
+
f"Provided custom {handler_domain} exception handler `{fn_name_}` has the wrong type for {required_arg} argument."
|
|
1705
1701
|
f"Expected to be `Exception` but got `{arg_annotations[required_arg]}`"
|
|
1706
1702
|
)
|
|
1707
1703
|
raise ValueError(msg)
|
|
@@ -1725,7 +1721,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1725
1721
|
|
|
1726
1722
|
if not valid:
|
|
1727
1723
|
msg = (
|
|
1728
|
-
f"Provided custom {handler_domain} exception handler `{
|
|
1724
|
+
f"Provided custom {handler_domain} exception handler `{fn_name_}` has the wrong return type."
|
|
1729
1725
|
f"Expected `EventSpec | list[EventSpec] | None` but got `{return_type}`"
|
|
1730
1726
|
)
|
|
1731
1727
|
raise ValueError(msg)
|
|
@@ -1754,15 +1750,13 @@ async def process(
|
|
|
1754
1750
|
try:
|
|
1755
1751
|
# Add request data to the state.
|
|
1756
1752
|
router_data = event.router_data
|
|
1757
|
-
router_data.update(
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
}
|
|
1765
|
-
)
|
|
1753
|
+
router_data.update({
|
|
1754
|
+
constants.RouteVar.QUERY: format.format_query_params(event.router_data),
|
|
1755
|
+
constants.RouteVar.CLIENT_TOKEN: event.token,
|
|
1756
|
+
constants.RouteVar.SESSION_ID: sid,
|
|
1757
|
+
constants.RouteVar.HEADERS: headers,
|
|
1758
|
+
constants.RouteVar.CLIENT_IP: client_ip,
|
|
1759
|
+
})
|
|
1766
1760
|
# Get the state for the session exclusively.
|
|
1767
1761
|
async with app.state_manager.modify_state(event.substate_token) as state:
|
|
1768
1762
|
# When this is a brand new instance of the state, signal the
|
|
@@ -1781,16 +1775,16 @@ async def process(
|
|
|
1781
1775
|
name=f"reflex_emit_reload|{event.name}|{time.time()}|{event.token}",
|
|
1782
1776
|
)
|
|
1783
1777
|
return
|
|
1778
|
+
router_data[constants.RouteVar.PATH] = "/" + (
|
|
1779
|
+
app.router(path) or "404"
|
|
1780
|
+
if (path := router_data.get(constants.RouteVar.PATH))
|
|
1781
|
+
else "404"
|
|
1782
|
+
).removeprefix("/")
|
|
1784
1783
|
# re-assign only when the value is different
|
|
1785
1784
|
if state.router_data != router_data:
|
|
1786
1785
|
# assignment will recurse into substates and force recalculation of
|
|
1787
1786
|
# dependent ComputedVar (dynamic route variables)
|
|
1788
1787
|
state.router_data = router_data
|
|
1789
|
-
router_data[constants.RouteVar.PATH] = "/" + (
|
|
1790
|
-
app.router(path) or "404"
|
|
1791
|
-
if (path := router_data.get(constants.RouteVar.PATH))
|
|
1792
|
-
else "404"
|
|
1793
|
-
).removeprefix("/")
|
|
1794
1788
|
state.router = RouterData.from_router_data(router_data)
|
|
1795
1789
|
|
|
1796
1790
|
# Preprocess the event.
|
|
@@ -1820,7 +1814,7 @@ async def process(
|
|
|
1820
1814
|
raise
|
|
1821
1815
|
|
|
1822
1816
|
|
|
1823
|
-
|
|
1817
|
+
def ping(_request: Request) -> Response:
|
|
1824
1818
|
"""Test API endpoint.
|
|
1825
1819
|
|
|
1826
1820
|
Args:
|
|
@@ -1852,7 +1846,7 @@ async def health(_request: Request) -> JSONResponse:
|
|
|
1852
1846
|
if prerequisites.check_db_used():
|
|
1853
1847
|
from reflex.model import get_db_status
|
|
1854
1848
|
|
|
1855
|
-
tasks.append(get_db_status
|
|
1849
|
+
tasks.append(run_in_thread(get_db_status))
|
|
1856
1850
|
if prerequisites.check_redis_used():
|
|
1857
1851
|
tasks.append(prerequisites.get_redis_status())
|
|
1858
1852
|
|
reflex/app_mixins/lifespan.py
CHANGED
|
@@ -41,13 +41,13 @@ class LifespanMixin(AppMixin):
|
|
|
41
41
|
signature = inspect.signature(task)
|
|
42
42
|
if "app" in signature.parameters:
|
|
43
43
|
task = functools.partial(task, app=app)
|
|
44
|
-
|
|
45
|
-
if isinstance(
|
|
46
|
-
await stack.enter_async_context(
|
|
44
|
+
t_ = task()
|
|
45
|
+
if isinstance(t_, contextlib._AsyncGeneratorContextManager):
|
|
46
|
+
await stack.enter_async_context(t_)
|
|
47
47
|
console.debug(run_msg.format(type="asynccontextmanager"))
|
|
48
|
-
elif isinstance(
|
|
48
|
+
elif isinstance(t_, Coroutine):
|
|
49
49
|
task_ = asyncio.create_task(
|
|
50
|
-
|
|
50
|
+
t_,
|
|
51
51
|
name=f"reflex_lifespan_task|{task_name}|{time.time()}",
|
|
52
52
|
)
|
|
53
53
|
task_.add_done_callback(lambda t: t.result())
|
|
@@ -71,6 +71,13 @@ class LifespanMixin(AppMixin):
|
|
|
71
71
|
await event_namespace._token_manager.disconnect_all()
|
|
72
72
|
except Exception as e:
|
|
73
73
|
console.error(f"Error during lifespan cleanup: {e}")
|
|
74
|
+
# Flush any pending writes from the state manager.
|
|
75
|
+
try:
|
|
76
|
+
state_manager = self.state_manager # pyright: ignore[reportAttributeAccessIssue]
|
|
77
|
+
except AttributeError:
|
|
78
|
+
pass
|
|
79
|
+
else:
|
|
80
|
+
await state_manager.close()
|
|
74
81
|
|
|
75
82
|
def register_lifespan_task(self, task: Callable | asyncio.Task, **task_kwargs):
|
|
76
83
|
"""Register a task to run during the lifespan of the app.
|
reflex/base.py
CHANGED
reflex/compiler/compiler.py
CHANGED
|
@@ -758,12 +758,10 @@ def into_component(component: Component | ComponentCallable) -> Component:
|
|
|
758
758
|
raise TypeError(
|
|
759
759
|
"Cannot pass a Var to a built-in function. Consider using .length() for accessing the length of an iterable Var."
|
|
760
760
|
).with_traceback(e.__traceback__) from None
|
|
761
|
-
if message.endswith(
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
)
|
|
766
|
-
):
|
|
761
|
+
if message.endswith((
|
|
762
|
+
"indices must be integers or slices, not NumberCastedVar",
|
|
763
|
+
"indices must be integers or slices, not BooleanCastedVar",
|
|
764
|
+
)):
|
|
767
765
|
raise TypeError(
|
|
768
766
|
"Cannot index into a primitive sequence with a Var. Consider calling rx.Var.create() on the sequence."
|
|
769
767
|
).with_traceback(e.__traceback__) from None
|
reflex/compiler/templates.py
CHANGED
|
@@ -83,9 +83,9 @@ class _RenderUtils:
|
|
|
83
83
|
|
|
84
84
|
@staticmethod
|
|
85
85
|
def render_iterable_tag(component: Any) -> str:
|
|
86
|
-
children_rendered = "".join(
|
|
87
|
-
|
|
88
|
-
)
|
|
86
|
+
children_rendered = "".join([
|
|
87
|
+
_RenderUtils.render(child) for child in component.get("children", [])
|
|
88
|
+
])
|
|
89
89
|
return f"Array.prototype.map.call({component['iterable_state']} ?? [],(({component['arg_name']},{component['arg_index']})=>({children_rendered})))"
|
|
90
90
|
|
|
91
91
|
@staticmethod
|
|
@@ -189,16 +189,14 @@ def app_root_template(
|
|
|
189
189
|
|
|
190
190
|
custom_code_str = "\n".join(custom_codes)
|
|
191
191
|
|
|
192
|
-
import_window_libraries = "\n".join(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
]
|
|
197
|
-
)
|
|
192
|
+
import_window_libraries = "\n".join([
|
|
193
|
+
f'import * as {lib_alias} from "{lib_path}";'
|
|
194
|
+
for lib_alias, lib_path in window_libraries
|
|
195
|
+
])
|
|
198
196
|
|
|
199
|
-
window_imports_str = "\n".join(
|
|
200
|
-
|
|
201
|
-
)
|
|
197
|
+
window_imports_str = "\n".join([
|
|
198
|
+
f' "{lib_path}": {lib_alias},' for lib_alias, lib_path in window_libraries
|
|
199
|
+
])
|
|
202
200
|
|
|
203
201
|
return f"""
|
|
204
202
|
{imports_str}
|
|
@@ -277,12 +275,10 @@ def context_template(
|
|
|
277
275
|
Rendered context file content as string.
|
|
278
276
|
"""
|
|
279
277
|
initial_state = initial_state or {}
|
|
280
|
-
state_contexts_str = "".join(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
]
|
|
285
|
-
)
|
|
278
|
+
state_contexts_str = "".join([
|
|
279
|
+
f"{format_state_name(state_name)}: createContext(null),"
|
|
280
|
+
for state_name in initial_state
|
|
281
|
+
])
|
|
286
282
|
|
|
287
283
|
state_str = (
|
|
288
284
|
rf"""
|
|
@@ -486,16 +482,14 @@ def package_json_template(
|
|
|
486
482
|
Returns:
|
|
487
483
|
Rendered package.json content as string.
|
|
488
484
|
"""
|
|
489
|
-
return json.dumps(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
)
|
|
485
|
+
return json.dumps({
|
|
486
|
+
"name": "reflex",
|
|
487
|
+
"type": "module",
|
|
488
|
+
"scripts": scripts,
|
|
489
|
+
"dependencies": dependencies,
|
|
490
|
+
"devDependencies": dev_dependencies,
|
|
491
|
+
"overrides": overrides,
|
|
492
|
+
})
|
|
499
493
|
|
|
500
494
|
|
|
501
495
|
def vite_config_template(base: str, hmr: bool, force_full_reload: bool):
|
|
@@ -693,9 +687,9 @@ def styles_template(stylesheets: list[str]) -> str:
|
|
|
693
687
|
Returns:
|
|
694
688
|
Rendered styles.css content as string.
|
|
695
689
|
"""
|
|
696
|
-
return "@layer __reflex_base;\n" + "\n".join(
|
|
697
|
-
|
|
698
|
-
)
|
|
690
|
+
return "@layer __reflex_base;\n" + "\n".join([
|
|
691
|
+
f"@import url('{sheet_name}');" for sheet_name in stylesheets
|
|
692
|
+
])
|
|
699
693
|
|
|
700
694
|
|
|
701
695
|
def _render_hooks(hooks: dict[str, VarData | None], memo: list | None = None) -> str:
|
reflex/compiler/utils.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import concurrent.futures
|
|
7
|
+
import operator
|
|
7
8
|
import traceback
|
|
8
9
|
from collections.abc import Mapping, Sequence
|
|
9
10
|
from datetime import datetime
|
|
@@ -161,7 +162,7 @@ def get_import_dict(
|
|
|
161
162
|
return _ImportDict(
|
|
162
163
|
lib=lib,
|
|
163
164
|
default=default,
|
|
164
|
-
rest=rest
|
|
165
|
+
rest=rest or [],
|
|
165
166
|
)
|
|
166
167
|
|
|
167
168
|
|
|
@@ -190,7 +191,7 @@ def _sorted_keys(d: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
190
191
|
Returns:
|
|
191
192
|
A new dictionary with sorted keys.
|
|
192
193
|
"""
|
|
193
|
-
return dict(sorted(d.items(), key=
|
|
194
|
+
return dict(sorted(d.items(), key=operator.itemgetter(0)))
|
|
194
195
|
|
|
195
196
|
|
|
196
197
|
def compile_state(state: type[BaseState]) -> dict:
|
|
@@ -442,9 +443,9 @@ def create_theme(style: ComponentStyle) -> dict:
|
|
|
442
443
|
|
|
443
444
|
root_style = {
|
|
444
445
|
# Root styles.
|
|
445
|
-
":root": Style(
|
|
446
|
-
|
|
447
|
-
),
|
|
446
|
+
":root": Style({
|
|
447
|
+
f"*{k}": v for k, v in style_rules.items() if k.startswith(":")
|
|
448
|
+
}),
|
|
448
449
|
# Body styles.
|
|
449
450
|
"body": Style(
|
|
450
451
|
{k: v for k, v in style_rules.items() if not k.startswith(":")},
|