reflex 0.7.0a4__py3-none-any.whl → 0.7.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/package.json.jinja2 +7 -1
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
- reflex/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +269 -86
- reflex/base.py +4 -10
- reflex/compiler/compiler.py +46 -12
- reflex/compiler/templates.py +1 -2
- reflex/compiler/utils.py +23 -14
- reflex/components/base/bare.py +109 -16
- reflex/components/component.py +179 -124
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +1 -0
- reflex/components/core/auto_scroll.py +114 -0
- reflex/components/core/auto_scroll.pyi +284 -0
- reflex/components/core/banner.py +40 -9
- reflex/components/core/banner.pyi +400 -87
- reflex/components/core/breakpoints.py +1 -1
- reflex/components/core/cond.py +0 -8
- reflex/components/core/foreach.py +12 -2
- reflex/components/core/html.pyi +200 -19
- reflex/components/core/match.py +4 -4
- reflex/components/core/sticky.py +4 -30
- reflex/components/core/sticky.pyi +874 -90
- reflex/components/core/upload.py +3 -5
- reflex/components/core/upload.pyi +2 -4
- reflex/components/datadisplay/code.py +36 -10
- reflex/components/datadisplay/code.pyi +1 -1
- reflex/components/datadisplay/dataeditor.py +1 -3
- reflex/components/datadisplay/dataeditor.pyi +1 -3
- reflex/components/el/elements/base.py +95 -17
- reflex/components/el/elements/base.pyi +278 -19
- reflex/components/el/elements/forms.py +124 -102
- reflex/components/el/elements/forms.pyi +2787 -365
- reflex/components/el/elements/inline.py +24 -15
- reflex/components/el/elements/inline.pyi +5655 -546
- reflex/components/el/elements/media.py +79 -95
- reflex/components/el/elements/media.pyi +5167 -565
- reflex/components/el/elements/metadata.py +19 -17
- reflex/components/el/elements/metadata.pyi +841 -89
- reflex/components/el/elements/other.py +3 -5
- reflex/components/el/elements/other.pyi +1404 -137
- reflex/components/el/elements/scripts.py +10 -13
- reflex/components/el/elements/scripts.pyi +634 -65
- reflex/components/el/elements/sectioning.pyi +3001 -286
- reflex/components/el/elements/tables.py +14 -35
- reflex/components/el/elements/tables.pyi +2029 -218
- reflex/components/el/elements/typography.py +10 -13
- reflex/components/el/elements/typography.pyi +3014 -297
- reflex/components/lucide/icon.py +22 -6
- reflex/components/markdown/markdown.py +30 -10
- reflex/components/markdown/markdown.pyi +3 -2
- reflex/components/plotly/plotly.py +1 -3
- reflex/components/plotly/plotly.pyi +1 -3
- reflex/components/radix/primitives/form.pyi +624 -93
- reflex/components/radix/themes/color_mode.py +1 -1
- reflex/components/radix/themes/color_mode.pyi +213 -31
- reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
- reflex/components/radix/themes/components/badge.pyi +199 -18
- reflex/components/radix/themes/components/button.pyi +213 -31
- reflex/components/radix/themes/components/callout.pyi +1000 -95
- reflex/components/radix/themes/components/card.pyi +199 -18
- reflex/components/radix/themes/components/context_menu.py +79 -1
- reflex/components/radix/themes/components/context_menu.pyi +320 -1
- reflex/components/radix/themes/components/dialog.pyi +199 -18
- reflex/components/radix/themes/components/hover_card.pyi +199 -18
- reflex/components/radix/themes/components/icon_button.pyi +213 -31
- reflex/components/radix/themes/components/inset.pyi +199 -18
- reflex/components/radix/themes/components/popover.pyi +199 -18
- reflex/components/radix/themes/components/table.pyi +1437 -154
- reflex/components/radix/themes/components/text_area.py +2 -2
- reflex/components/radix/themes/components/text_area.pyi +201 -20
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/text_field.pyi +444 -88
- reflex/components/radix/themes/layout/box.pyi +200 -19
- reflex/components/radix/themes/layout/center.pyi +199 -18
- reflex/components/radix/themes/layout/container.pyi +199 -18
- reflex/components/radix/themes/layout/flex.pyi +199 -18
- reflex/components/radix/themes/layout/grid.pyi +199 -18
- reflex/components/radix/themes/layout/list.pyi +604 -57
- reflex/components/radix/themes/layout/section.pyi +199 -18
- reflex/components/radix/themes/layout/spacer.pyi +199 -18
- reflex/components/radix/themes/layout/stack.pyi +597 -54
- reflex/components/radix/themes/typography/blockquote.pyi +200 -19
- reflex/components/radix/themes/typography/code.pyi +199 -18
- reflex/components/radix/themes/typography/heading.pyi +199 -18
- reflex/components/radix/themes/typography/link.pyi +238 -28
- reflex/components/radix/themes/typography/text.pyi +1394 -127
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/react_player/react_player.pyi +1 -3
- reflex/components/sonner/toast.py +41 -12
- reflex/components/sonner/toast.pyi +20 -6
- reflex/components/tags/iter_tag.py +4 -0
- reflex/components/tags/tag.py +3 -3
- reflex/config.py +187 -28
- reflex/constants/__init__.py +2 -0
- reflex/constants/base.py +6 -0
- reflex/constants/compiler.py +9 -0
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +8 -5
- reflex/constants/utils.py +1 -3
- reflex/event.py +7 -16
- reflex/experimental/layout.pyi +597 -54
- reflex/py.typed +0 -0
- reflex/reflex.py +44 -48
- reflex/state.py +49 -44
- reflex/style.py +15 -22
- reflex/testing.py +2 -0
- reflex/utils/build.py +12 -0
- reflex/utils/console.py +4 -0
- reflex/utils/decorator.py +25 -0
- reflex/utils/exec.py +92 -34
- reflex/utils/format.py +35 -6
- reflex/utils/path_ops.py +32 -1
- reflex/utils/prerequisites.py +45 -35
- reflex/utils/processes.py +12 -13
- reflex/utils/serializers.py +20 -43
- reflex/utils/telemetry.py +4 -15
- reflex/utils/types.py +36 -66
- reflex/vars/base.py +53 -76
- reflex/vars/function.py +17 -5
- reflex/vars/number.py +1 -1
- reflex/vars/sequence.py +80 -4
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/METADATA +4 -5
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/RECORD +128 -124
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/LICENSE +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/WHEEL +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/entry_points.txt +0 -0
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
{% for package, version in dev_dependencies.items() %}
|
|
17
17
|
"{{ package }}": "{{ version }}"{% if not loop.last %},{% endif %}
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
{% endfor %}
|
|
20
|
+
},
|
|
21
|
+
"overrides": {
|
|
22
|
+
{% for package, version in overrides.items() %}
|
|
23
|
+
"{{ package }}": "{{ version }}"{% if not loop.last %},{% endif %}
|
|
24
|
+
|
|
19
25
|
{% endfor %}
|
|
20
26
|
}
|
|
21
27
|
}
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
export default function RadixThemesColorModeProvider({ children }) {
|
|
11
11
|
const { theme, resolvedTheme, setTheme } = useTheme();
|
|
12
12
|
const [rawColorMode, setRawColorMode] = useState(defaultColorMode);
|
|
13
|
-
const [resolvedColorMode, setResolvedColorMode] = useState(
|
|
13
|
+
const [resolvedColorMode, setResolvedColorMode] = useState(
|
|
14
|
+
defaultColorMode === "dark" ? "dark" : "light"
|
|
15
|
+
);
|
|
14
16
|
|
|
15
17
|
useEffect(() => {
|
|
16
18
|
if (isDevMode) {
|
reflex/__init__.py
CHANGED
reflex/__init__.pyi
CHANGED
|
@@ -34,6 +34,7 @@ from .components.component import Component as Component
|
|
|
34
34
|
from .components.component import ComponentNamespace as ComponentNamespace
|
|
35
35
|
from .components.component import NoSSRComponent as NoSSRComponent
|
|
36
36
|
from .components.component import memo as memo
|
|
37
|
+
from .components.core.auto_scroll import auto_scroll as auto_scroll
|
|
37
38
|
from .components.core.banner import connection_banner as connection_banner
|
|
38
39
|
from .components.core.banner import connection_modal as connection_modal
|
|
39
40
|
from .components.core.breakpoints import breakpoints as breakpoints
|
reflex/app.py
CHANGED
|
@@ -11,17 +11,17 @@ import functools
|
|
|
11
11
|
import inspect
|
|
12
12
|
import io
|
|
13
13
|
import json
|
|
14
|
-
import multiprocessing
|
|
15
|
-
import platform
|
|
16
14
|
import sys
|
|
17
15
|
import traceback
|
|
18
16
|
from datetime import datetime
|
|
19
17
|
from pathlib import Path
|
|
18
|
+
from timeit import default_timer as timer
|
|
20
19
|
from types import SimpleNamespace
|
|
21
20
|
from typing import (
|
|
22
21
|
TYPE_CHECKING,
|
|
23
22
|
Any,
|
|
24
23
|
AsyncIterator,
|
|
24
|
+
BinaryIO,
|
|
25
25
|
Callable,
|
|
26
26
|
Coroutine,
|
|
27
27
|
Dict,
|
|
@@ -35,12 +35,15 @@ from typing import (
|
|
|
35
35
|
get_type_hints,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
from fastapi import FastAPI, HTTPException, Request
|
|
38
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
39
|
+
from fastapi import UploadFile as FastAPIUploadFile
|
|
39
40
|
from fastapi.middleware import cors
|
|
40
41
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
41
42
|
from fastapi.staticfiles import StaticFiles
|
|
42
43
|
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
|
43
44
|
from socketio import ASGIApp, AsyncNamespace, AsyncServer
|
|
45
|
+
from starlette.datastructures import Headers
|
|
46
|
+
from starlette.datastructures import UploadFile as StarletteUploadFile
|
|
44
47
|
from starlette_admin.contrib.sqla.admin import Admin
|
|
45
48
|
from starlette_admin.contrib.sqla.view import ModelView
|
|
46
49
|
|
|
@@ -72,7 +75,8 @@ from reflex.components.core.client_side_routing import (
|
|
|
72
75
|
from reflex.components.core.sticky import sticky
|
|
73
76
|
from reflex.components.core.upload import Upload, get_upload_dir
|
|
74
77
|
from reflex.components.radix import themes
|
|
75
|
-
from reflex.
|
|
78
|
+
from reflex.components.sonner.toast import toast
|
|
79
|
+
from reflex.config import ExecutorType, environment, get_config
|
|
76
80
|
from reflex.event import (
|
|
77
81
|
_EVENT_FIELDS,
|
|
78
82
|
Event,
|
|
@@ -81,7 +85,6 @@ from reflex.event import (
|
|
|
81
85
|
EventType,
|
|
82
86
|
IndividualEventType,
|
|
83
87
|
get_hydrate_event,
|
|
84
|
-
window_alert,
|
|
85
88
|
)
|
|
86
89
|
from reflex.model import Model, get_db_status
|
|
87
90
|
from reflex.page import DECORATED_PAGES
|
|
@@ -97,6 +100,7 @@ from reflex.state import (
|
|
|
97
100
|
StateManager,
|
|
98
101
|
StateUpdate,
|
|
99
102
|
_substate_key,
|
|
103
|
+
all_base_state_classes,
|
|
100
104
|
code_uses_state_contexts,
|
|
101
105
|
)
|
|
102
106
|
from reflex.utils import (
|
|
@@ -108,12 +112,13 @@ from reflex.utils import (
|
|
|
108
112
|
prerequisites,
|
|
109
113
|
types,
|
|
110
114
|
)
|
|
111
|
-
from reflex.utils.exec import is_prod_mode, is_testing_env
|
|
115
|
+
from reflex.utils.exec import get_compile_context, is_prod_mode, is_testing_env
|
|
112
116
|
from reflex.utils.imports import ImportVar
|
|
113
117
|
|
|
114
118
|
if TYPE_CHECKING:
|
|
115
119
|
from reflex.vars import Var
|
|
116
120
|
|
|
121
|
+
|
|
117
122
|
# Define custom types.
|
|
118
123
|
ComponentCallable = Callable[[], Component]
|
|
119
124
|
Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
|
|
@@ -139,7 +144,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
|
|
139
144
|
EventSpec: The window alert event.
|
|
140
145
|
|
|
141
146
|
"""
|
|
142
|
-
from reflex.components.sonner.toast import
|
|
147
|
+
from reflex.components.sonner.toast import toast
|
|
143
148
|
|
|
144
149
|
error = traceback.format_exc()
|
|
145
150
|
|
|
@@ -150,18 +155,44 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
|
|
150
155
|
if is_prod_mode()
|
|
151
156
|
else [f"{type(exception).__name__}: {exception}.", "See logs for details."]
|
|
152
157
|
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
|
|
159
|
+
return toast(
|
|
160
|
+
"An error occurred.",
|
|
161
|
+
level="error",
|
|
162
|
+
fallback_to_alert=True,
|
|
163
|
+
description="<br/>".join(error_message),
|
|
164
|
+
position="top-center",
|
|
165
|
+
id="backend_error",
|
|
166
|
+
style={"width": "500px"},
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def extra_overlay_function() -> Optional[Component]:
|
|
171
|
+
"""Extra overlay function to add to the overlay component.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
The extra overlay function.
|
|
175
|
+
"""
|
|
176
|
+
config = get_config()
|
|
177
|
+
|
|
178
|
+
extra_config = config.extra_overlay_function
|
|
179
|
+
config_overlay = None
|
|
180
|
+
if extra_config:
|
|
181
|
+
module, _, function_name = extra_config.rpartition(".")
|
|
182
|
+
try:
|
|
183
|
+
module = __import__(module)
|
|
184
|
+
config_overlay = Fragment.create(getattr(module, function_name)())
|
|
185
|
+
config_overlay._get_all_imports()
|
|
186
|
+
except Exception as e:
|
|
187
|
+
from reflex.compiler.utils import save_error
|
|
188
|
+
|
|
189
|
+
log_path = save_error(e)
|
|
190
|
+
|
|
191
|
+
console.error(
|
|
192
|
+
f"Error loading extra_overlay_function {extra_config}. Error saved to {log_path}"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return config_overlay
|
|
165
196
|
|
|
166
197
|
|
|
167
198
|
def default_overlay_component() -> Component:
|
|
@@ -170,14 +201,21 @@ def default_overlay_component() -> Component:
|
|
|
170
201
|
Returns:
|
|
171
202
|
The default overlay_component, which is a connection_modal.
|
|
172
203
|
"""
|
|
173
|
-
|
|
204
|
+
from reflex.components.component import memo
|
|
205
|
+
|
|
206
|
+
def default_overlay_components():
|
|
207
|
+
return Fragment.create(
|
|
208
|
+
connection_pulser(),
|
|
209
|
+
connection_toaster(),
|
|
210
|
+
*(
|
|
211
|
+
[backend_disabled()]
|
|
212
|
+
if get_compile_context() == constants.CompileContext.DEPLOY
|
|
213
|
+
else []
|
|
214
|
+
),
|
|
215
|
+
*codespaces.codespaces_auto_redirect(),
|
|
216
|
+
)
|
|
174
217
|
|
|
175
|
-
return Fragment.create(
|
|
176
|
-
connection_pulser(),
|
|
177
|
-
connection_toaster(),
|
|
178
|
-
*([backend_disabled()] if config.is_reflex_cloud else []),
|
|
179
|
-
*codespaces.codespaces_auto_redirect(),
|
|
180
|
-
)
|
|
218
|
+
return Fragment.create(memo(default_overlay_components)())
|
|
181
219
|
|
|
182
220
|
|
|
183
221
|
def default_error_boundary(*children: Component) -> Component:
|
|
@@ -199,6 +237,53 @@ class OverlayFragment(Fragment):
|
|
|
199
237
|
pass
|
|
200
238
|
|
|
201
239
|
|
|
240
|
+
@dataclasses.dataclass(frozen=True)
|
|
241
|
+
class UploadFile(StarletteUploadFile):
|
|
242
|
+
"""A file uploaded to the server.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
file: The standard Python file object (non-async).
|
|
246
|
+
filename: The original file name.
|
|
247
|
+
size: The size of the file in bytes.
|
|
248
|
+
headers: The headers of the request.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
file: BinaryIO
|
|
252
|
+
|
|
253
|
+
path: Optional[Path] = dataclasses.field(default=None)
|
|
254
|
+
|
|
255
|
+
_deprecated_filename: Optional[str] = dataclasses.field(default=None)
|
|
256
|
+
|
|
257
|
+
size: Optional[int] = dataclasses.field(default=None)
|
|
258
|
+
|
|
259
|
+
headers: Headers = dataclasses.field(default_factory=Headers)
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def name(self) -> Optional[str]:
|
|
263
|
+
"""Get the name of the uploaded file.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The name of the uploaded file.
|
|
267
|
+
"""
|
|
268
|
+
if self.path:
|
|
269
|
+
return self.path.name
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def filename(self) -> Optional[str]:
|
|
273
|
+
"""Get the filename of the uploaded file.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
The filename of the uploaded file.
|
|
277
|
+
"""
|
|
278
|
+
console.deprecate(
|
|
279
|
+
feature_name="UploadFile.filename",
|
|
280
|
+
reason="Use UploadFile.name instead.",
|
|
281
|
+
deprecation_version="0.7.1",
|
|
282
|
+
removal_version="0.8.0",
|
|
283
|
+
)
|
|
284
|
+
return self._deprecated_filename
|
|
285
|
+
|
|
286
|
+
|
|
202
287
|
@dataclasses.dataclass(
|
|
203
288
|
frozen=True,
|
|
204
289
|
)
|
|
@@ -249,11 +334,26 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
249
334
|
|
|
250
335
|
# A component that is present on every page (defaults to the Connection Error banner).
|
|
251
336
|
overlay_component: Optional[Union[Component, ComponentCallable]] = (
|
|
252
|
-
dataclasses.field(
|
|
337
|
+
dataclasses.field(default=None)
|
|
253
338
|
)
|
|
254
339
|
|
|
255
340
|
# Error boundary component to wrap the app with.
|
|
256
|
-
error_boundary: Optional[ComponentCallable] =
|
|
341
|
+
error_boundary: Optional[ComponentCallable] = dataclasses.field(default=None)
|
|
342
|
+
|
|
343
|
+
# 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.
|
|
344
|
+
app_wraps: Dict[tuple[int, str], Callable[[bool], Optional[Component]]] = (
|
|
345
|
+
dataclasses.field(
|
|
346
|
+
default_factory=lambda: {
|
|
347
|
+
(55, "ErrorBoundary"): (
|
|
348
|
+
lambda stateful: default_error_boundary() if stateful else None
|
|
349
|
+
),
|
|
350
|
+
(5, "Overlay"): (
|
|
351
|
+
lambda stateful: default_overlay_component() if stateful else None
|
|
352
|
+
),
|
|
353
|
+
(4, "ExtraOverlay"): lambda stateful: extra_overlay_function(),
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
)
|
|
257
357
|
|
|
258
358
|
# Components to add to the head of every page.
|
|
259
359
|
head_components: List[Component] = dataclasses.field(default_factory=list)
|
|
@@ -275,6 +375,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
275
375
|
# A map from a page route to the component to render. Users should use `add_page`.
|
|
276
376
|
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
|
277
377
|
|
|
378
|
+
# A mapping of pages which created states as they were being evaluated.
|
|
379
|
+
_stateful_pages: Dict[str, None] = dataclasses.field(default_factory=dict)
|
|
380
|
+
|
|
278
381
|
# The backend API object.
|
|
279
382
|
_api: FastAPI | None = None
|
|
280
383
|
|
|
@@ -308,6 +411,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
308
411
|
[Exception], Union[EventSpec, List[EventSpec], None]
|
|
309
412
|
] = default_backend_exception_handler
|
|
310
413
|
|
|
414
|
+
# Put the toast provider in the app wrap.
|
|
415
|
+
toaster: Component | None = dataclasses.field(default_factory=toast.provider)
|
|
416
|
+
|
|
311
417
|
@property
|
|
312
418
|
def api(self) -> FastAPI | None:
|
|
313
419
|
"""Get the backend api.
|
|
@@ -489,8 +595,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
489
595
|
"""Add optional api endpoints (_upload)."""
|
|
490
596
|
if not self.api:
|
|
491
597
|
return
|
|
492
|
-
|
|
493
|
-
|
|
598
|
+
upload_is_used_marker = (
|
|
599
|
+
prerequisites.get_backend_dir() / constants.Dirs.UPLOAD_IS_USED
|
|
600
|
+
)
|
|
601
|
+
if Upload.is_used or upload_is_used_marker.exists():
|
|
494
602
|
# To upload files.
|
|
495
603
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
|
496
604
|
|
|
@@ -500,10 +608,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
500
608
|
StaticFiles(directory=get_upload_dir()),
|
|
501
609
|
name="uploaded_files",
|
|
502
610
|
)
|
|
611
|
+
|
|
612
|
+
upload_is_used_marker.parent.mkdir(parents=True, exist_ok=True)
|
|
613
|
+
upload_is_used_marker.touch()
|
|
503
614
|
if codespaces.is_running_in_codespaces():
|
|
504
615
|
self.api.get(str(constants.Endpoint.AUTH_CODESPACE))(
|
|
505
616
|
codespaces.auth_codespace
|
|
506
617
|
)
|
|
618
|
+
if environment.REFLEX_ADD_ALL_ROUTES_ENDPOINT.get():
|
|
619
|
+
self.add_all_routes_endpoint()
|
|
507
620
|
|
|
508
621
|
def _add_cors(self):
|
|
509
622
|
"""Add CORS middleware to the app."""
|
|
@@ -541,7 +654,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
541
654
|
Returns:
|
|
542
655
|
The generated component.
|
|
543
656
|
"""
|
|
544
|
-
|
|
657
|
+
from reflex.compiler.compiler import into_component
|
|
658
|
+
|
|
659
|
+
return into_component(component)
|
|
545
660
|
|
|
546
661
|
def add_page(
|
|
547
662
|
self,
|
|
@@ -642,13 +757,19 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
642
757
|
route: The route of the page to compile.
|
|
643
758
|
save_page: If True, the compiled page is saved to self._pages.
|
|
644
759
|
"""
|
|
760
|
+
n_states_before = len(all_base_state_classes)
|
|
645
761
|
component, enable_state = compiler.compile_unevaluated_page(
|
|
646
762
|
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
|
647
763
|
)
|
|
648
764
|
|
|
765
|
+
# Indicate that the app should use state.
|
|
649
766
|
if enable_state:
|
|
650
767
|
self._enable_state()
|
|
651
768
|
|
|
769
|
+
# Indicate that evaluating this page creates one or more state classes.
|
|
770
|
+
if len(all_base_state_classes) > n_states_before:
|
|
771
|
+
self._stateful_pages[route] = None
|
|
772
|
+
|
|
652
773
|
# Add the page.
|
|
653
774
|
self._check_routes_conflict(route)
|
|
654
775
|
if save_page:
|
|
@@ -863,33 +984,17 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
863
984
|
for k, component in self._pages.items():
|
|
864
985
|
self._pages[k] = self._add_overlay_to_component(component)
|
|
865
986
|
|
|
866
|
-
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
|
867
|
-
if self.error_boundary is None:
|
|
868
|
-
return component
|
|
869
|
-
|
|
870
|
-
component = self.error_boundary(*component.children)
|
|
871
|
-
|
|
872
|
-
return component
|
|
873
|
-
|
|
874
|
-
def _setup_error_boundary(self):
|
|
875
|
-
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
|
876
|
-
if self._state is None and self.error_boundary is default_error_boundary:
|
|
877
|
-
self.error_boundary = None
|
|
878
|
-
|
|
879
|
-
for k, component in self._pages.items():
|
|
880
|
-
# Skip the 404 page
|
|
881
|
-
if k == constants.Page404.SLUG:
|
|
882
|
-
continue
|
|
883
|
-
self._pages[k] = self._add_error_boundary_to_component(component)
|
|
884
|
-
|
|
885
987
|
def _setup_sticky_badge(self):
|
|
886
988
|
"""Add the sticky badge to the app."""
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
989
|
+
from reflex.components.component import memo
|
|
990
|
+
|
|
991
|
+
@memo
|
|
992
|
+
def memoized_badge():
|
|
890
993
|
sticky_badge = sticky()
|
|
891
994
|
sticky_badge._add_style_recursive({})
|
|
892
|
-
|
|
995
|
+
return sticky_badge
|
|
996
|
+
|
|
997
|
+
self.app_wraps[(0, "StickyBadge")] = lambda _: memoized_badge()
|
|
893
998
|
|
|
894
999
|
def _apply_decorated_pages(self):
|
|
895
1000
|
"""Add @rx.page decorated pages to the app.
|
|
@@ -956,6 +1061,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
956
1061
|
def get_compilation_time() -> str:
|
|
957
1062
|
return str(datetime.now().time()).split(".")[0]
|
|
958
1063
|
|
|
1064
|
+
should_compile = self._should_compile()
|
|
1065
|
+
backend_dir = prerequisites.get_backend_dir()
|
|
1066
|
+
if not should_compile and backend_dir.exists():
|
|
1067
|
+
stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
|
|
1068
|
+
if stateful_pages_marker.exists():
|
|
1069
|
+
with stateful_pages_marker.open("r") as f:
|
|
1070
|
+
stateful_pages = json.load(f)
|
|
1071
|
+
for route in stateful_pages:
|
|
1072
|
+
console.info(f"BE Evaluating stateful page: {route}")
|
|
1073
|
+
self._compile_page(route, save_page=False)
|
|
1074
|
+
self._enable_state()
|
|
1075
|
+
self._add_optional_endpoints()
|
|
1076
|
+
return
|
|
1077
|
+
|
|
959
1078
|
# Render a default 404 page if the user didn't supply one
|
|
960
1079
|
if constants.Page404.SLUG not in self._unevaluated_pages:
|
|
961
1080
|
self.add_page(route=constants.Page404.SLUG)
|
|
@@ -987,6 +1106,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
987
1106
|
console.debug(f"Evaluating page: {route}")
|
|
988
1107
|
self._compile_page(route, save_page=should_compile)
|
|
989
1108
|
|
|
1109
|
+
# Save the pages which created new states at eval time.
|
|
1110
|
+
self._write_stateful_pages_marker()
|
|
1111
|
+
|
|
990
1112
|
# Add the optional endpoints (_upload)
|
|
991
1113
|
self._add_optional_endpoints()
|
|
992
1114
|
|
|
@@ -1012,24 +1134,48 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1012
1134
|
)
|
|
1013
1135
|
|
|
1014
1136
|
with console.timing("Evaluate Pages (Frontend)"):
|
|
1137
|
+
performance_metrics: list[tuple[str, float]] = []
|
|
1015
1138
|
for route in self._unevaluated_pages:
|
|
1016
1139
|
console.debug(f"Evaluating page: {route}")
|
|
1140
|
+
start = timer()
|
|
1017
1141
|
self._compile_page(route, save_page=should_compile)
|
|
1142
|
+
end = timer()
|
|
1143
|
+
performance_metrics.append((route, end - start))
|
|
1018
1144
|
progress.advance(task)
|
|
1145
|
+
console.debug(
|
|
1146
|
+
"Slowest pages:\n"
|
|
1147
|
+
+ "\n".join(
|
|
1148
|
+
f"{route}: {time * 1000:.1f}ms"
|
|
1149
|
+
for route, time in sorted(
|
|
1150
|
+
performance_metrics, key=lambda x: x[1], reverse=True
|
|
1151
|
+
)[:10]
|
|
1152
|
+
)
|
|
1153
|
+
)
|
|
1154
|
+
# Save the pages which created new states at eval time.
|
|
1155
|
+
self._write_stateful_pages_marker()
|
|
1019
1156
|
|
|
1020
1157
|
# Add the optional endpoints (_upload)
|
|
1021
1158
|
self._add_optional_endpoints()
|
|
1022
1159
|
|
|
1023
1160
|
self._validate_var_dependencies()
|
|
1024
1161
|
self._setup_overlay_component()
|
|
1025
|
-
|
|
1026
|
-
if config.show_built_with_reflex:
|
|
1162
|
+
|
|
1163
|
+
if config.show_built_with_reflex is None:
|
|
1164
|
+
if (
|
|
1165
|
+
get_compile_context() == constants.CompileContext.DEPLOY
|
|
1166
|
+
and prerequisites.get_user_tier() in ["pro", "team", "enterprise"]
|
|
1167
|
+
):
|
|
1168
|
+
config.show_built_with_reflex = False
|
|
1169
|
+
else:
|
|
1170
|
+
config.show_built_with_reflex = True
|
|
1171
|
+
|
|
1172
|
+
if is_prod_mode() and config.show_built_with_reflex:
|
|
1027
1173
|
self._setup_sticky_badge()
|
|
1028
1174
|
|
|
1029
1175
|
progress.advance(task)
|
|
1030
1176
|
|
|
1031
1177
|
# Store the compile results.
|
|
1032
|
-
compile_results = []
|
|
1178
|
+
compile_results: list[tuple[str, str]] = []
|
|
1033
1179
|
|
|
1034
1180
|
progress.advance(task)
|
|
1035
1181
|
|
|
@@ -1049,6 +1195,35 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1049
1195
|
# Add the custom components from the page to the set.
|
|
1050
1196
|
custom_components |= component._get_all_custom_components()
|
|
1051
1197
|
|
|
1198
|
+
if (toaster := self.toaster) is not None:
|
|
1199
|
+
from reflex.components.component import memo
|
|
1200
|
+
|
|
1201
|
+
@memo
|
|
1202
|
+
def memoized_toast_provider():
|
|
1203
|
+
return toaster
|
|
1204
|
+
|
|
1205
|
+
toast_provider = Fragment.create(memoized_toast_provider())
|
|
1206
|
+
|
|
1207
|
+
app_wrappers[(1, "ToasterProvider")] = toast_provider
|
|
1208
|
+
|
|
1209
|
+
# Add the app wraps to the app.
|
|
1210
|
+
for key, app_wrap in self.app_wraps.items():
|
|
1211
|
+
component = app_wrap(self._state is not None)
|
|
1212
|
+
if component is not None:
|
|
1213
|
+
app_wrappers[key] = component
|
|
1214
|
+
|
|
1215
|
+
for component in app_wrappers.values():
|
|
1216
|
+
custom_components |= component._get_all_custom_components()
|
|
1217
|
+
|
|
1218
|
+
if self.error_boundary:
|
|
1219
|
+
console.deprecate(
|
|
1220
|
+
feature_name="App.error_boundary",
|
|
1221
|
+
reason="Use app_wraps instead.",
|
|
1222
|
+
deprecation_version="0.7.1",
|
|
1223
|
+
removal_version="0.8.0",
|
|
1224
|
+
)
|
|
1225
|
+
app_wrappers[(55, "ErrorBoundary")] = self.error_boundary()
|
|
1226
|
+
|
|
1052
1227
|
# Perform auto-memoization of stateful components.
|
|
1053
1228
|
with console.timing("Auto-memoize StatefulComponents"):
|
|
1054
1229
|
(
|
|
@@ -1090,33 +1265,19 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1090
1265
|
),
|
|
1091
1266
|
)
|
|
1092
1267
|
|
|
1093
|
-
|
|
1094
|
-
# Fallback to ThreadPoolExecutor as something that will always work.
|
|
1095
|
-
executor = None
|
|
1096
|
-
if (
|
|
1097
|
-
platform.system() in ("Linux", "Darwin")
|
|
1098
|
-
and (number_of_processes := environment.REFLEX_COMPILE_PROCESSES.get())
|
|
1099
|
-
is not None
|
|
1100
|
-
):
|
|
1101
|
-
executor = concurrent.futures.ProcessPoolExecutor(
|
|
1102
|
-
max_workers=number_of_processes or None,
|
|
1103
|
-
mp_context=multiprocessing.get_context("fork"),
|
|
1104
|
-
)
|
|
1105
|
-
else:
|
|
1106
|
-
executor = concurrent.futures.ThreadPoolExecutor(
|
|
1107
|
-
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
|
1108
|
-
)
|
|
1268
|
+
executor = ExecutorType.get_executor_from_environment()
|
|
1109
1269
|
|
|
1110
1270
|
for route, component in zip(self._pages, page_components, strict=True):
|
|
1111
1271
|
ExecutorSafeFunctions.COMPONENTS[route] = component
|
|
1112
1272
|
|
|
1113
1273
|
ExecutorSafeFunctions.STATE = self._state
|
|
1114
1274
|
|
|
1115
|
-
with executor:
|
|
1116
|
-
result_futures = []
|
|
1275
|
+
with console.timing("Compile to Javascript"), executor as executor:
|
|
1276
|
+
result_futures: list[concurrent.futures.Future[tuple[str, str]]] = []
|
|
1117
1277
|
|
|
1118
|
-
def _submit_work(fn: Callable, *args, **kwargs):
|
|
1278
|
+
def _submit_work(fn: Callable[..., tuple[str, str]], *args, **kwargs):
|
|
1119
1279
|
f = executor.submit(fn, *args, **kwargs)
|
|
1280
|
+
f.add_done_callback(lambda _: progress.advance(task))
|
|
1120
1281
|
result_futures.append(f)
|
|
1121
1282
|
|
|
1122
1283
|
# Compile the pre-compiled pages.
|
|
@@ -1142,10 +1303,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1142
1303
|
_submit_work(compiler.remove_tailwind_from_postcss)
|
|
1143
1304
|
|
|
1144
1305
|
# Wait for all compilation tasks to complete.
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1306
|
+
compile_results.extend(
|
|
1307
|
+
future.result()
|
|
1308
|
+
for future in concurrent.futures.as_completed(result_futures)
|
|
1309
|
+
)
|
|
1149
1310
|
|
|
1150
1311
|
app_root = self._app_root(app_wrappers=app_wrappers)
|
|
1151
1312
|
|
|
@@ -1170,10 +1331,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1170
1331
|
progress.advance(task)
|
|
1171
1332
|
|
|
1172
1333
|
# Compile custom components.
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1334
|
+
(
|
|
1335
|
+
custom_components_output,
|
|
1336
|
+
custom_components_result,
|
|
1337
|
+
custom_components_imports,
|
|
1338
|
+
) = compiler.compile_components(custom_components)
|
|
1339
|
+
compile_results.append((custom_components_output, custom_components_result))
|
|
1177
1340
|
all_imports.update(custom_components_imports)
|
|
1178
1341
|
|
|
1179
1342
|
progress.advance(task)
|
|
@@ -1211,6 +1374,25 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1211
1374
|
for output_path, code in compile_results:
|
|
1212
1375
|
compiler_utils.write_page(output_path, code)
|
|
1213
1376
|
|
|
1377
|
+
def _write_stateful_pages_marker(self):
|
|
1378
|
+
"""Write list of routes that create dynamic states for the backend to use later."""
|
|
1379
|
+
if self._state is not None:
|
|
1380
|
+
stateful_pages_marker = (
|
|
1381
|
+
prerequisites.get_backend_dir() / constants.Dirs.STATEFUL_PAGES
|
|
1382
|
+
)
|
|
1383
|
+
stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
|
|
1384
|
+
with stateful_pages_marker.open("w") as f:
|
|
1385
|
+
json.dump(list(self._stateful_pages), f)
|
|
1386
|
+
|
|
1387
|
+
def add_all_routes_endpoint(self):
|
|
1388
|
+
"""Add an endpoint to the app that returns all the routes."""
|
|
1389
|
+
if not self.api:
|
|
1390
|
+
return
|
|
1391
|
+
|
|
1392
|
+
@self.api.get(str(constants.Endpoint.ALL_ROUTES))
|
|
1393
|
+
async def all_routes():
|
|
1394
|
+
return list(self._unevaluated_pages.keys())
|
|
1395
|
+
|
|
1214
1396
|
@contextlib.asynccontextmanager
|
|
1215
1397
|
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
|
1216
1398
|
"""Modify the state out of band.
|
|
@@ -1517,7 +1699,7 @@ def upload(app: App):
|
|
|
1517
1699
|
The upload function.
|
|
1518
1700
|
"""
|
|
1519
1701
|
|
|
1520
|
-
async def upload_file(request: Request, files: List[
|
|
1702
|
+
async def upload_file(request: Request, files: List[FastAPIUploadFile]):
|
|
1521
1703
|
"""Upload a file.
|
|
1522
1704
|
|
|
1523
1705
|
Args:
|
|
@@ -1593,7 +1775,8 @@ def upload(app: App):
|
|
|
1593
1775
|
file_copies.append(
|
|
1594
1776
|
UploadFile(
|
|
1595
1777
|
file=content_copy,
|
|
1596
|
-
|
|
1778
|
+
path=Path(file.filename.lstrip("/")) if file.filename else None,
|
|
1779
|
+
_deprecated_filename=file.filename,
|
|
1597
1780
|
size=file.size,
|
|
1598
1781
|
headers=file.headers,
|
|
1599
1782
|
)
|
reflex/base.py
CHANGED
|
@@ -5,15 +5,9 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
from typing import TYPE_CHECKING, Any, List, Type
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from pydantic.v1.fields import ModelField
|
|
12
|
-
except ModuleNotFoundError:
|
|
13
|
-
if not TYPE_CHECKING:
|
|
14
|
-
import pydantic.main as pydantic_main
|
|
15
|
-
from pydantic import BaseModel
|
|
16
|
-
from pydantic.fields import ModelField
|
|
8
|
+
import pydantic.v1.main as pydantic_main
|
|
9
|
+
from pydantic.v1 import BaseModel
|
|
10
|
+
from pydantic.v1.fields import ModelField
|
|
17
11
|
|
|
18
12
|
|
|
19
13
|
def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
|
|
@@ -50,7 +44,7 @@ if TYPE_CHECKING:
|
|
|
50
44
|
from reflex.vars import Var
|
|
51
45
|
|
|
52
46
|
|
|
53
|
-
class Base(BaseModel):
|
|
47
|
+
class Base(BaseModel):
|
|
54
48
|
"""The base class subclassed by all Reflex classes.
|
|
55
49
|
|
|
56
50
|
This class wraps Pydantic and provides common methods such as
|