reflex 0.6.6.post2__py3-none-any.whl → 0.6.7__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/stateful_component.js.jinja2 +5 -1
- reflex/.templates/web/utils/state.js +36 -28
- reflex/__init__.py +1 -1
- reflex/__init__.pyi +1 -0
- reflex/app.py +41 -16
- reflex/assets.py +2 -2
- reflex/base.py +8 -7
- reflex/compiler/templates.py +1 -0
- reflex/compiler/utils.py +2 -3
- reflex/components/base/bare.py +2 -2
- reflex/components/component.py +54 -29
- reflex/components/core/banner.py +2 -2
- reflex/components/core/banner.pyi +1 -1
- reflex/components/core/client_side_routing.py +2 -2
- reflex/components/core/client_side_routing.pyi +1 -1
- reflex/components/core/clipboard.py +11 -9
- reflex/components/core/clipboard.pyi +1 -1
- reflex/components/core/cond.py +3 -3
- reflex/components/core/foreach.py +1 -1
- reflex/components/core/html.pyi +1 -1
- reflex/components/core/upload.py +8 -8
- reflex/components/datadisplay/code.py +5 -5
- reflex/components/datadisplay/dataeditor.py +8 -28
- reflex/components/datadisplay/dataeditor.pyi +1 -1
- reflex/components/datadisplay/shiki_code_block.py +7 -7
- reflex/components/dynamic.py +2 -2
- reflex/components/el/elements/__init__.py +1 -1
- reflex/components/el/elements/__init__.pyi +1 -1
- reflex/components/el/elements/base.py +2 -2
- reflex/components/el/elements/base.pyi +1 -1
- reflex/components/el/elements/forms.py +40 -10
- reflex/components/el/elements/forms.pyi +17 -15
- reflex/components/el/elements/inline.py +1 -1
- reflex/components/el/elements/inline.pyi +28 -28
- reflex/components/el/elements/media.py +1 -4
- reflex/components/el/elements/media.pyi +25 -26
- reflex/components/el/elements/metadata.py +6 -6
- reflex/components/el/elements/metadata.pyi +4 -4
- reflex/components/el/elements/other.py +17 -9
- reflex/components/el/elements/other.pyi +7 -7
- reflex/components/el/elements/scripts.py +1 -2
- reflex/components/el/elements/scripts.pyi +3 -3
- reflex/components/el/elements/sectioning.py +16 -16
- reflex/components/el/elements/sectioning.pyi +15 -15
- reflex/components/el/elements/tables.py +1 -1
- reflex/components/el/elements/tables.pyi +10 -10
- reflex/components/el/elements/typography.py +1 -1
- reflex/components/el/elements/typography.pyi +15 -15
- reflex/components/markdown/markdown.py +3 -3
- reflex/components/next/image.py +1 -1
- reflex/components/next/image.pyi +1 -1
- reflex/components/plotly/plotly.py +2 -2
- reflex/components/radix/primitives/accordion.py +2 -1
- reflex/components/radix/primitives/form.pyi +3 -3
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/radix/themes/base.py +4 -10
- reflex/components/radix/themes/color_mode.pyi +2 -2
- reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
- reflex/components/radix/themes/components/badge.pyi +1 -1
- reflex/components/radix/themes/components/button.pyi +1 -1
- reflex/components/radix/themes/components/callout.pyi +5 -5
- reflex/components/radix/themes/components/card.pyi +1 -1
- reflex/components/radix/themes/components/checkbox.pyi +3 -3
- reflex/components/radix/themes/components/context_menu.py +11 -0
- reflex/components/radix/themes/components/context_menu.pyi +155 -0
- reflex/components/radix/themes/components/dialog.pyi +1 -1
- reflex/components/radix/themes/components/hover_card.pyi +1 -1
- reflex/components/radix/themes/components/icon_button.py +1 -1
- reflex/components/radix/themes/components/icon_button.pyi +1 -1
- reflex/components/radix/themes/components/inset.pyi +1 -1
- reflex/components/radix/themes/components/popover.pyi +1 -1
- reflex/components/radix/themes/components/radio_group.py +2 -4
- reflex/components/radix/themes/components/radio_group.pyi +1 -1
- reflex/components/radix/themes/components/select.pyi +3 -3
- reflex/components/radix/themes/components/slider.pyi +1 -1
- reflex/components/radix/themes/components/switch.pyi +1 -1
- reflex/components/radix/themes/components/table.pyi +7 -7
- reflex/components/radix/themes/components/tabs.pyi +2 -2
- reflex/components/radix/themes/components/text_area.py +3 -0
- reflex/components/radix/themes/components/text_area.pyi +3 -1
- reflex/components/radix/themes/components/text_field.py +16 -1
- reflex/components/radix/themes/components/text_field.pyi +105 -17
- reflex/components/radix/themes/layout/box.pyi +1 -1
- reflex/components/radix/themes/layout/center.pyi +1 -1
- reflex/components/radix/themes/layout/flex.pyi +1 -1
- reflex/components/radix/themes/layout/grid.pyi +1 -1
- reflex/components/radix/themes/layout/list.py +0 -4
- reflex/components/radix/themes/layout/list.pyi +3 -8
- reflex/components/radix/themes/layout/section.pyi +1 -1
- reflex/components/radix/themes/layout/spacer.pyi +1 -1
- reflex/components/radix/themes/layout/stack.pyi +3 -3
- reflex/components/radix/themes/typography/blockquote.pyi +1 -1
- reflex/components/radix/themes/typography/code.pyi +1 -1
- reflex/components/radix/themes/typography/heading.pyi +1 -1
- reflex/components/radix/themes/typography/link.py +5 -1
- reflex/components/radix/themes/typography/link.pyi +1 -1
- reflex/components/radix/themes/typography/text.pyi +7 -7
- reflex/components/recharts/cartesian.py +1 -1
- reflex/components/recharts/charts.py +4 -4
- reflex/components/recharts/polar.py +1 -1
- reflex/components/recharts/polar.pyi +1 -1
- reflex/components/sonner/toast.py +4 -7
- reflex/components/suneditor/editor.py +6 -6
- reflex/components/suneditor/editor.pyi +6 -6
- reflex/config.py +25 -10
- reflex/constants/compiler.py +6 -0
- reflex/constants/config.py +2 -0
- reflex/constants/custom_components.py +1 -1
- reflex/constants/route.py +1 -1
- reflex/custom_components/custom_components.py +21 -21
- reflex/event.py +57 -22
- reflex/experimental/client_state.py +2 -1
- reflex/experimental/layout.py +0 -6
- reflex/model.py +125 -9
- reflex/reflex.py +12 -8
- reflex/state.py +200 -88
- reflex/style.py +1 -4
- reflex/testing.py +10 -11
- reflex/utils/build.py +1 -1
- reflex/utils/console.py +75 -6
- reflex/utils/exceptions.py +12 -0
- reflex/utils/exec.py +10 -10
- reflex/utils/export.py +1 -2
- reflex/utils/format.py +11 -8
- reflex/utils/path_ops.py +2 -2
- reflex/utils/prerequisites.py +31 -28
- reflex/utils/processes.py +4 -4
- reflex/utils/pyi_generator.py +12 -11
- reflex/utils/types.py +6 -3
- reflex/vars/__init__.py +1 -0
- reflex/vars/base.py +75 -38
- reflex/vars/datetime.py +222 -0
- reflex/vars/function.py +3 -3
- reflex/vars/number.py +3 -3
- reflex/vars/object.py +5 -5
- reflex/vars/sequence.py +7 -7
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/METADATA +2 -2
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/RECORD +141 -140
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/LICENSE +0 -0
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/WHEEL +0 -0
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/entry_points.txt +0 -0
|
@@ -5,11 +5,15 @@ export function {{tag_name}} () {
|
|
|
5
5
|
{{ hook }}
|
|
6
6
|
{% endfor %}
|
|
7
7
|
|
|
8
|
+
{% for hook, data in component._get_all_hooks().items() if not data.position or data.position == const.hook_position.PRE_TRIGGER %}
|
|
9
|
+
{{ hook }}
|
|
10
|
+
{% endfor %}
|
|
11
|
+
|
|
8
12
|
{% for hook in memo_trigger_hooks %}
|
|
9
13
|
{{ hook }}
|
|
10
14
|
{% endfor %}
|
|
11
15
|
|
|
12
|
-
{% for hook in component._get_all_hooks() %}
|
|
16
|
+
{% for hook, data in component._get_all_hooks().items() if data.position and data.position == const.hook_position.POST_TRIGGER %}
|
|
13
17
|
{{ hook }}
|
|
14
18
|
{% endfor %}
|
|
15
19
|
|
|
@@ -40,9 +40,6 @@ let event_processing = false;
|
|
|
40
40
|
// Array holding pending events to be processed.
|
|
41
41
|
const event_queue = [];
|
|
42
42
|
|
|
43
|
-
// Pending upload promises, by id
|
|
44
|
-
const upload_controllers = {};
|
|
45
|
-
|
|
46
43
|
/**
|
|
47
44
|
* Generate a UUID (Used for session tokens).
|
|
48
45
|
* Taken from: https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid
|
|
@@ -300,7 +297,7 @@ export const applyEvent = async (event, socket) => {
|
|
|
300
297
|
if (socket) {
|
|
301
298
|
socket.emit(
|
|
302
299
|
"event",
|
|
303
|
-
|
|
300
|
+
event,
|
|
304
301
|
);
|
|
305
302
|
return true;
|
|
306
303
|
}
|
|
@@ -407,6 +404,8 @@ export const connect = async (
|
|
|
407
404
|
transports: transports,
|
|
408
405
|
autoUnref: false,
|
|
409
406
|
});
|
|
407
|
+
// Ensure undefined fields in events are sent as null instead of removed
|
|
408
|
+
socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v)
|
|
410
409
|
|
|
411
410
|
function checkVisibility() {
|
|
412
411
|
if (document.visibilityState === "visible") {
|
|
@@ -443,8 +442,7 @@ export const connect = async (
|
|
|
443
442
|
});
|
|
444
443
|
|
|
445
444
|
// On each received message, queue the updates and events.
|
|
446
|
-
socket.current.on("event", async (
|
|
447
|
-
const update = JSON5.parse(message);
|
|
445
|
+
socket.current.on("event", async (update) => {
|
|
448
446
|
for (const substate in update.delta) {
|
|
449
447
|
dispatch[substate](update.delta[substate]);
|
|
450
448
|
}
|
|
@@ -456,7 +454,7 @@ export const connect = async (
|
|
|
456
454
|
});
|
|
457
455
|
socket.current.on("reload", async (event) => {
|
|
458
456
|
event_processing = false;
|
|
459
|
-
queueEvents([...initialEvents(),
|
|
457
|
+
queueEvents([...initialEvents(), event], socket);
|
|
460
458
|
});
|
|
461
459
|
|
|
462
460
|
document.addEventListener("visibilitychange", checkVisibility);
|
|
@@ -485,7 +483,9 @@ export const uploadFiles = async (
|
|
|
485
483
|
return false;
|
|
486
484
|
}
|
|
487
485
|
|
|
488
|
-
|
|
486
|
+
const upload_ref_name = `__upload_controllers_${upload_id}`
|
|
487
|
+
|
|
488
|
+
if (refs[upload_ref_name]) {
|
|
489
489
|
console.log("Upload already in progress for ", upload_id);
|
|
490
490
|
return false;
|
|
491
491
|
}
|
|
@@ -497,23 +497,31 @@ export const uploadFiles = async (
|
|
|
497
497
|
// Whenever called, responseText will contain the entire response so far.
|
|
498
498
|
const chunks = progressEvent.event.target.responseText.trim().split("\n");
|
|
499
499
|
// So only process _new_ chunks beyond resp_idx.
|
|
500
|
-
chunks.slice(resp_idx).map((
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
500
|
+
chunks.slice(resp_idx).map((chunk_json) => {
|
|
501
|
+
try {
|
|
502
|
+
const chunk = JSON5.parse(chunk_json);
|
|
503
|
+
event_callbacks.map((f, ix) => {
|
|
504
|
+
f(chunk)
|
|
505
|
+
.then(() => {
|
|
506
|
+
if (ix === event_callbacks.length - 1) {
|
|
507
|
+
// Mark this chunk as processed.
|
|
508
|
+
resp_idx += 1;
|
|
509
|
+
}
|
|
510
|
+
})
|
|
511
|
+
.catch((e) => {
|
|
512
|
+
if (progressEvent.progress === 1) {
|
|
513
|
+
// Chunk may be incomplete, so only report errors when full response is available.
|
|
514
|
+
console.log("Error processing chunk", chunk, e);
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
} catch (e) {
|
|
520
|
+
if (progressEvent.progress === 1) {
|
|
521
|
+
console.log("Error parsing chunk", chunk_json, e);
|
|
522
|
+
}
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
517
525
|
});
|
|
518
526
|
};
|
|
519
527
|
|
|
@@ -537,7 +545,7 @@ export const uploadFiles = async (
|
|
|
537
545
|
});
|
|
538
546
|
|
|
539
547
|
// Send the file to the server.
|
|
540
|
-
|
|
548
|
+
refs[upload_ref_name] = controller;
|
|
541
549
|
|
|
542
550
|
try {
|
|
543
551
|
return await axios.post(getBackendURL(UPLOADURL), formdata, config);
|
|
@@ -557,7 +565,7 @@ export const uploadFiles = async (
|
|
|
557
565
|
}
|
|
558
566
|
return false;
|
|
559
567
|
} finally {
|
|
560
|
-
delete
|
|
568
|
+
delete refs[upload_ref_name];
|
|
561
569
|
}
|
|
562
570
|
};
|
|
563
571
|
|
|
@@ -799,7 +807,7 @@ export const useEventLoop = (
|
|
|
799
807
|
connect(
|
|
800
808
|
socket,
|
|
801
809
|
dispatch,
|
|
802
|
-
["websocket"
|
|
810
|
+
["websocket"],
|
|
803
811
|
setConnectErrors,
|
|
804
812
|
client_storage
|
|
805
813
|
);
|
reflex/__init__.py
CHANGED
reflex/__init__.pyi
CHANGED
|
@@ -186,6 +186,7 @@ from .istate.wrappers import get_state as get_state
|
|
|
186
186
|
from .middleware import Middleware as Middleware
|
|
187
187
|
from .middleware import middleware as middleware
|
|
188
188
|
from .model import Model as Model
|
|
189
|
+
from .model import asession as asession
|
|
189
190
|
from .model import session as session
|
|
190
191
|
from .page import page as page
|
|
191
192
|
from .state import ComponentState as ComponentState
|
reflex/app.py
CHANGED
|
@@ -17,6 +17,7 @@ import sys
|
|
|
17
17
|
import traceback
|
|
18
18
|
from datetime import datetime
|
|
19
19
|
from pathlib import Path
|
|
20
|
+
from types import SimpleNamespace
|
|
20
21
|
from typing import (
|
|
21
22
|
TYPE_CHECKING,
|
|
22
23
|
Any,
|
|
@@ -363,6 +364,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
363
364
|
max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
|
|
364
365
|
ping_interval=constants.Ping.INTERVAL,
|
|
365
366
|
ping_timeout=constants.Ping.TIMEOUT,
|
|
367
|
+
json=SimpleNamespace(
|
|
368
|
+
dumps=staticmethod(format.json_dumps),
|
|
369
|
+
loads=staticmethod(json.loads),
|
|
370
|
+
),
|
|
371
|
+
transports=["websocket"],
|
|
366
372
|
)
|
|
367
373
|
elif getattr(self.sio, "async_mode", "") != "asgi":
|
|
368
374
|
raise RuntimeError(
|
|
@@ -430,7 +436,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
430
436
|
allow_credentials=True,
|
|
431
437
|
allow_methods=["*"],
|
|
432
438
|
allow_headers=["*"],
|
|
433
|
-
allow_origins=
|
|
439
|
+
allow_origins=get_config().cors_allowed_origins,
|
|
434
440
|
)
|
|
435
441
|
|
|
436
442
|
@property
|
|
@@ -467,7 +473,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
467
473
|
|
|
468
474
|
def add_page(
|
|
469
475
|
self,
|
|
470
|
-
component: Component | ComponentCallable,
|
|
476
|
+
component: Component | ComponentCallable | None = None,
|
|
471
477
|
route: str | None = None,
|
|
472
478
|
title: str | Var | None = None,
|
|
473
479
|
description: str | Var | None = None,
|
|
@@ -490,17 +496,33 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
490
496
|
meta: The metadata of the page.
|
|
491
497
|
|
|
492
498
|
Raises:
|
|
493
|
-
|
|
499
|
+
PageValueError: When the component is not set for a non-404 page.
|
|
500
|
+
RouteValueError: When the specified route name already exists.
|
|
494
501
|
"""
|
|
495
502
|
# If the route is not set, get it from the callable.
|
|
496
503
|
if route is None:
|
|
497
504
|
if not isinstance(component, Callable):
|
|
498
|
-
raise
|
|
505
|
+
raise exceptions.RouteValueError(
|
|
506
|
+
"Route must be set if component is not a callable."
|
|
507
|
+
)
|
|
499
508
|
# Format the route.
|
|
500
509
|
route = format.format_route(component.__name__)
|
|
501
510
|
else:
|
|
502
511
|
route = format.format_route(route, format_case=False)
|
|
503
512
|
|
|
513
|
+
if route == constants.Page404.SLUG:
|
|
514
|
+
if component is None:
|
|
515
|
+
component = Default404Page.create()
|
|
516
|
+
component = wait_for_client_redirect(self._generate_component(component))
|
|
517
|
+
title = title or constants.Page404.TITLE
|
|
518
|
+
description = description or constants.Page404.DESCRIPTION
|
|
519
|
+
image = image or constants.Page404.IMAGE
|
|
520
|
+
else:
|
|
521
|
+
if component is None:
|
|
522
|
+
raise exceptions.PageValueError(
|
|
523
|
+
"Component must be set for a non-404 page."
|
|
524
|
+
)
|
|
525
|
+
|
|
504
526
|
# Check if the route given is valid
|
|
505
527
|
verify_route_validity(route)
|
|
506
528
|
|
|
@@ -516,7 +538,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
516
538
|
if route == constants.PageNames.INDEX_ROUTE
|
|
517
539
|
else f"`{route}`"
|
|
518
540
|
)
|
|
519
|
-
raise
|
|
541
|
+
raise exceptions.RouteValueError(
|
|
520
542
|
f"Duplicate page route {route_name} already exists. Make sure you do not have two"
|
|
521
543
|
f" pages with the same route"
|
|
522
544
|
)
|
|
@@ -633,10 +655,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
633
655
|
on_load: The event handler(s) that will be called each time the page load.
|
|
634
656
|
meta: The metadata of the page.
|
|
635
657
|
"""
|
|
636
|
-
|
|
637
|
-
|
|
658
|
+
console.deprecate(
|
|
659
|
+
feature_name="App.add_custom_404_page",
|
|
660
|
+
reason=f"Use app.add_page(component, route='/{constants.Page404.SLUG}') instead.",
|
|
661
|
+
deprecation_version="0.6.7",
|
|
662
|
+
removal_version="0.8.0",
|
|
663
|
+
)
|
|
638
664
|
self.add_page(
|
|
639
|
-
component=
|
|
665
|
+
component=component,
|
|
640
666
|
route=constants.Page404.SLUG,
|
|
641
667
|
title=title or constants.Page404.TITLE,
|
|
642
668
|
image=image or constants.Page404.IMAGE,
|
|
@@ -837,7 +863,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
837
863
|
|
|
838
864
|
# Render a default 404 page if the user didn't supply one
|
|
839
865
|
if constants.Page404.SLUG not in self.unevaluated_pages:
|
|
840
|
-
self.
|
|
866
|
+
self.add_page(route=constants.Page404.SLUG)
|
|
841
867
|
|
|
842
868
|
# Fix up the style.
|
|
843
869
|
self.style = evaluate_style_namespaces(self.style)
|
|
@@ -947,12 +973,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
947
973
|
is not None
|
|
948
974
|
):
|
|
949
975
|
executor = concurrent.futures.ProcessPoolExecutor(
|
|
950
|
-
max_workers=number_of_processes,
|
|
976
|
+
max_workers=number_of_processes or None,
|
|
951
977
|
mp_context=multiprocessing.get_context("fork"),
|
|
952
978
|
)
|
|
953
979
|
else:
|
|
954
980
|
executor = concurrent.futures.ThreadPoolExecutor(
|
|
955
|
-
max_workers=environment.REFLEX_COMPILE_THREADS.get()
|
|
981
|
+
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
|
956
982
|
)
|
|
957
983
|
|
|
958
984
|
for route, component in zip(self.pages, page_components):
|
|
@@ -965,7 +991,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
965
991
|
|
|
966
992
|
def _submit_work(fn, *args, **kwargs):
|
|
967
993
|
f = executor.submit(fn, *args, **kwargs)
|
|
968
|
-
# f = executor.apipe(fn, *args, **kwargs)
|
|
969
994
|
result_futures.append(f)
|
|
970
995
|
|
|
971
996
|
# Compile the pre-compiled pages.
|
|
@@ -1157,7 +1182,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1157
1182
|
if hasattr(handler_fn, "__name__"):
|
|
1158
1183
|
_fn_name = handler_fn.__name__
|
|
1159
1184
|
else:
|
|
1160
|
-
_fn_name = handler_fn.
|
|
1185
|
+
_fn_name = type(handler_fn).__name__
|
|
1161
1186
|
|
|
1162
1187
|
if isinstance(handler_fn, functools.partial):
|
|
1163
1188
|
raise ValueError(
|
|
@@ -1270,7 +1295,7 @@ async def process(
|
|
|
1270
1295
|
await asyncio.create_task(
|
|
1271
1296
|
app.event_namespace.emit(
|
|
1272
1297
|
"reload",
|
|
1273
|
-
data=
|
|
1298
|
+
data=event,
|
|
1274
1299
|
to=sid,
|
|
1275
1300
|
)
|
|
1276
1301
|
)
|
|
@@ -1523,7 +1548,7 @@ class EventNamespace(AsyncNamespace):
|
|
|
1523
1548
|
"""
|
|
1524
1549
|
# Creating a task prevents the update from being blocked behind other coroutines.
|
|
1525
1550
|
await asyncio.create_task(
|
|
1526
|
-
self.emit(str(constants.SocketEvent.EVENT), update
|
|
1551
|
+
self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
|
|
1527
1552
|
)
|
|
1528
1553
|
|
|
1529
1554
|
async def on_event(self, sid, data):
|
|
@@ -1536,7 +1561,7 @@ class EventNamespace(AsyncNamespace):
|
|
|
1536
1561
|
sid: The Socket.IO session id.
|
|
1537
1562
|
data: The event data.
|
|
1538
1563
|
"""
|
|
1539
|
-
fields =
|
|
1564
|
+
fields = data
|
|
1540
1565
|
# Get the event.
|
|
1541
1566
|
event = Event(
|
|
1542
1567
|
**{k: v for k, v in fields.items() if k not in ("handler", "event_actions")}
|
reflex/assets.py
CHANGED
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
from reflex import constants
|
|
8
|
-
from reflex.
|
|
8
|
+
from reflex.config import EnvironmentVariables
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def asset(
|
|
@@ -52,7 +52,7 @@ def asset(
|
|
|
52
52
|
The relative URL to the asset.
|
|
53
53
|
"""
|
|
54
54
|
assets = constants.Dirs.APP_ASSETS
|
|
55
|
-
backend_only =
|
|
55
|
+
backend_only = EnvironmentVariables.REFLEX_BACKEND_ONLY.get()
|
|
56
56
|
|
|
57
57
|
# Local asset handling
|
|
58
58
|
if not shared:
|
reflex/base.py
CHANGED
|
@@ -30,15 +30,16 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
|
|
|
30
30
|
|
|
31
31
|
# can't use reflex.config.environment here cause of circular import
|
|
32
32
|
reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true"
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
base = None
|
|
34
|
+
try:
|
|
35
|
+
for base in bases:
|
|
35
36
|
if not reload and getattr(base, field_name, None):
|
|
36
37
|
pass
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
except TypeError as te:
|
|
39
|
+
raise VarNameError(
|
|
40
|
+
f'State var "{field_name}" in {base} has been shadowed by a substate var; '
|
|
41
|
+
f'use a different field name instead".'
|
|
42
|
+
) from te
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
# monkeypatch pydantic validate_field_name method to skip validating
|
reflex/compiler/templates.py
CHANGED
|
@@ -45,6 +45,7 @@ class ReflexJinjaEnvironment(Environment):
|
|
|
45
45
|
"on_load_internal": constants.CompileVars.ON_LOAD_INTERNAL,
|
|
46
46
|
"update_vars_internal": constants.CompileVars.UPDATE_VARS_INTERNAL,
|
|
47
47
|
"frontend_exception_state": constants.CompileVars.FRONTEND_EXCEPTION_STATE_FULL,
|
|
48
|
+
"hook_position": constants.Hooks.HookPosition,
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
|
reflex/compiler/utils.py
CHANGED
|
@@ -115,7 +115,7 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
|
|
115
115
|
default, rest = compile_import_statement(fields)
|
|
116
116
|
|
|
117
117
|
# prevent lib from being rendered on the page if all imports are non rendered kind
|
|
118
|
-
if not any(
|
|
118
|
+
if not any(f.render for f in fields): # type: ignore
|
|
119
119
|
continue
|
|
120
120
|
|
|
121
121
|
if not lib:
|
|
@@ -123,8 +123,7 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
|
|
123
123
|
raise ValueError("No default field allowed for empty library.")
|
|
124
124
|
if rest is None or len(rest) == 0:
|
|
125
125
|
raise ValueError("No fields to import.")
|
|
126
|
-
for module in sorted(rest)
|
|
127
|
-
import_dicts.append(get_import_dict(module))
|
|
126
|
+
import_dicts.extend(get_import_dict(module) for module in sorted(rest))
|
|
128
127
|
continue
|
|
129
128
|
|
|
130
129
|
# remove the version before rendering the package imports
|
reflex/components/base/bare.py
CHANGED
|
@@ -103,8 +103,8 @@ class Bare(Component):
|
|
|
103
103
|
def _render(self) -> Tag:
|
|
104
104
|
if isinstance(self.contents, Var):
|
|
105
105
|
if isinstance(self.contents, (BooleanVar, ObjectVar)):
|
|
106
|
-
return Tagless(contents=f"{{{
|
|
107
|
-
return Tagless(contents=f"{{{
|
|
106
|
+
return Tagless(contents=f"{{{self.contents.to_string()!s}}}")
|
|
107
|
+
return Tagless(contents=f"{{{self.contents!s}}}")
|
|
108
108
|
return Tagless(contents=str(self.contents))
|
|
109
109
|
|
|
110
110
|
def _get_vars(self, include_children: bool = False) -> Iterator[Var]:
|
reflex/components/component.py
CHANGED
|
@@ -161,7 +161,7 @@ class ComponentNamespace(SimpleNamespace):
|
|
|
161
161
|
Returns:
|
|
162
162
|
The hash of the namespace.
|
|
163
163
|
"""
|
|
164
|
-
return hash(self.
|
|
164
|
+
return hash(type(self).__name__)
|
|
165
165
|
|
|
166
166
|
|
|
167
167
|
def evaluate_style_namespaces(style: ComponentStyle) -> dict:
|
|
@@ -583,7 +583,7 @@ class Component(BaseComponent, ABC):
|
|
|
583
583
|
return self._create_event_chain(args_spec, value.guess_type(), key=key)
|
|
584
584
|
else:
|
|
585
585
|
raise ValueError(
|
|
586
|
-
f"Invalid event chain: {
|
|
586
|
+
f"Invalid event chain: {value!s} of type {value._var_type}"
|
|
587
587
|
)
|
|
588
588
|
elif isinstance(value, EventChain):
|
|
589
589
|
# Trust that the caller knows what they're doing passing an EventChain directly
|
|
@@ -653,7 +653,6 @@ class Component(BaseComponent, ABC):
|
|
|
653
653
|
|
|
654
654
|
Returns:
|
|
655
655
|
The event triggers.
|
|
656
|
-
|
|
657
656
|
"""
|
|
658
657
|
default_triggers: Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]] = {
|
|
659
658
|
EventTriggers.ON_FOCUS: no_args_event_spec,
|
|
@@ -1111,7 +1110,7 @@ class Component(BaseComponent, ABC):
|
|
|
1111
1110
|
vars.append(prop_var)
|
|
1112
1111
|
|
|
1113
1112
|
# Style keeps track of its own VarData instance, so embed in a temp Var that is yielded.
|
|
1114
|
-
if isinstance(self.style, dict) and self.style or isinstance(self.style, Var):
|
|
1113
|
+
if (isinstance(self.style, dict) and self.style) or isinstance(self.style, Var):
|
|
1115
1114
|
vars.append(
|
|
1116
1115
|
Var(
|
|
1117
1116
|
_js_expr="style",
|
|
@@ -1209,7 +1208,7 @@ class Component(BaseComponent, ABC):
|
|
|
1209
1208
|
Yields:
|
|
1210
1209
|
The parent classes that define the method (differently than the base).
|
|
1211
1210
|
"""
|
|
1212
|
-
seen_methods =
|
|
1211
|
+
seen_methods = {getattr(Component, method)}
|
|
1213
1212
|
for clz in cls.mro():
|
|
1214
1213
|
if clz is Component:
|
|
1215
1214
|
break
|
|
@@ -1369,7 +1368,9 @@ class Component(BaseComponent, ABC):
|
|
|
1369
1368
|
if user_hooks_data is not None:
|
|
1370
1369
|
other_imports.append(user_hooks_data.imports)
|
|
1371
1370
|
other_imports.extend(
|
|
1372
|
-
|
|
1371
|
+
hook_vardata.imports
|
|
1372
|
+
for hook_vardata in self._get_added_hooks().values()
|
|
1373
|
+
if hook_vardata is not None
|
|
1373
1374
|
)
|
|
1374
1375
|
|
|
1375
1376
|
return imports.merge_imports(_imports, *other_imports)
|
|
@@ -1391,15 +1392,9 @@ class Component(BaseComponent, ABC):
|
|
|
1391
1392
|
|
|
1392
1393
|
# Collect imports from Vars used directly by this component.
|
|
1393
1394
|
var_datas = [var._get_all_var_data() for var in self._get_vars()]
|
|
1394
|
-
var_imports: List[ImmutableParsedImportDict] =
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
filter(
|
|
1398
|
-
None,
|
|
1399
|
-
var_datas,
|
|
1400
|
-
),
|
|
1401
|
-
)
|
|
1402
|
-
)
|
|
1395
|
+
var_imports: List[ImmutableParsedImportDict] = [
|
|
1396
|
+
var_data.imports for var_data in var_datas if var_data is not None
|
|
1397
|
+
]
|
|
1403
1398
|
|
|
1404
1399
|
added_import_dicts: list[ParsedImportDict] = []
|
|
1405
1400
|
for clz in self._iter_parent_classes_with_method("add_imports"):
|
|
@@ -1408,8 +1403,9 @@ class Component(BaseComponent, ABC):
|
|
|
1408
1403
|
if not isinstance(list_of_import_dict, list):
|
|
1409
1404
|
list_of_import_dict = [list_of_import_dict]
|
|
1410
1405
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1406
|
+
added_import_dicts.extend(
|
|
1407
|
+
[parse_imports(import_dict) for import_dict in list_of_import_dict]
|
|
1408
|
+
)
|
|
1413
1409
|
|
|
1414
1410
|
return imports.merge_imports(
|
|
1415
1411
|
*self._get_props_imports(),
|
|
@@ -1466,7 +1462,9 @@ class Component(BaseComponent, ABC):
|
|
|
1466
1462
|
"""
|
|
1467
1463
|
ref = self.get_ref()
|
|
1468
1464
|
if ref is not None:
|
|
1469
|
-
return
|
|
1465
|
+
return (
|
|
1466
|
+
f"const {ref} = useRef(null); {Var(_js_expr=ref)._as_ref()!s} = {ref};"
|
|
1467
|
+
)
|
|
1470
1468
|
|
|
1471
1469
|
def _get_vars_hooks(self) -> dict[str, None]:
|
|
1472
1470
|
"""Get the hooks required by vars referenced in this component.
|
|
@@ -1521,7 +1519,7 @@ class Component(BaseComponent, ABC):
|
|
|
1521
1519
|
**self._get_special_hooks(),
|
|
1522
1520
|
}
|
|
1523
1521
|
|
|
1524
|
-
def _get_added_hooks(self) -> dict[str,
|
|
1522
|
+
def _get_added_hooks(self) -> dict[str, VarData | None]:
|
|
1525
1523
|
"""Get the hooks added via `add_hooks` method.
|
|
1526
1524
|
|
|
1527
1525
|
Returns:
|
|
@@ -1530,17 +1528,15 @@ class Component(BaseComponent, ABC):
|
|
|
1530
1528
|
code = {}
|
|
1531
1529
|
|
|
1532
1530
|
def extract_var_hooks(hook: Var):
|
|
1533
|
-
_imports = {}
|
|
1534
1531
|
var_data = VarData.merge(hook._get_all_var_data())
|
|
1535
1532
|
if var_data is not None:
|
|
1536
1533
|
for sub_hook in var_data.hooks:
|
|
1537
|
-
code[sub_hook] =
|
|
1538
|
-
|
|
1539
|
-
_imports = var_data.imports
|
|
1534
|
+
code[sub_hook] = None
|
|
1535
|
+
|
|
1540
1536
|
if str(hook) in code:
|
|
1541
|
-
code[str(hook)] =
|
|
1537
|
+
code[str(hook)] = VarData.merge(var_data, code[str(hook)])
|
|
1542
1538
|
else:
|
|
1543
|
-
code[str(hook)] =
|
|
1539
|
+
code[str(hook)] = var_data
|
|
1544
1540
|
|
|
1545
1541
|
# Add the hook code from add_hooks for each parent class (this is reversed to preserve
|
|
1546
1542
|
# the order of the hooks in the final output)
|
|
@@ -1549,7 +1545,7 @@ class Component(BaseComponent, ABC):
|
|
|
1549
1545
|
if isinstance(hook, Var):
|
|
1550
1546
|
extract_var_hooks(hook)
|
|
1551
1547
|
else:
|
|
1552
|
-
code[hook] =
|
|
1548
|
+
code[hook] = None
|
|
1553
1549
|
|
|
1554
1550
|
return code
|
|
1555
1551
|
|
|
@@ -1591,8 +1587,7 @@ class Component(BaseComponent, ABC):
|
|
|
1591
1587
|
if hooks is not None:
|
|
1592
1588
|
code[hooks] = None
|
|
1593
1589
|
|
|
1594
|
-
|
|
1595
|
-
code[hook] = None
|
|
1590
|
+
code.update(self._get_added_hooks())
|
|
1596
1591
|
|
|
1597
1592
|
# Add the hook code for the children.
|
|
1598
1593
|
for child in self.children:
|
|
@@ -2194,6 +2189,31 @@ class StatefulComponent(BaseComponent):
|
|
|
2194
2189
|
]
|
|
2195
2190
|
return [var_name]
|
|
2196
2191
|
|
|
2192
|
+
@staticmethod
|
|
2193
|
+
def _get_deps_from_event_trigger(event: EventChain | EventSpec | Var) -> set[str]:
|
|
2194
|
+
"""Get the dependencies accessed by event triggers.
|
|
2195
|
+
|
|
2196
|
+
Args:
|
|
2197
|
+
event: The event trigger to extract deps from.
|
|
2198
|
+
|
|
2199
|
+
Returns:
|
|
2200
|
+
The dependencies accessed by the event triggers.
|
|
2201
|
+
"""
|
|
2202
|
+
events: list = [event]
|
|
2203
|
+
deps = set()
|
|
2204
|
+
|
|
2205
|
+
if isinstance(event, EventChain):
|
|
2206
|
+
events.extend(event.events)
|
|
2207
|
+
|
|
2208
|
+
for ev in events:
|
|
2209
|
+
if isinstance(ev, EventSpec):
|
|
2210
|
+
for arg in ev.args:
|
|
2211
|
+
for a in arg:
|
|
2212
|
+
var_datas = VarData.merge(a._get_all_var_data())
|
|
2213
|
+
if var_datas and var_datas.deps is not None:
|
|
2214
|
+
deps |= {str(dep) for dep in var_datas.deps}
|
|
2215
|
+
return deps
|
|
2216
|
+
|
|
2197
2217
|
@classmethod
|
|
2198
2218
|
def _get_memoized_event_triggers(
|
|
2199
2219
|
cls,
|
|
@@ -2230,6 +2250,11 @@ class StatefulComponent(BaseComponent):
|
|
|
2230
2250
|
|
|
2231
2251
|
# Calculate Var dependencies accessed by the handler for useCallback dep array.
|
|
2232
2252
|
var_deps = ["addEvents", "Event"]
|
|
2253
|
+
|
|
2254
|
+
# Get deps from event trigger var data.
|
|
2255
|
+
var_deps.extend(cls._get_deps_from_event_trigger(event))
|
|
2256
|
+
|
|
2257
|
+
# Get deps from hooks.
|
|
2233
2258
|
for arg in event_args:
|
|
2234
2259
|
var_data = arg._get_all_var_data()
|
|
2235
2260
|
if var_data is None:
|
|
@@ -2563,7 +2588,7 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
|
|
2563
2588
|
Returns:
|
|
2564
2589
|
The hash of the var.
|
|
2565
2590
|
"""
|
|
2566
|
-
return hash((self.
|
|
2591
|
+
return hash((type(self).__name__, self._js_expr))
|
|
2567
2592
|
|
|
2568
2593
|
@classmethod
|
|
2569
2594
|
def create(
|
reflex/components/core/banner.py
CHANGED
|
@@ -109,7 +109,7 @@ class ConnectionToaster(Toaster):
|
|
|
109
109
|
)
|
|
110
110
|
|
|
111
111
|
individual_hooks = [
|
|
112
|
-
f"const toast_props = {
|
|
112
|
+
f"const toast_props = {LiteralVar.create(props)!s};",
|
|
113
113
|
"const [userDismissed, setUserDismissed] = useState(false);",
|
|
114
114
|
FunctionStringVar(
|
|
115
115
|
"useEffect",
|
|
@@ -124,7 +124,7 @@ class ConnectionToaster(Toaster):
|
|
|
124
124
|
Var(
|
|
125
125
|
_js_expr=f"""
|
|
126
126
|
() => {{
|
|
127
|
-
if ({
|
|
127
|
+
if ({has_too_many_connection_errors!s}) {{
|
|
128
128
|
if (!userDismissed) {{
|
|
129
129
|
toast.error(
|
|
130
130
|
`Cannot connect to server: ${{{connection_error}}}.`,
|
|
@@ -321,7 +321,7 @@ class ConnectionPulser(Div):
|
|
|
321
321
|
"""Create a connection pulser component.
|
|
322
322
|
|
|
323
323
|
Args:
|
|
324
|
-
access_key:
|
|
324
|
+
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
|
325
325
|
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
|
326
326
|
content_editable: Indicates whether the element's content is editable.
|
|
327
327
|
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
|
@@ -24,7 +24,7 @@ class ClientSideRouting(Component):
|
|
|
24
24
|
library = "$/utils/client_side_routing"
|
|
25
25
|
tag = "useClientSideRouting"
|
|
26
26
|
|
|
27
|
-
def add_hooks(self) -> list[str]:
|
|
27
|
+
def add_hooks(self) -> list[str | Var]:
|
|
28
28
|
"""Get the hooks to render.
|
|
29
29
|
|
|
30
30
|
Returns:
|
|
@@ -66,4 +66,4 @@ class Default404Page(Component):
|
|
|
66
66
|
tag = "Error"
|
|
67
67
|
is_default = True
|
|
68
68
|
|
|
69
|
-
status_code: Var[int] = 404
|
|
69
|
+
status_code: Var[int] = Var.create(404)
|