reflex 0.6.0a4__py3-none-any.whl → 0.6.1__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/jinja/web/pages/_app.js.jinja2 +14 -0
- reflex/.templates/web/utils/state.js +67 -40
- reflex/app.py +9 -5
- reflex/app_mixins/lifespan.py +24 -6
- reflex/base.py +7 -13
- reflex/compiler/utils.py +17 -8
- reflex/components/base/bare.py +3 -1
- reflex/components/base/meta.py +5 -3
- reflex/components/component.py +19 -19
- reflex/components/core/cond.py +4 -4
- reflex/components/datadisplay/__init__.py +0 -1
- reflex/components/datadisplay/__init__.pyi +0 -1
- reflex/components/datadisplay/code.py +93 -106
- reflex/components/datadisplay/code.pyi +710 -53
- reflex/components/datadisplay/logo.py +22 -20
- reflex/components/dynamic.py +157 -0
- reflex/components/el/elements/forms.py +4 -1
- reflex/components/gridjs/datatable.py +2 -1
- reflex/components/markdown/markdown.py +10 -6
- reflex/components/markdown/markdown.pyi +3 -0
- reflex/components/radix/themes/components/progress.py +22 -0
- reflex/components/radix/themes/components/progress.pyi +2 -0
- reflex/components/radix/themes/components/segmented_control.py +3 -0
- reflex/components/radix/themes/components/segmented_control.pyi +2 -0
- reflex/components/radix/themes/layout/stack.py +1 -1
- reflex/components/recharts/cartesian.py +1 -1
- reflex/components/sonner/toast.py +3 -3
- reflex/components/tags/iter_tag.py +5 -1
- reflex/config.py +2 -2
- reflex/constants/base.py +4 -1
- reflex/constants/installer.py +8 -1
- reflex/event.py +61 -20
- reflex/experimental/assets.py +3 -1
- reflex/experimental/client_state.py +5 -1
- reflex/experimental/misc.py +5 -3
- reflex/middleware/hydrate_middleware.py +1 -2
- reflex/page.py +10 -3
- reflex/reflex.py +20 -3
- reflex/state.py +105 -43
- reflex/style.py +12 -2
- reflex/testing.py +8 -4
- reflex/utils/console.py +1 -1
- reflex/utils/exceptions.py +4 -0
- reflex/utils/exec.py +170 -18
- reflex/utils/format.py +6 -44
- reflex/utils/path_ops.py +36 -1
- reflex/utils/prerequisites.py +42 -14
- reflex/utils/serializers.py +7 -46
- reflex/utils/types.py +17 -2
- reflex/vars/base.py +303 -43
- reflex/vars/number.py +3 -0
- reflex/vars/sequence.py +43 -0
- {reflex-0.6.0a4.dist-info → reflex-0.6.1.dist-info}/METADATA +2 -3
- {reflex-0.6.0a4.dist-info → reflex-0.6.1.dist-info}/RECORD +57 -56
- {reflex-0.6.0a4.dist-info → reflex-0.6.1.dist-info}/LICENSE +0 -0
- {reflex-0.6.0a4.dist-info → reflex-0.6.1.dist-info}/WHEEL +0 -0
- {reflex-0.6.0a4.dist-info → reflex-0.6.1.dist-info}/entry_points.txt +0 -0
|
@@ -7,6 +7,10 @@ import '/styles/styles.css'
|
|
|
7
7
|
{% block declaration %}
|
|
8
8
|
import { EventLoopProvider, StateProvider, defaultColorMode } from "/utils/context.js";
|
|
9
9
|
import { ThemeProvider } from 'next-themes'
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import * as utils_context from "/utils/context.js";
|
|
12
|
+
import * as utils_state from "/utils/state.js";
|
|
13
|
+
import * as radix from "@radix-ui/themes";
|
|
10
14
|
|
|
11
15
|
{% for custom_code in custom_codes %}
|
|
12
16
|
{{custom_code}}
|
|
@@ -26,6 +30,16 @@ function AppWrap({children}) {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export default function MyApp({ Component, pageProps }) {
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
// Make contexts and state objects available globally for dynamic eval'd components
|
|
35
|
+
let windowImports = {
|
|
36
|
+
"react": React,
|
|
37
|
+
"@radix-ui/themes": radix,
|
|
38
|
+
"/utils/context": utils_context,
|
|
39
|
+
"/utils/state": utils_state,
|
|
40
|
+
};
|
|
41
|
+
window["__reflex"] = windowImports;
|
|
42
|
+
}, []);
|
|
29
43
|
return (
|
|
30
44
|
<ThemeProvider defaultTheme={ defaultColorMode } attribute="class">
|
|
31
45
|
<AppWrap>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "utils/context.js";
|
|
16
16
|
import debounce from "/utils/helpers/debounce";
|
|
17
17
|
import throttle from "/utils/helpers/throttle";
|
|
18
|
+
import * as Babel from "@babel/standalone";
|
|
18
19
|
|
|
19
20
|
// Endpoint URLs.
|
|
20
21
|
const EVENTURL = env.EVENT;
|
|
@@ -117,8 +118,8 @@ export const isStateful = () => {
|
|
|
117
118
|
if (event_queue.length === 0) {
|
|
118
119
|
return false;
|
|
119
120
|
}
|
|
120
|
-
return event_queue.some(event => event.name.startsWith("reflex___state"));
|
|
121
|
-
}
|
|
121
|
+
return event_queue.some((event) => event.name.startsWith("reflex___state"));
|
|
122
|
+
};
|
|
122
123
|
|
|
123
124
|
/**
|
|
124
125
|
* Apply a delta to the state.
|
|
@@ -129,6 +130,22 @@ export const applyDelta = (state, delta) => {
|
|
|
129
130
|
return { ...state, ...delta };
|
|
130
131
|
};
|
|
131
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Evaluate a dynamic component.
|
|
135
|
+
* @param component The component to evaluate.
|
|
136
|
+
* @returns The evaluated component.
|
|
137
|
+
*/
|
|
138
|
+
export const evalReactComponent = async (component) => {
|
|
139
|
+
if (!window.React && window.__reflex) {
|
|
140
|
+
window.React = window.__reflex.react;
|
|
141
|
+
}
|
|
142
|
+
const output = Babel.transform(component, { presets: ["react"] }).code;
|
|
143
|
+
const encodedJs = encodeURIComponent(output);
|
|
144
|
+
const dataUri = "data:text/javascript;charset=utf-8," + encodedJs;
|
|
145
|
+
const module = await eval(`import(dataUri)`);
|
|
146
|
+
return module.default;
|
|
147
|
+
};
|
|
148
|
+
|
|
132
149
|
/**
|
|
133
150
|
* Only Queue and process events when websocket connection exists.
|
|
134
151
|
* @param event The event to queue.
|
|
@@ -141,7 +158,7 @@ export const queueEventIfSocketExists = async (events, socket) => {
|
|
|
141
158
|
return;
|
|
142
159
|
}
|
|
143
160
|
await queueEvents(events, socket);
|
|
144
|
-
}
|
|
161
|
+
};
|
|
145
162
|
|
|
146
163
|
/**
|
|
147
164
|
* Handle frontend event or send the event to the backend via Websocket.
|
|
@@ -208,7 +225,10 @@ export const applyEvent = async (event, socket) => {
|
|
|
208
225
|
const a = document.createElement("a");
|
|
209
226
|
a.hidden = true;
|
|
210
227
|
// Special case when linking to uploaded files
|
|
211
|
-
a.href = event.payload.url.replace(
|
|
228
|
+
a.href = event.payload.url.replace(
|
|
229
|
+
"${getBackendURL(env.UPLOAD)}",
|
|
230
|
+
getBackendURL(env.UPLOAD)
|
|
231
|
+
);
|
|
212
232
|
a.download = event.payload.filename;
|
|
213
233
|
a.click();
|
|
214
234
|
a.remove();
|
|
@@ -249,7 +269,7 @@ export const applyEvent = async (event, socket) => {
|
|
|
249
269
|
} catch (e) {
|
|
250
270
|
console.log("_call_script", e);
|
|
251
271
|
if (window && window?.onerror) {
|
|
252
|
-
window.onerror(e.message, null, null, null, e)
|
|
272
|
+
window.onerror(e.message, null, null, null, e);
|
|
253
273
|
}
|
|
254
274
|
}
|
|
255
275
|
return false;
|
|
@@ -290,10 +310,9 @@ export const applyEvent = async (event, socket) => {
|
|
|
290
310
|
export const applyRestEvent = async (event, socket) => {
|
|
291
311
|
let eventSent = false;
|
|
292
312
|
if (event.handler === "uploadFiles") {
|
|
293
|
-
|
|
294
313
|
if (event.payload.files === undefined || event.payload.files.length === 0) {
|
|
295
314
|
// Submit the event over the websocket to trigger the event handler.
|
|
296
|
-
return await applyEvent(Event(event.name), socket)
|
|
315
|
+
return await applyEvent(Event(event.name), socket);
|
|
297
316
|
}
|
|
298
317
|
|
|
299
318
|
// Start upload, but do not wait for it, which would block other events.
|
|
@@ -397,7 +416,7 @@ export const connect = async (
|
|
|
397
416
|
console.log("Disconnect backend before bfcache on navigation");
|
|
398
417
|
socket.current.disconnect();
|
|
399
418
|
}
|
|
400
|
-
}
|
|
419
|
+
};
|
|
401
420
|
|
|
402
421
|
// Once the socket is open, hydrate the page.
|
|
403
422
|
socket.current.on("connect", () => {
|
|
@@ -416,7 +435,7 @@ export const connect = async (
|
|
|
416
435
|
});
|
|
417
436
|
|
|
418
437
|
// On each received message, queue the updates and events.
|
|
419
|
-
socket.current.on("event", (message) => {
|
|
438
|
+
socket.current.on("event", async (message) => {
|
|
420
439
|
const update = JSON5.parse(message);
|
|
421
440
|
for (const substate in update.delta) {
|
|
422
441
|
dispatch[substate](update.delta[substate]);
|
|
@@ -574,7 +593,11 @@ export const hydrateClientStorage = (client_storage) => {
|
|
|
574
593
|
}
|
|
575
594
|
}
|
|
576
595
|
}
|
|
577
|
-
if (
|
|
596
|
+
if (
|
|
597
|
+
client_storage.cookies ||
|
|
598
|
+
client_storage.local_storage ||
|
|
599
|
+
client_storage.session_storage
|
|
600
|
+
) {
|
|
578
601
|
return client_storage_values;
|
|
579
602
|
}
|
|
580
603
|
return {};
|
|
@@ -614,15 +637,17 @@ const applyClientStorageDelta = (client_storage, delta) => {
|
|
|
614
637
|
) {
|
|
615
638
|
const options = client_storage.local_storage[state_key];
|
|
616
639
|
localStorage.setItem(options.name || state_key, delta[substate][key]);
|
|
617
|
-
} else if(
|
|
640
|
+
} else if (
|
|
618
641
|
client_storage.session_storage &&
|
|
619
642
|
state_key in client_storage.session_storage &&
|
|
620
643
|
typeof window !== "undefined"
|
|
621
644
|
) {
|
|
622
645
|
const session_options = client_storage.session_storage[state_key];
|
|
623
|
-
sessionStorage.setItem(
|
|
646
|
+
sessionStorage.setItem(
|
|
647
|
+
session_options.name || state_key,
|
|
648
|
+
delta[substate][key]
|
|
649
|
+
);
|
|
624
650
|
}
|
|
625
|
-
|
|
626
651
|
}
|
|
627
652
|
}
|
|
628
653
|
};
|
|
@@ -651,7 +676,7 @@ export const useEventLoop = (
|
|
|
651
676
|
if (!(args instanceof Array)) {
|
|
652
677
|
args = [args];
|
|
653
678
|
}
|
|
654
|
-
const _e = args.filter((o) => o?.preventDefault !== undefined)[0]
|
|
679
|
+
const _e = args.filter((o) => o?.preventDefault !== undefined)[0];
|
|
655
680
|
|
|
656
681
|
if (event_actions?.preventDefault && _e?.preventDefault) {
|
|
657
682
|
_e.preventDefault();
|
|
@@ -671,7 +696,7 @@ export const useEventLoop = (
|
|
|
671
696
|
debounce(
|
|
672
697
|
combined_name,
|
|
673
698
|
() => queueEvents(events, socket),
|
|
674
|
-
event_actions.debounce
|
|
699
|
+
event_actions.debounce
|
|
675
700
|
);
|
|
676
701
|
} else {
|
|
677
702
|
queueEvents(events, socket);
|
|
@@ -696,30 +721,32 @@ export const useEventLoop = (
|
|
|
696
721
|
}
|
|
697
722
|
}, [router.isReady]);
|
|
698
723
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
724
|
+
// Handle frontend errors and send them to the backend via websocket.
|
|
725
|
+
useEffect(() => {
|
|
726
|
+
if (typeof window === "undefined") {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|
731
|
+
addEvents([
|
|
732
|
+
Event(`${exception_state_name}.handle_frontend_exception`, {
|
|
708
733
|
stack: error.stack,
|
|
709
|
-
})
|
|
710
|
-
|
|
711
|
-
|
|
734
|
+
}),
|
|
735
|
+
]);
|
|
736
|
+
return false;
|
|
737
|
+
};
|
|
712
738
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
739
|
+
//NOTE: Only works in Chrome v49+
|
|
740
|
+
//https://github.com/mknichel/javascript-errors?tab=readme-ov-file#promise-rejection-events
|
|
741
|
+
window.onunhandledrejection = function (event) {
|
|
742
|
+
addEvents([
|
|
743
|
+
Event(`${exception_state_name}.handle_frontend_exception`, {
|
|
744
|
+
stack: event.reason.stack,
|
|
745
|
+
}),
|
|
746
|
+
]);
|
|
747
|
+
return false;
|
|
748
|
+
};
|
|
749
|
+
}, []);
|
|
723
750
|
|
|
724
751
|
// Main event loop.
|
|
725
752
|
useEffect(() => {
|
|
@@ -782,11 +809,11 @@ export const useEventLoop = (
|
|
|
782
809
|
// Route after the initial page hydration.
|
|
783
810
|
useEffect(() => {
|
|
784
811
|
const change_start = () => {
|
|
785
|
-
const main_state_dispatch = dispatch["reflex___state____state"]
|
|
812
|
+
const main_state_dispatch = dispatch["reflex___state____state"];
|
|
786
813
|
if (main_state_dispatch !== undefined) {
|
|
787
|
-
main_state_dispatch({ is_hydrated: false })
|
|
814
|
+
main_state_dispatch({ is_hydrated: false });
|
|
788
815
|
}
|
|
789
|
-
}
|
|
816
|
+
};
|
|
790
817
|
const change_complete = () => addEvents(onLoadInternalEvent());
|
|
791
818
|
router.events.on("routeChangeStart", change_start);
|
|
792
819
|
router.events.on("routeChangeComplete", change_complete);
|
reflex/app.py
CHANGED
|
@@ -482,9 +482,8 @@ class App(MiddlewareMixin, LifespanMixin, Base):
|
|
|
482
482
|
"""
|
|
483
483
|
# If the route is not set, get it from the callable.
|
|
484
484
|
if route is None:
|
|
485
|
-
|
|
486
|
-
component
|
|
487
|
-
), "Route must be set if component is not a callable."
|
|
485
|
+
if not isinstance(component, Callable):
|
|
486
|
+
raise ValueError("Route must be set if component is not a callable.")
|
|
488
487
|
# Format the route.
|
|
489
488
|
route = format.format_route(component.__name__)
|
|
490
489
|
else:
|
|
@@ -1528,6 +1527,9 @@ class EventNamespace(AsyncNamespace):
|
|
|
1528
1527
|
async def on_event(self, sid, data):
|
|
1529
1528
|
"""Event for receiving front-end websocket events.
|
|
1530
1529
|
|
|
1530
|
+
Raises:
|
|
1531
|
+
RuntimeError: If the Socket.IO is badly initialized.
|
|
1532
|
+
|
|
1531
1533
|
Args:
|
|
1532
1534
|
sid: The Socket.IO session id.
|
|
1533
1535
|
data: The event data.
|
|
@@ -1540,9 +1542,11 @@ class EventNamespace(AsyncNamespace):
|
|
|
1540
1542
|
self.sid_to_token[sid] = event.token
|
|
1541
1543
|
|
|
1542
1544
|
# Get the event environment.
|
|
1543
|
-
|
|
1545
|
+
if self.app.sio is None:
|
|
1546
|
+
raise RuntimeError("Socket.IO is not initialized.")
|
|
1544
1547
|
environ = self.app.sio.get_environ(sid, self.namespace)
|
|
1545
|
-
|
|
1548
|
+
if environ is None:
|
|
1549
|
+
raise RuntimeError("Socket.IO environ is not initialized.")
|
|
1546
1550
|
|
|
1547
1551
|
# Get the client headers.
|
|
1548
1552
|
headers = {
|
reflex/app_mixins/lifespan.py
CHANGED
|
@@ -6,11 +6,13 @@ import asyncio
|
|
|
6
6
|
import contextlib
|
|
7
7
|
import functools
|
|
8
8
|
import inspect
|
|
9
|
-
import sys
|
|
10
9
|
from typing import Callable, Coroutine, Set, Union
|
|
11
10
|
|
|
12
11
|
from fastapi import FastAPI
|
|
13
12
|
|
|
13
|
+
from reflex.utils import console
|
|
14
|
+
from reflex.utils.exceptions import InvalidLifespanTaskType
|
|
15
|
+
|
|
14
16
|
from .mixin import AppMixin
|
|
15
17
|
|
|
16
18
|
|
|
@@ -26,6 +28,7 @@ class LifespanMixin(AppMixin):
|
|
|
26
28
|
try:
|
|
27
29
|
async with contextlib.AsyncExitStack() as stack:
|
|
28
30
|
for task in self.lifespan_tasks:
|
|
31
|
+
run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # type: ignore
|
|
29
32
|
if isinstance(task, asyncio.Task):
|
|
30
33
|
running_tasks.append(task)
|
|
31
34
|
else:
|
|
@@ -35,15 +38,19 @@ class LifespanMixin(AppMixin):
|
|
|
35
38
|
_t = task()
|
|
36
39
|
if isinstance(_t, contextlib._AsyncGeneratorContextManager):
|
|
37
40
|
await stack.enter_async_context(_t)
|
|
41
|
+
console.debug(run_msg.format(type="asynccontextmanager"))
|
|
38
42
|
elif isinstance(_t, Coroutine):
|
|
39
|
-
|
|
43
|
+
task_ = asyncio.create_task(_t)
|
|
44
|
+
task_.add_done_callback(lambda t: t.result())
|
|
45
|
+
running_tasks.append(task_)
|
|
46
|
+
console.debug(run_msg.format(type="coroutine"))
|
|
47
|
+
else:
|
|
48
|
+
console.debug(run_msg.format(type="function"))
|
|
40
49
|
yield
|
|
41
50
|
finally:
|
|
42
|
-
cancel_kwargs = (
|
|
43
|
-
{"msg": "lifespan_cleanup"} if sys.version_info >= (3, 9) else {}
|
|
44
|
-
)
|
|
45
51
|
for task in running_tasks:
|
|
46
|
-
|
|
52
|
+
console.debug(f"Canceling lifespan task: {task}")
|
|
53
|
+
task.cancel(msg="lifespan_cleanup")
|
|
47
54
|
|
|
48
55
|
def register_lifespan_task(self, task: Callable | asyncio.Task, **task_kwargs):
|
|
49
56
|
"""Register a task to run during the lifespan of the app.
|
|
@@ -51,7 +58,18 @@ class LifespanMixin(AppMixin):
|
|
|
51
58
|
Args:
|
|
52
59
|
task: The task to register.
|
|
53
60
|
task_kwargs: The kwargs of the task.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
InvalidLifespanTaskType: If the task is a generator function.
|
|
54
64
|
"""
|
|
65
|
+
if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
|
|
66
|
+
raise InvalidLifespanTaskType(
|
|
67
|
+
f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
|
|
68
|
+
)
|
|
69
|
+
|
|
55
70
|
if task_kwargs:
|
|
71
|
+
original_task = task
|
|
56
72
|
task = functools.partial(task, **task_kwargs) # type: ignore
|
|
73
|
+
functools.update_wrapper(task, original_task) # type: ignore
|
|
57
74
|
self.lifespan_tasks.add(task) # type: ignore
|
|
75
|
+
console.debug(f"Registered lifespan task: {task.__name__}") # type: ignore
|
reflex/base.py
CHANGED
|
@@ -47,6 +47,9 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
|
|
|
47
47
|
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
|
48
48
|
pydantic_main.validate_field_name = validate_field_name # type: ignore
|
|
49
49
|
|
|
50
|
+
if TYPE_CHECKING:
|
|
51
|
+
from reflex.vars import Var
|
|
52
|
+
|
|
50
53
|
|
|
51
54
|
class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|
52
55
|
"""The base class subclassed by all Reflex classes.
|
|
@@ -92,7 +95,7 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|
|
92
95
|
return self
|
|
93
96
|
|
|
94
97
|
@classmethod
|
|
95
|
-
def get_fields(cls) -> dict[str,
|
|
98
|
+
def get_fields(cls) -> dict[str, ModelField]:
|
|
96
99
|
"""Get the fields of the object.
|
|
97
100
|
|
|
98
101
|
Returns:
|
|
@@ -101,7 +104,7 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|
|
101
104
|
return cls.__fields__
|
|
102
105
|
|
|
103
106
|
@classmethod
|
|
104
|
-
def add_field(cls, var:
|
|
107
|
+
def add_field(cls, var: Var, default_value: Any):
|
|
105
108
|
"""Add a pydantic field after class definition.
|
|
106
109
|
|
|
107
110
|
Used by State.add_var() to correctly handle the new variable.
|
|
@@ -110,7 +113,7 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|
|
110
113
|
var: The variable to add a pydantic field for.
|
|
111
114
|
default_value: The default value of the field
|
|
112
115
|
"""
|
|
113
|
-
var_name = var.
|
|
116
|
+
var_name = var._var_field_name
|
|
114
117
|
new_field = ModelField.infer(
|
|
115
118
|
name=var_name,
|
|
116
119
|
value=default_value,
|
|
@@ -133,13 +136,4 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|
|
133
136
|
# Seems like this function signature was wrong all along?
|
|
134
137
|
# If the user wants a field that we know of, get it and pass it off to _get_value
|
|
135
138
|
key = getattr(self, key)
|
|
136
|
-
return
|
|
137
|
-
key,
|
|
138
|
-
to_dict=True,
|
|
139
|
-
by_alias=False,
|
|
140
|
-
include=None,
|
|
141
|
-
exclude=None,
|
|
142
|
-
exclude_unset=False,
|
|
143
|
-
exclude_defaults=False,
|
|
144
|
-
exclude_none=False,
|
|
145
|
-
)
|
|
139
|
+
return key
|
reflex/compiler/utils.py
CHANGED
|
@@ -44,6 +44,9 @@ def compile_import_statement(fields: list[ImportVar]) -> tuple[str, list[str]]:
|
|
|
44
44
|
Args:
|
|
45
45
|
fields: The set of fields to import from the library.
|
|
46
46
|
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If there is more than one default import.
|
|
49
|
+
|
|
47
50
|
Returns:
|
|
48
51
|
The libraries for default and rest.
|
|
49
52
|
default: default library. When install "import def from library".
|
|
@@ -54,7 +57,8 @@ def compile_import_statement(fields: list[ImportVar]) -> tuple[str, list[str]]:
|
|
|
54
57
|
|
|
55
58
|
# Check for default imports.
|
|
56
59
|
defaults = {field for field in fields_set if field.is_default}
|
|
57
|
-
|
|
60
|
+
if len(defaults) >= 2:
|
|
61
|
+
raise ValueError("Only one default import is allowed.")
|
|
58
62
|
|
|
59
63
|
# Get the default import, and the specific imports.
|
|
60
64
|
default = next(iter({field.name for field in defaults}), "")
|
|
@@ -92,6 +96,9 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
|
|
92
96
|
Args:
|
|
93
97
|
import_dict: The import dict to compile.
|
|
94
98
|
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If an import in the dict is invalid.
|
|
101
|
+
|
|
95
102
|
Returns:
|
|
96
103
|
The list of import dict.
|
|
97
104
|
"""
|
|
@@ -106,8 +113,10 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
|
|
106
113
|
continue
|
|
107
114
|
|
|
108
115
|
if not lib:
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
if default:
|
|
117
|
+
raise ValueError("No default field allowed for empty library.")
|
|
118
|
+
if rest is None or len(rest) == 0:
|
|
119
|
+
raise ValueError("No fields to import.")
|
|
111
120
|
for module in sorted(rest):
|
|
112
121
|
import_dicts.append(get_import_dict(module))
|
|
113
122
|
continue
|
|
@@ -155,7 +164,7 @@ def compile_state(state: Type[BaseState]) -> dict:
|
|
|
155
164
|
initial_state = state(_reflex_internal_init=True).dict(
|
|
156
165
|
initial=True, include_computed=False
|
|
157
166
|
)
|
|
158
|
-
return
|
|
167
|
+
return initial_state
|
|
159
168
|
|
|
160
169
|
|
|
161
170
|
def _compile_client_storage_field(
|
|
@@ -429,11 +438,11 @@ def add_meta(
|
|
|
429
438
|
Returns:
|
|
430
439
|
The component with the metadata added.
|
|
431
440
|
"""
|
|
432
|
-
meta_tags = [
|
|
433
|
-
|
|
434
|
-
children: list[Any] = [
|
|
435
|
-
Title.create(title),
|
|
441
|
+
meta_tags = [
|
|
442
|
+
item if isinstance(item, Component) else Meta.create(**item) for item in meta
|
|
436
443
|
]
|
|
444
|
+
|
|
445
|
+
children: list[Any] = [Title.create(title)]
|
|
437
446
|
if description:
|
|
438
447
|
children.append(Description.create(content=description))
|
|
439
448
|
children.append(Image.create(content=image))
|
reflex/components/base/bare.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import Any, Iterator
|
|
|
7
7
|
from reflex.components.component import Component
|
|
8
8
|
from reflex.components.tags import Tag
|
|
9
9
|
from reflex.components.tags.tagless import Tagless
|
|
10
|
-
from reflex.vars
|
|
10
|
+
from reflex.vars import ArrayVar, BooleanVar, ObjectVar, Var
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Bare(Component):
|
|
@@ -33,6 +33,8 @@ class Bare(Component):
|
|
|
33
33
|
|
|
34
34
|
def _render(self) -> Tag:
|
|
35
35
|
if isinstance(self.contents, Var):
|
|
36
|
+
if isinstance(self.contents, (BooleanVar, ObjectVar, ArrayVar)):
|
|
37
|
+
return Tagless(contents=f"{{{str(self.contents.to_string())}}}")
|
|
36
38
|
return Tagless(contents=f"{{{str(self.contents)}}}")
|
|
37
39
|
return Tagless(contents=str(self.contents))
|
|
38
40
|
|
reflex/components/base/meta.py
CHANGED
|
@@ -16,13 +16,15 @@ class Title(Component):
|
|
|
16
16
|
def render(self) -> dict:
|
|
17
17
|
"""Render the title component.
|
|
18
18
|
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: If the title is not a single string.
|
|
21
|
+
|
|
19
22
|
Returns:
|
|
20
23
|
The rendered title component.
|
|
21
24
|
"""
|
|
22
25
|
# Make sure the title is a single string.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
), "Title must be a single string."
|
|
26
|
+
if len(self.children) != 1 or not isinstance(self.children[0], Bare):
|
|
27
|
+
raise ValueError("Title must be a single string.")
|
|
26
28
|
return super().render()
|
|
27
29
|
|
|
28
30
|
|
reflex/components/component.py
CHANGED
|
@@ -25,6 +25,7 @@ import reflex.state
|
|
|
25
25
|
from reflex.base import Base
|
|
26
26
|
from reflex.compiler.templates import STATEFUL_COMPONENT
|
|
27
27
|
from reflex.components.core.breakpoints import Breakpoints
|
|
28
|
+
from reflex.components.dynamic import load_dynamic_serializer
|
|
28
29
|
from reflex.components.tags import Tag
|
|
29
30
|
from reflex.constants import (
|
|
30
31
|
Dirs,
|
|
@@ -52,9 +53,9 @@ from reflex.utils.imports import (
|
|
|
52
53
|
ParsedImportDict,
|
|
53
54
|
parse_imports,
|
|
54
55
|
)
|
|
55
|
-
from reflex.utils.serializers import serializer
|
|
56
56
|
from reflex.vars import VarData
|
|
57
57
|
from reflex.vars.base import LiteralVar, Var
|
|
58
|
+
from reflex.vars.sequence import LiteralArrayVar
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
class BaseComponent(Base, ABC):
|
|
@@ -477,7 +478,7 @@ class Component(BaseComponent, ABC):
|
|
|
477
478
|
# Merge styles, the later ones overriding keys in the earlier ones.
|
|
478
479
|
style = {k: v for style_dict in style for k, v in style_dict.items()}
|
|
479
480
|
|
|
480
|
-
if isinstance(style, Breakpoints):
|
|
481
|
+
if isinstance(style, (Breakpoints, Var)):
|
|
481
482
|
style = {
|
|
482
483
|
# Assign the Breakpoints to the self-referential selector to avoid squashing down to a regular dict.
|
|
483
484
|
"&": style,
|
|
@@ -496,7 +497,12 @@ class Component(BaseComponent, ABC):
|
|
|
496
497
|
# Convert class_name to str if it's list
|
|
497
498
|
class_name = kwargs.get("class_name", "")
|
|
498
499
|
if isinstance(class_name, (List, tuple)):
|
|
499
|
-
|
|
500
|
+
if any(isinstance(c, Var) for c in class_name):
|
|
501
|
+
kwargs["class_name"] = LiteralArrayVar.create(
|
|
502
|
+
class_name, _var_type=List[str]
|
|
503
|
+
).join(" ")
|
|
504
|
+
else:
|
|
505
|
+
kwargs["class_name"] = " ".join(class_name)
|
|
500
506
|
|
|
501
507
|
# Construct the component.
|
|
502
508
|
super().__init__(*args, **kwargs)
|
|
@@ -623,8 +629,8 @@ class Component(BaseComponent, ABC):
|
|
|
623
629
|
if types._issubclass(field.type_, EventHandler):
|
|
624
630
|
args_spec = None
|
|
625
631
|
annotation = field.annotation
|
|
626
|
-
if
|
|
627
|
-
args_spec =
|
|
632
|
+
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
|
633
|
+
args_spec = metadata[0]
|
|
628
634
|
default_triggers[field.name] = args_spec or (lambda: [])
|
|
629
635
|
return default_triggers
|
|
630
636
|
|
|
@@ -1738,10 +1744,14 @@ class CustomComponent(Component):
|
|
|
1738
1744
|
Args:
|
|
1739
1745
|
seen: The tags of the components that have already been seen.
|
|
1740
1746
|
|
|
1747
|
+
Raises:
|
|
1748
|
+
ValueError: If the tag is not set.
|
|
1749
|
+
|
|
1741
1750
|
Returns:
|
|
1742
1751
|
The set of custom components.
|
|
1743
1752
|
"""
|
|
1744
|
-
|
|
1753
|
+
if self.tag is None:
|
|
1754
|
+
raise ValueError("The tag must be set.")
|
|
1745
1755
|
|
|
1746
1756
|
# Store the seen components in a set to avoid infinite recursion.
|
|
1747
1757
|
if seen is None:
|
|
@@ -1890,19 +1900,6 @@ class NoSSRComponent(Component):
|
|
|
1890
1900
|
return "".join((library_import, mod_import, opts_fragment))
|
|
1891
1901
|
|
|
1892
1902
|
|
|
1893
|
-
@serializer
|
|
1894
|
-
def serialize_component(comp: Component):
|
|
1895
|
-
"""Serialize a component.
|
|
1896
|
-
|
|
1897
|
-
Args:
|
|
1898
|
-
comp: The component to serialize.
|
|
1899
|
-
|
|
1900
|
-
Returns:
|
|
1901
|
-
The serialized component.
|
|
1902
|
-
"""
|
|
1903
|
-
return str(comp)
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
1903
|
class StatefulComponent(BaseComponent):
|
|
1907
1904
|
"""A component that depends on state and is rendered outside of the page component.
|
|
1908
1905
|
|
|
@@ -2315,3 +2312,6 @@ class MemoizationLeaf(Component):
|
|
|
2315
2312
|
update={"disposition": MemoizationDisposition.ALWAYS}
|
|
2316
2313
|
)
|
|
2317
2314
|
return comp
|
|
2315
|
+
|
|
2316
|
+
|
|
2317
|
+
load_dynamic_serializer()
|
reflex/components/core/cond.py
CHANGED
|
@@ -138,13 +138,13 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
|
|
138
138
|
"""
|
|
139
139
|
# Convert the condition to a Var.
|
|
140
140
|
cond_var = LiteralVar.create(condition)
|
|
141
|
-
|
|
141
|
+
if cond_var is None:
|
|
142
|
+
raise ValueError("The condition must be set.")
|
|
142
143
|
|
|
143
144
|
# If the first component is a component, create a Cond component.
|
|
144
145
|
if isinstance(c1, BaseComponent):
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
), "Both arguments must be components."
|
|
146
|
+
if c2 is not None and not isinstance(c2, BaseComponent):
|
|
147
|
+
raise ValueError("Both arguments must be components.")
|
|
148
148
|
return Cond.create(cond_var, c1, c2)
|
|
149
149
|
|
|
150
150
|
# Otherwise, create a conditional Var.
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
# ------------------------------------------------------
|
|
5
5
|
|
|
6
6
|
from .code import CodeBlock as CodeBlock
|
|
7
|
-
from .code import LiteralCodeBlockTheme as LiteralCodeBlockTheme
|
|
8
7
|
from .code import LiteralCodeLanguage as LiteralCodeLanguage
|
|
9
8
|
from .code import code_block as code_block
|
|
10
9
|
from .dataeditor import DataEditorTheme as DataEditorTheme
|