reflex 0.7.14a5__py3-none-any.whl → 0.8.0__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/app/rxconfig.py.jinja2 +4 -1
- reflex/.templates/jinja/web/package.json.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +21 -11
- reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
- reflex/.templates/jinja/web/styles/styles.css.jinja2 +1 -0
- reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
- reflex/.templates/web/app/entry.client.js +8 -0
- reflex/.templates/web/app/routes.js +10 -0
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
- reflex/.templates/web/postcss.config.js +1 -1
- reflex/.templates/web/react-router.config.js +6 -0
- reflex/.templates/web/styles/__reflex_style_reset.css +399 -0
- reflex/.templates/web/utils/client_side_routing.js +21 -19
- reflex/.templates/web/utils/react-theme.js +92 -0
- reflex/.templates/web/utils/state.js +251 -100
- reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
- reflex/.templates/web/vite.config.js +39 -0
- reflex/__init__.py +1 -6
- reflex/__init__.pyi +327 -192
- reflex/app.py +103 -152
- reflex/base.py +1 -87
- reflex/compiler/compiler.py +70 -19
- reflex/compiler/templates.py +3 -3
- reflex/compiler/utils.py +91 -33
- reflex/components/__init__.py +0 -2
- reflex/components/__init__.pyi +34 -18
- reflex/components/base/__init__.py +1 -5
- reflex/components/base/__init__.pyi +30 -21
- reflex/components/base/app_wrap.pyi +7 -7
- reflex/components/base/body.pyi +7 -7
- reflex/components/base/document.py +18 -14
- reflex/components/base/document.pyi +88 -38
- reflex/components/base/error_boundary.pyi +7 -7
- reflex/components/base/fragment.pyi +7 -7
- reflex/components/base/link.pyi +12 -12
- reflex/components/base/meta.py +4 -15
- reflex/components/base/meta.pyi +31 -31
- reflex/components/base/script.py +60 -58
- reflex/components/base/script.pyi +248 -34
- reflex/components/base/strict_mode.pyi +7 -7
- reflex/components/component.py +146 -217
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +77 -37
- reflex/components/core/auto_scroll.pyi +7 -7
- reflex/components/core/banner.pyi +33 -33
- reflex/components/core/client_side_routing.py +7 -6
- reflex/components/core/client_side_routing.pyi +8 -59
- reflex/components/core/clipboard.pyi +7 -7
- reflex/components/core/debounce.py +1 -0
- reflex/components/core/debounce.pyi +7 -7
- reflex/components/core/foreach.py +5 -4
- reflex/components/core/helmet.py +14 -0
- reflex/components/{next/base.pyi → core/helmet.pyi} +12 -10
- reflex/components/core/html.pyi +7 -7
- reflex/components/core/match.py +3 -3
- reflex/components/core/sticky.pyi +21 -20
- reflex/components/core/upload.py +4 -2
- reflex/components/core/upload.pyi +26 -25
- reflex/components/datadisplay/__init__.pyi +13 -7
- reflex/components/datadisplay/code.py +14 -79
- reflex/components/datadisplay/code.pyi +11 -13
- reflex/components/datadisplay/dataeditor.pyi +38 -15
- reflex/components/datadisplay/shiki_code_block.py +5 -3
- reflex/components/datadisplay/shiki_code_block.pyi +16 -15
- reflex/components/dynamic.py +5 -5
- reflex/components/el/__init__.pyi +506 -246
- reflex/components/el/element.pyi +7 -7
- reflex/components/el/elements/__init__.pyi +504 -245
- reflex/components/el/elements/base.pyi +7 -7
- reflex/components/el/elements/forms.pyi +146 -101
- reflex/components/el/elements/inline.pyi +142 -142
- reflex/components/el/elements/media.pyi +131 -130
- reflex/components/el/elements/metadata.pyi +32 -32
- reflex/components/el/elements/other.pyi +37 -37
- reflex/components/el/elements/scripts.pyi +17 -17
- reflex/components/el/elements/sectioning.pyi +77 -77
- reflex/components/el/elements/tables.pyi +52 -52
- reflex/components/el/elements/typography.pyi +77 -77
- reflex/components/field.py +175 -0
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +14 -14
- reflex/components/lucide/icon.py +6 -2
- reflex/components/lucide/icon.pyi +19 -17
- reflex/components/markdown/markdown.py +5 -3
- reflex/components/markdown/markdown.pyi +7 -7
- reflex/components/moment/moment.py +1 -1
- reflex/components/moment/moment.pyi +7 -7
- reflex/components/plotly/plotly.py +12 -6
- reflex/components/plotly/plotly.pyi +50 -49
- reflex/components/props.py +376 -27
- reflex/components/radix/__init__.pyi +123 -65
- reflex/components/radix/primitives/__init__.pyi +6 -4
- reflex/components/radix/primitives/accordion.py +8 -1
- reflex/components/radix/primitives/accordion.pyi +37 -37
- reflex/components/radix/primitives/base.pyi +12 -12
- reflex/components/radix/primitives/drawer.pyi +56 -55
- reflex/components/radix/primitives/form.pyi +63 -53
- reflex/components/radix/primitives/progress.pyi +26 -25
- reflex/components/radix/primitives/slider.pyi +27 -27
- reflex/components/radix/themes/__init__.pyi +5 -6
- reflex/components/radix/themes/base.py +3 -3
- reflex/components/radix/themes/base.pyi +42 -42
- reflex/components/radix/themes/color_mode.py +5 -6
- reflex/components/radix/themes/color_mode.pyi +17 -17
- reflex/components/radix/themes/components/__init__.pyi +75 -38
- reflex/components/radix/themes/components/alert_dialog.pyi +37 -37
- reflex/components/radix/themes/components/aspect_ratio.pyi +7 -7
- reflex/components/radix/themes/components/avatar.pyi +7 -7
- reflex/components/radix/themes/components/badge.pyi +7 -7
- reflex/components/radix/themes/components/button.pyi +7 -7
- reflex/components/radix/themes/components/callout.pyi +26 -25
- reflex/components/radix/themes/components/card.pyi +7 -7
- reflex/components/radix/themes/components/checkbox.pyi +16 -15
- reflex/components/radix/themes/components/checkbox_cards.pyi +12 -12
- reflex/components/radix/themes/components/checkbox_group.pyi +12 -12
- reflex/components/radix/themes/components/context_menu.pyi +67 -67
- reflex/components/radix/themes/components/data_list.pyi +22 -22
- reflex/components/radix/themes/components/dialog.pyi +36 -35
- reflex/components/radix/themes/components/dropdown_menu.pyi +42 -42
- reflex/components/radix/themes/components/hover_card.pyi +21 -20
- reflex/components/radix/themes/components/icon_button.pyi +7 -7
- reflex/components/radix/themes/components/inset.pyi +7 -7
- reflex/components/radix/themes/components/popover.pyi +22 -22
- reflex/components/radix/themes/components/progress.pyi +7 -7
- reflex/components/radix/themes/components/radio.pyi +7 -7
- reflex/components/radix/themes/components/radio_cards.pyi +12 -12
- reflex/components/radix/themes/components/radio_group.pyi +21 -20
- reflex/components/radix/themes/components/scroll_area.pyi +7 -7
- reflex/components/radix/themes/components/segmented_control.pyi +12 -12
- reflex/components/radix/themes/components/select.pyi +46 -45
- reflex/components/radix/themes/components/separator.pyi +7 -7
- reflex/components/radix/themes/components/skeleton.pyi +7 -7
- reflex/components/radix/themes/components/slider.pyi +17 -9
- reflex/components/radix/themes/components/spinner.pyi +7 -7
- reflex/components/radix/themes/components/switch.pyi +7 -7
- reflex/components/radix/themes/components/table.pyi +37 -37
- reflex/components/radix/themes/components/tabs.pyi +26 -25
- reflex/components/radix/themes/components/text_area.pyi +15 -9
- reflex/components/radix/themes/components/text_field.pyi +32 -19
- reflex/components/radix/themes/components/tooltip.pyi +7 -7
- reflex/components/radix/themes/layout/__init__.pyi +27 -14
- reflex/components/radix/themes/layout/base.pyi +7 -7
- reflex/components/radix/themes/layout/box.pyi +7 -7
- reflex/components/radix/themes/layout/center.pyi +7 -7
- reflex/components/radix/themes/layout/container.pyi +7 -7
- reflex/components/radix/themes/layout/flex.pyi +7 -7
- reflex/components/radix/themes/layout/grid.pyi +7 -7
- reflex/components/radix/themes/layout/list.pyi +26 -25
- reflex/components/radix/themes/layout/section.pyi +7 -7
- reflex/components/radix/themes/layout/spacer.pyi +7 -7
- reflex/components/radix/themes/layout/stack.pyi +17 -17
- reflex/components/radix/themes/typography/__init__.pyi +7 -5
- reflex/components/radix/themes/typography/blockquote.pyi +7 -7
- reflex/components/radix/themes/typography/code.pyi +7 -7
- reflex/components/radix/themes/typography/heading.pyi +7 -7
- reflex/components/radix/themes/typography/link.py +46 -11
- reflex/components/radix/themes/typography/link.pyi +312 -9
- reflex/components/radix/themes/typography/text.pyi +36 -35
- reflex/components/react_player/audio.pyi +10 -8
- reflex/components/react_player/react_player.pyi +7 -7
- reflex/components/react_player/video.pyi +10 -8
- reflex/components/recharts/__init__.pyi +208 -100
- reflex/components/recharts/cartesian.py +10 -8
- reflex/components/recharts/cartesian.pyi +90 -94
- reflex/components/recharts/charts.py +4 -2
- reflex/components/recharts/charts.pyi +49 -49
- reflex/components/recharts/general.pyi +31 -31
- reflex/components/recharts/polar.py +8 -4
- reflex/components/recharts/polar.pyi +23 -23
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/recharts/recharts.pyi +12 -12
- reflex/components/sonner/toast.py +3 -3
- reflex/components/sonner/toast.pyi +9 -9
- reflex/config.py +10 -113
- reflex/constants/__init__.py +2 -2
- reflex/constants/base.py +28 -11
- reflex/constants/compiler.py +12 -3
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +26 -20
- reflex/constants/route.py +27 -8
- reflex/constants/state.py +2 -0
- reflex/custom_components/custom_components.py +0 -14
- reflex/environment.py +77 -5
- reflex/event.py +178 -81
- reflex/experimental/__init__.py +0 -30
- reflex/istate/__init__.py +69 -0
- reflex/istate/manager.py +1 -0
- reflex/istate/proxy.py +5 -3
- reflex/page.py +0 -27
- reflex/plugins/__init__.py +3 -2
- reflex/plugins/base.py +5 -1
- reflex/plugins/shared_tailwind.py +215 -0
- reflex/plugins/sitemap.py +206 -0
- reflex/plugins/tailwind_v3.py +15 -108
- reflex/plugins/tailwind_v4.py +18 -110
- reflex/reflex.py +1 -0
- reflex/route.py +157 -75
- reflex/state.py +171 -155
- reflex/testing.py +86 -16
- reflex/utils/build.py +38 -82
- reflex/utils/exec.py +83 -175
- reflex/utils/export.py +2 -2
- reflex/utils/format.py +1 -5
- reflex/utils/imports.py +5 -16
- reflex/utils/misc.py +67 -0
- reflex/utils/prerequisites.py +66 -68
- reflex/utils/processes.py +24 -47
- reflex/utils/pyi_generator.py +44 -49
- reflex/utils/serializers.py +14 -1
- reflex/utils/telemetry.py +0 -15
- reflex/utils/types.py +197 -62
- reflex/vars/__init__.py +2 -0
- reflex/vars/base.py +367 -134
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/METADATA +15 -8
- reflex-0.8.0.dist-info/RECORD +403 -0
- reflex/.templates/web/next.config.js +0 -7
- reflex/components/base/head.py +0 -20
- reflex/components/base/head.pyi +0 -116
- reflex/components/next/__init__.py +0 -10
- reflex/components/next/base.py +0 -7
- reflex/components/next/image.py +0 -117
- reflex/components/next/image.pyi +0 -94
- reflex/components/next/link.py +0 -20
- reflex/components/next/link.pyi +0 -67
- reflex/components/next/video.py +0 -38
- reflex/components/next/video.pyi +0 -68
- reflex/components/suneditor/__init__.py +0 -5
- reflex/components/suneditor/editor.py +0 -269
- reflex/components/suneditor/editor.pyi +0 -199
- reflex/experimental/layout.py +0 -254
- reflex-0.7.14a5.dist-info/RECORD +0 -407
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.14a5.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
reflex/app.py
CHANGED
|
@@ -13,7 +13,14 @@ import io
|
|
|
13
13
|
import json
|
|
14
14
|
import sys
|
|
15
15
|
import traceback
|
|
16
|
-
from collections.abc import
|
|
16
|
+
from collections.abc import (
|
|
17
|
+
AsyncGenerator,
|
|
18
|
+
AsyncIterator,
|
|
19
|
+
Callable,
|
|
20
|
+
Coroutine,
|
|
21
|
+
Mapping,
|
|
22
|
+
Sequence,
|
|
23
|
+
)
|
|
17
24
|
from datetime import datetime
|
|
18
25
|
from pathlib import Path
|
|
19
26
|
from timeit import default_timer as timer
|
|
@@ -32,7 +39,6 @@ from starlette.middleware import cors
|
|
|
32
39
|
from starlette.requests import ClientDisconnect, Request
|
|
33
40
|
from starlette.responses import JSONResponse, Response, StreamingResponse
|
|
34
41
|
from starlette.staticfiles import StaticFiles
|
|
35
|
-
from typing_extensions import deprecated
|
|
36
42
|
|
|
37
43
|
from reflex import constants
|
|
38
44
|
from reflex.admin import AdminDash
|
|
@@ -61,7 +67,7 @@ from reflex.components.core.banner import (
|
|
|
61
67
|
)
|
|
62
68
|
from reflex.components.core.breakpoints import set_breakpoints
|
|
63
69
|
from reflex.components.core.client_side_routing import (
|
|
64
|
-
|
|
70
|
+
default_404_page,
|
|
65
71
|
wait_for_client_redirect,
|
|
66
72
|
)
|
|
67
73
|
from reflex.components.core.sticky import sticky
|
|
@@ -78,6 +84,7 @@ from reflex.event import (
|
|
|
78
84
|
EventType,
|
|
79
85
|
IndividualEventType,
|
|
80
86
|
get_hydrate_event,
|
|
87
|
+
noop,
|
|
81
88
|
)
|
|
82
89
|
from reflex.model import Model, get_db_status
|
|
83
90
|
from reflex.page import DECORATED_PAGES
|
|
@@ -214,17 +221,21 @@ def default_overlay_component() -> Component:
|
|
|
214
221
|
return Fragment.create(memo(default_overlay_components)())
|
|
215
222
|
|
|
216
223
|
|
|
217
|
-
def default_error_boundary(*children: Component) -> Component:
|
|
224
|
+
def default_error_boundary(*children: Component, **props) -> Component:
|
|
218
225
|
"""Default error_boundary attribute for App.
|
|
219
226
|
|
|
220
227
|
Args:
|
|
221
228
|
*children: The children to render in the error boundary.
|
|
229
|
+
**props: The props to pass to the error boundary.
|
|
222
230
|
|
|
223
231
|
Returns:
|
|
224
232
|
The default error_boundary, which is an ErrorBoundary.
|
|
225
233
|
|
|
226
234
|
"""
|
|
227
|
-
return ErrorBoundary.create(
|
|
235
|
+
return ErrorBoundary.create(
|
|
236
|
+
*children,
|
|
237
|
+
**props,
|
|
238
|
+
)
|
|
228
239
|
|
|
229
240
|
|
|
230
241
|
class OverlayFragment(Fragment):
|
|
@@ -246,8 +257,6 @@ class UploadFile(StarletteUploadFile):
|
|
|
246
257
|
|
|
247
258
|
path: Path | None = dataclasses.field(default=None)
|
|
248
259
|
|
|
249
|
-
_deprecated_filename: str | None = dataclasses.field(default=None)
|
|
250
|
-
|
|
251
260
|
size: int | None = dataclasses.field(default=None)
|
|
252
261
|
|
|
253
262
|
headers: Headers = dataclasses.field(default_factory=Headers)
|
|
@@ -263,21 +272,6 @@ class UploadFile(StarletteUploadFile):
|
|
|
263
272
|
return self.path.name
|
|
264
273
|
return None
|
|
265
274
|
|
|
266
|
-
@property
|
|
267
|
-
def filename(self) -> str | None:
|
|
268
|
-
"""Get the filename of the uploaded file.
|
|
269
|
-
|
|
270
|
-
Returns:
|
|
271
|
-
The filename of the uploaded file.
|
|
272
|
-
"""
|
|
273
|
-
console.deprecate(
|
|
274
|
-
feature_name="UploadFile.filename",
|
|
275
|
-
reason="Use UploadFile.name instead.",
|
|
276
|
-
deprecation_version="0.7.1",
|
|
277
|
-
removal_version="0.8.0",
|
|
278
|
-
)
|
|
279
|
-
return self._deprecated_filename
|
|
280
|
-
|
|
281
275
|
|
|
282
276
|
@dataclasses.dataclass(
|
|
283
277
|
frozen=True,
|
|
@@ -291,8 +285,8 @@ class UnevaluatedPage:
|
|
|
291
285
|
description: Var | str | None
|
|
292
286
|
image: str
|
|
293
287
|
on_load: EventType[()] | None
|
|
294
|
-
meta:
|
|
295
|
-
context:
|
|
288
|
+
meta: Sequence[Mapping[str, str]]
|
|
289
|
+
context: Mapping[str, Any]
|
|
296
290
|
|
|
297
291
|
def merged_with(self, other: UnevaluatedPage) -> UnevaluatedPage:
|
|
298
292
|
"""Merge the other page into this one.
|
|
@@ -350,20 +344,22 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
350
344
|
# A list of URLs to [stylesheets](https://reflex.dev/docs/styling/custom-stylesheets/) to include in the app.
|
|
351
345
|
stylesheets: list[str] = dataclasses.field(default_factory=list)
|
|
352
346
|
|
|
347
|
+
# Whether to include CSS reset for margin and padding (defaults to True).
|
|
348
|
+
reset_style: bool = dataclasses.field(default=True)
|
|
349
|
+
|
|
353
350
|
# A component that is present on every page (defaults to the Connection Error banner).
|
|
354
351
|
overlay_component: Component | ComponentCallable | None = dataclasses.field(
|
|
355
352
|
default=None
|
|
356
353
|
)
|
|
357
354
|
|
|
358
|
-
# Error boundary component to wrap the app with.
|
|
359
|
-
error_boundary: ComponentCallable | None = dataclasses.field(default=None)
|
|
360
|
-
|
|
361
355
|
# App wraps to be applied to the whole app. Expected to be a dictionary of (order, name) to a function that takes whether the state is enabled and optionally returns a component.
|
|
362
356
|
app_wraps: dict[tuple[int, str], Callable[[bool], Component | None]] = (
|
|
363
357
|
dataclasses.field(
|
|
364
358
|
default_factory=lambda: {
|
|
365
359
|
(55, "ErrorBoundary"): (
|
|
366
|
-
lambda stateful: default_error_boundary(
|
|
360
|
+
lambda stateful: default_error_boundary(
|
|
361
|
+
**({"on_error": noop()} if not stateful else {})
|
|
362
|
+
)
|
|
367
363
|
),
|
|
368
364
|
(5, "Overlay"): (
|
|
369
365
|
lambda stateful: default_overlay_component() if stateful else None
|
|
@@ -443,24 +439,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
443
439
|
# FastAPI app for compatibility with FastAPI.
|
|
444
440
|
_cached_fastapi_app: FastAPI | None = None
|
|
445
441
|
|
|
446
|
-
@property
|
|
447
|
-
@deprecated("Use `api_transformer=your_fastapi_app` instead.")
|
|
448
|
-
def api(self) -> FastAPI:
|
|
449
|
-
"""Get the backend api.
|
|
450
|
-
|
|
451
|
-
Returns:
|
|
452
|
-
The backend api.
|
|
453
|
-
"""
|
|
454
|
-
if self._cached_fastapi_app is None:
|
|
455
|
-
self._cached_fastapi_app = FastAPI()
|
|
456
|
-
console.deprecate(
|
|
457
|
-
feature_name="App.api",
|
|
458
|
-
reason="Set `api_transformer=your_fastapi_app` instead.",
|
|
459
|
-
deprecation_version="0.7.9",
|
|
460
|
-
removal_version="0.8.0",
|
|
461
|
-
)
|
|
462
|
-
return self._cached_fastapi_app
|
|
463
|
-
|
|
464
442
|
@property
|
|
465
443
|
def event_namespace(self) -> EventNamespace | None:
|
|
466
444
|
"""Get the event namespace.
|
|
@@ -619,7 +597,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
619
597
|
self._apply_decorated_pages()
|
|
620
598
|
|
|
621
599
|
compile_future = concurrent.futures.ThreadPoolExecutor(max_workers=1).submit(
|
|
622
|
-
self._compile
|
|
600
|
+
self._compile, prerender_routes=is_prod_mode()
|
|
623
601
|
)
|
|
624
602
|
|
|
625
603
|
def callback(f: concurrent.futures.Future):
|
|
@@ -794,13 +772,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
794
772
|
msg = "Route must be set if component is not a callable."
|
|
795
773
|
raise exceptions.RouteValueError(msg)
|
|
796
774
|
# Format the route.
|
|
797
|
-
route = format.format_route(component.__name__)
|
|
775
|
+
route = format.format_route(format.to_kebab_case(component.__name__))
|
|
798
776
|
else:
|
|
799
|
-
route = format.format_route(route
|
|
777
|
+
route = format.format_route(route)
|
|
800
778
|
|
|
801
779
|
if route == constants.Page404.SLUG:
|
|
802
780
|
if component is None:
|
|
803
|
-
component =
|
|
781
|
+
component = default_404_page
|
|
804
782
|
component = wait_for_client_redirect(self._generate_component(component))
|
|
805
783
|
title = title or constants.Page404.TITLE
|
|
806
784
|
description = description or constants.Page404.DESCRIPTION
|
|
@@ -821,7 +799,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
821
799
|
image=image,
|
|
822
800
|
on_load=on_load,
|
|
823
801
|
meta=meta,
|
|
824
|
-
context=context,
|
|
802
|
+
context=context or {},
|
|
825
803
|
)
|
|
826
804
|
|
|
827
805
|
if route in self._unevaluated_pages:
|
|
@@ -851,10 +829,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
851
829
|
state = self._state if self._state else State
|
|
852
830
|
state.setup_dynamic_args(get_route_args(route))
|
|
853
831
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
832
|
+
self._load_events[route] = (
|
|
833
|
+
(on_load if isinstance(on_load, list) else [on_load])
|
|
834
|
+
if on_load is not None
|
|
835
|
+
else []
|
|
836
|
+
)
|
|
858
837
|
|
|
859
838
|
self._unevaluated_pages[route] = unevaluated_page
|
|
860
839
|
|
|
@@ -883,24 +862,40 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
883
862
|
if save_page:
|
|
884
863
|
self._pages[route] = component
|
|
885
864
|
|
|
886
|
-
|
|
865
|
+
@functools.cached_property
|
|
866
|
+
def router(self) -> Callable[[str], str | None]:
|
|
867
|
+
"""Get the route computer function.
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
The route computer function.
|
|
871
|
+
"""
|
|
872
|
+
from reflex.route import get_router
|
|
873
|
+
|
|
874
|
+
return get_router(list(self._unevaluated_pages))
|
|
875
|
+
|
|
876
|
+
def get_load_events(self, path: str) -> list[IndividualEventType[()]]:
|
|
887
877
|
"""Get the load events for a route.
|
|
888
878
|
|
|
889
879
|
Args:
|
|
890
|
-
|
|
880
|
+
path: The route to get the load events for.
|
|
891
881
|
|
|
892
882
|
Returns:
|
|
893
883
|
The load events for the route.
|
|
894
884
|
"""
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
885
|
+
four_oh_four_load_events = self._load_events.get("404", [])
|
|
886
|
+
route = self.router(path)
|
|
887
|
+
if not route:
|
|
888
|
+
# If the path is not a valid route, return the 404 page load events.
|
|
889
|
+
return four_oh_four_load_events
|
|
890
|
+
return self._load_events.get(
|
|
891
|
+
route,
|
|
892
|
+
four_oh_four_load_events,
|
|
893
|
+
)
|
|
899
894
|
|
|
900
895
|
def _check_routes_conflict(self, new_route: str):
|
|
901
896
|
"""Verify if there is any conflict between the new route and any existing route.
|
|
902
897
|
|
|
903
|
-
Based on conflicts that
|
|
898
|
+
Based on conflicts that React Router would throw if not intercepted.
|
|
904
899
|
|
|
905
900
|
Raises:
|
|
906
901
|
RouteValueError: exception showing which conflict exist with the route to be added
|
|
@@ -916,7 +911,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
916
911
|
segments = (
|
|
917
912
|
constants.RouteRegex.SINGLE_SEGMENT,
|
|
918
913
|
constants.RouteRegex.DOUBLE_SEGMENT,
|
|
919
|
-
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
|
920
914
|
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
|
921
915
|
)
|
|
922
916
|
for route in self._pages:
|
|
@@ -938,44 +932,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
938
932
|
# info1 will break away into its own tree.
|
|
939
933
|
break
|
|
940
934
|
|
|
941
|
-
def add_custom_404_page(
|
|
942
|
-
self,
|
|
943
|
-
component: Component | ComponentCallable | None = None,
|
|
944
|
-
title: str = constants.Page404.TITLE,
|
|
945
|
-
image: str = constants.Page404.IMAGE,
|
|
946
|
-
description: str = constants.Page404.DESCRIPTION,
|
|
947
|
-
on_load: EventType[()] | None = None,
|
|
948
|
-
meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
|
|
949
|
-
):
|
|
950
|
-
"""Define a custom 404 page for any url having no match.
|
|
951
|
-
|
|
952
|
-
If there is no page defined on 'index' route, add the 404 page to it.
|
|
953
|
-
If there is no global catchall defined, add the 404 page with a catchall.
|
|
954
|
-
|
|
955
|
-
Args:
|
|
956
|
-
component: The component to display at the page.
|
|
957
|
-
title: The title of the page.
|
|
958
|
-
image: The image to display on the page.
|
|
959
|
-
description: The description of the page.
|
|
960
|
-
on_load: The event handler(s) that will be called each time the page load.
|
|
961
|
-
meta: The metadata of the page.
|
|
962
|
-
"""
|
|
963
|
-
console.deprecate(
|
|
964
|
-
feature_name="App.add_custom_404_page",
|
|
965
|
-
reason=f"Use app.add_page(component, route='/{constants.Page404.SLUG}') instead.",
|
|
966
|
-
deprecation_version="0.6.7",
|
|
967
|
-
removal_version="0.8.0",
|
|
968
|
-
)
|
|
969
|
-
self.add_page(
|
|
970
|
-
component=component,
|
|
971
|
-
route=constants.Page404.SLUG,
|
|
972
|
-
title=title or constants.Page404.TITLE,
|
|
973
|
-
image=image or constants.Page404.IMAGE,
|
|
974
|
-
description=description or constants.Page404.DESCRIPTION,
|
|
975
|
-
on_load=on_load,
|
|
976
|
-
meta=meta,
|
|
977
|
-
)
|
|
978
|
-
|
|
979
935
|
def _setup_admin_dash(self):
|
|
980
936
|
"""Setup the admin dash."""
|
|
981
937
|
try:
|
|
@@ -1024,7 +980,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1024
980
|
for i, tags in imports.items()
|
|
1025
981
|
if i not in dependencies
|
|
1026
982
|
and i not in dev_dependencies
|
|
1027
|
-
and not any(i.startswith(prefix) for prefix in ["/", "$/", "."
|
|
983
|
+
and not any(i.startswith(prefix) for prefix in ["/", "$/", "."])
|
|
1028
984
|
and i != ""
|
|
1029
985
|
and any(tag.install for tag in tags)
|
|
1030
986
|
}
|
|
@@ -1148,17 +1104,17 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1148
1104
|
)
|
|
1149
1105
|
for dep in dep_set:
|
|
1150
1106
|
if dep not in state_cls.vars and dep not in state_cls.backend_vars:
|
|
1151
|
-
msg = f"ComputedVar {var.
|
|
1107
|
+
msg = f"ComputedVar {var._name} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
|
|
1152
1108
|
raise exceptions.VarDependencyError(msg)
|
|
1153
1109
|
|
|
1154
1110
|
for substate in state.class_subclasses:
|
|
1155
1111
|
self._validate_var_dependencies(substate)
|
|
1156
1112
|
|
|
1157
|
-
def _compile(self,
|
|
1113
|
+
def _compile(self, prerender_routes: bool = False, dry_run: bool = False):
|
|
1158
1114
|
"""Compile the app and output it to the pages folder.
|
|
1159
1115
|
|
|
1160
1116
|
Args:
|
|
1161
|
-
|
|
1117
|
+
prerender_routes: Whether to prerender the routes.
|
|
1162
1118
|
dry_run: Whether to compile the app without saving it.
|
|
1163
1119
|
|
|
1164
1120
|
Raises:
|
|
@@ -1293,15 +1249,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1293
1249
|
# Track imports found.
|
|
1294
1250
|
all_imports = {}
|
|
1295
1251
|
|
|
1296
|
-
# This has to happen before compiling stateful components as that
|
|
1297
|
-
# prevents recursive functions from reaching all components.
|
|
1298
|
-
for component in self._pages.values():
|
|
1299
|
-
# Add component._get_all_imports() to all_imports.
|
|
1300
|
-
all_imports.update(component._get_all_imports())
|
|
1301
|
-
|
|
1302
|
-
# Add the app wrappers from this component.
|
|
1303
|
-
app_wrappers.update(component._get_all_app_wrap_components())
|
|
1304
|
-
|
|
1305
1252
|
if (toaster := self.toaster) is not None:
|
|
1306
1253
|
from reflex.components.component import memo
|
|
1307
1254
|
|
|
@@ -1319,16 +1266,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1319
1266
|
if component is not None:
|
|
1320
1267
|
app_wrappers[key] = component
|
|
1321
1268
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1269
|
+
# Compile custom components.
|
|
1270
|
+
(
|
|
1271
|
+
custom_components_output,
|
|
1272
|
+
custom_components_result,
|
|
1273
|
+
custom_components_imports,
|
|
1274
|
+
) = compiler.compile_components(dict.fromkeys(CUSTOM_COMPONENTS.values()))
|
|
1275
|
+
compile_results.append((custom_components_output, custom_components_result))
|
|
1276
|
+
all_imports.update(custom_components_imports)
|
|
1277
|
+
progress.advance(task)
|
|
1324
1278
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1279
|
+
# This has to happen before compiling stateful components as that
|
|
1280
|
+
# prevents recursive functions from reaching all components.
|
|
1281
|
+
for component in self._pages.values():
|
|
1282
|
+
# Add component._get_all_imports() to all_imports.
|
|
1283
|
+
all_imports.update(component._get_all_imports())
|
|
1284
|
+
|
|
1285
|
+
# Add the app wrappers from this component.
|
|
1286
|
+
app_wrappers.update(component._get_all_app_wrap_components())
|
|
1332
1287
|
|
|
1333
1288
|
# Perform auto-memoization of stateful components.
|
|
1334
1289
|
with console.timing("Auto-memoize StatefulComponents"):
|
|
@@ -1407,7 +1362,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1407
1362
|
)
|
|
1408
1363
|
|
|
1409
1364
|
# Compile the root stylesheet with base styles.
|
|
1410
|
-
_submit_work(
|
|
1365
|
+
_submit_work(
|
|
1366
|
+
compiler.compile_root_stylesheet, self.stylesheets, self.reset_style
|
|
1367
|
+
)
|
|
1411
1368
|
|
|
1412
1369
|
# Compile the theme.
|
|
1413
1370
|
_submit_work(compile_theme, self.style)
|
|
@@ -1431,6 +1388,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1431
1388
|
)
|
|
1432
1389
|
)
|
|
1433
1390
|
),
|
|
1391
|
+
unevaluated_pages=list(self._unevaluated_pages.values()),
|
|
1434
1392
|
)
|
|
1435
1393
|
|
|
1436
1394
|
# Wait for all compilation tasks to complete.
|
|
@@ -1465,16 +1423,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1465
1423
|
)
|
|
1466
1424
|
progress.advance(task)
|
|
1467
1425
|
|
|
1468
|
-
# Compile custom components.
|
|
1469
|
-
(
|
|
1470
|
-
custom_components_output,
|
|
1471
|
-
custom_components_result,
|
|
1472
|
-
custom_components_imports,
|
|
1473
|
-
) = compiler.compile_components(dict.fromkeys(CUSTOM_COMPONENTS.values()))
|
|
1474
|
-
compile_results.append((custom_components_output, custom_components_result))
|
|
1475
|
-
all_imports.update(custom_components_imports)
|
|
1476
|
-
|
|
1477
|
-
progress.advance(task)
|
|
1478
1426
|
progress.stop()
|
|
1479
1427
|
|
|
1480
1428
|
if dry_run:
|
|
@@ -1484,15 +1432,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1484
1432
|
with console.timing("Install Frontend Packages"):
|
|
1485
1433
|
self._get_frontend_packages(all_imports)
|
|
1486
1434
|
|
|
1487
|
-
# Setup the
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
for package, import_vars in all_imports.items()
|
|
1491
|
-
if any(import_var.transpile for import_var in import_vars)
|
|
1492
|
-
]
|
|
1493
|
-
prerequisites.update_next_config(
|
|
1494
|
-
export=export,
|
|
1495
|
-
transpile_packages=transpile_packages,
|
|
1435
|
+
# Setup the react-router.config.js
|
|
1436
|
+
prerequisites.update_react_router_config(
|
|
1437
|
+
prerender_routes=prerender_routes,
|
|
1496
1438
|
)
|
|
1497
1439
|
|
|
1498
1440
|
if is_prod_mode():
|
|
@@ -1501,9 +1443,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1501
1443
|
else:
|
|
1502
1444
|
# In dev mode, delete removed pages and update existing pages.
|
|
1503
1445
|
keep_files = [Path(output_path) for output_path, _ in compile_results]
|
|
1504
|
-
for p in Path(
|
|
1505
|
-
|
|
1506
|
-
|
|
1446
|
+
for p in Path(
|
|
1447
|
+
prerequisites.get_web_dir()
|
|
1448
|
+
/ constants.Dirs.PAGES
|
|
1449
|
+
/ constants.Dirs.ROUTES
|
|
1450
|
+
).rglob("*"):
|
|
1507
1451
|
if p.is_file() and p not in keep_files:
|
|
1508
1452
|
# Remove pages that are no longer in the app.
|
|
1509
1453
|
p.unlink()
|
|
@@ -1740,7 +1684,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1740
1684
|
|
|
1741
1685
|
async def process(
|
|
1742
1686
|
app: App, event: Event, sid: str, headers: dict, client_ip: str
|
|
1743
|
-
) ->
|
|
1687
|
+
) -> AsyncGenerator[StateUpdate]:
|
|
1744
1688
|
"""Process an event.
|
|
1745
1689
|
|
|
1746
1690
|
Args:
|
|
@@ -1792,6 +1736,11 @@ async def process(
|
|
|
1792
1736
|
# assignment will recurse into substates and force recalculation of
|
|
1793
1737
|
# dependent ComputedVar (dynamic route variables)
|
|
1794
1738
|
state.router_data = router_data
|
|
1739
|
+
router_data[constants.RouteVar.PATH] = "/" + (
|
|
1740
|
+
app.router(path) or "404"
|
|
1741
|
+
if (path := router_data.get(constants.RouteVar.PATH))
|
|
1742
|
+
else "404"
|
|
1743
|
+
).removeprefix("/")
|
|
1795
1744
|
state.router = RouterData(router_data)
|
|
1796
1745
|
|
|
1797
1746
|
# Preprocess the event.
|
|
@@ -1970,7 +1919,6 @@ def upload(app: App):
|
|
|
1970
1919
|
UploadFile(
|
|
1971
1920
|
file=content_copy,
|
|
1972
1921
|
path=Path(file.filename.lstrip("/")) if file.filename else None,
|
|
1973
|
-
_deprecated_filename=file.filename,
|
|
1974
1922
|
size=file.size,
|
|
1975
1923
|
headers=file.headers,
|
|
1976
1924
|
)
|
|
@@ -2131,10 +2079,13 @@ class EventNamespace(AsyncNamespace):
|
|
|
2131
2079
|
except (KeyError, IndexError):
|
|
2132
2080
|
client_ip = environ.get("REMOTE_ADDR", "0.0.0.0")
|
|
2133
2081
|
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2082
|
+
async with contextlib.aclosing(
|
|
2083
|
+
process(self.app, event, sid, headers, client_ip)
|
|
2084
|
+
) as updates_gen:
|
|
2085
|
+
# Process the events.
|
|
2086
|
+
async for update in updates_gen:
|
|
2087
|
+
# Emit the update from processing the event.
|
|
2088
|
+
await self.emit_update(update=update, sid=sid)
|
|
2138
2089
|
|
|
2139
2090
|
async def on_ping(self, sid: str):
|
|
2140
2091
|
"""Event for testing the API endpoint.
|
reflex/base.py
CHANGED
|
@@ -1,48 +1,6 @@
|
|
|
1
1
|
"""Define the base Reflex class."""
|
|
2
2
|
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
-
|
|
8
|
-
import pydantic.v1.main as pydantic_main
|
|
9
3
|
from pydantic.v1 import BaseModel
|
|
10
|
-
from pydantic.v1.fields import ModelField
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def validate_field_name(bases: list[type[BaseModel]], field_name: str) -> None:
|
|
14
|
-
"""Ensure that the field's name does not shadow an existing attribute of the model.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
bases: List of base models to check for shadowed attrs.
|
|
18
|
-
field_name: name of attribute
|
|
19
|
-
|
|
20
|
-
Raises:
|
|
21
|
-
VarNameError: If state var field shadows another in its parent state
|
|
22
|
-
"""
|
|
23
|
-
from reflex.utils.exceptions import VarNameError
|
|
24
|
-
|
|
25
|
-
# can't use reflex.config.environment here cause of circular import
|
|
26
|
-
reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true"
|
|
27
|
-
base = None
|
|
28
|
-
try:
|
|
29
|
-
for base in bases:
|
|
30
|
-
if not reload and getattr(base, field_name, None):
|
|
31
|
-
pass
|
|
32
|
-
except TypeError as te:
|
|
33
|
-
msg = (
|
|
34
|
-
f'State var "{field_name}" in {base} has been shadowed by a substate var; '
|
|
35
|
-
f'use a different field name instead".'
|
|
36
|
-
)
|
|
37
|
-
raise VarNameError(msg) from te
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# monkeypatch pydantic validate_field_name method to skip validating
|
|
41
|
-
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
|
42
|
-
pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPrivateImportUsage]
|
|
43
|
-
|
|
44
|
-
if TYPE_CHECKING:
|
|
45
|
-
from reflex.vars import Var
|
|
46
4
|
|
|
47
5
|
|
|
48
6
|
class Base(BaseModel):
|
|
@@ -75,7 +33,7 @@ class Base(BaseModel):
|
|
|
75
33
|
default=serialize,
|
|
76
34
|
)
|
|
77
35
|
|
|
78
|
-
def set(self, **kwargs:
|
|
36
|
+
def set(self, **kwargs: object):
|
|
79
37
|
"""Set multiple fields and return the object.
|
|
80
38
|
|
|
81
39
|
Args:
|
|
@@ -87,47 +45,3 @@ class Base(BaseModel):
|
|
|
87
45
|
for key, value in kwargs.items():
|
|
88
46
|
setattr(self, key, value)
|
|
89
47
|
return self
|
|
90
|
-
|
|
91
|
-
@classmethod
|
|
92
|
-
def get_fields(cls) -> dict[str, ModelField]:
|
|
93
|
-
"""Get the fields of the object.
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
The fields of the object.
|
|
97
|
-
"""
|
|
98
|
-
return cls.__fields__
|
|
99
|
-
|
|
100
|
-
@classmethod
|
|
101
|
-
def add_field(cls, var: Var, default_value: Any):
|
|
102
|
-
"""Add a pydantic field after class definition.
|
|
103
|
-
|
|
104
|
-
Used by State.add_var() to correctly handle the new variable.
|
|
105
|
-
|
|
106
|
-
Args:
|
|
107
|
-
var: The variable to add a pydantic field for.
|
|
108
|
-
default_value: The default value of the field
|
|
109
|
-
"""
|
|
110
|
-
var_name = var._var_field_name
|
|
111
|
-
new_field = ModelField.infer(
|
|
112
|
-
name=var_name,
|
|
113
|
-
value=default_value,
|
|
114
|
-
annotation=var._var_type,
|
|
115
|
-
class_validators=None,
|
|
116
|
-
config=cls.__config__,
|
|
117
|
-
)
|
|
118
|
-
cls.__fields__.update({var_name: new_field})
|
|
119
|
-
|
|
120
|
-
def get_value(self, key: str) -> Any:
|
|
121
|
-
"""Get the value of a field.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
key: The key of the field.
|
|
125
|
-
|
|
126
|
-
Returns:
|
|
127
|
-
The value of the field.
|
|
128
|
-
"""
|
|
129
|
-
if isinstance(key, str):
|
|
130
|
-
# Seems like this function signature was wrong all along?
|
|
131
|
-
# If the user wants a field that we know of, get it and pass it off to _get_value
|
|
132
|
-
return getattr(self, key)
|
|
133
|
-
return key
|