reflex 0.4.9a2__py3-none-any.whl → 0.5.0a1__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/apps/blank/code/blank.py +19 -16
- reflex/.templates/apps/demo/code/pages/datatable.py +4 -4
- reflex/.templates/apps/demo/code/pages/forms.py +2 -2
- reflex/.templates/web/utils/helpers/debounce.js +17 -0
- reflex/.templates/web/utils/helpers/throttle.js +22 -0
- reflex/.templates/web/utils/state.js +21 -3
- reflex/__init__.py +6 -1
- reflex/__init__.pyi +4 -1
- reflex/app.py +157 -140
- reflex/app_module_for_backend.py +1 -1
- reflex/base.py +13 -15
- reflex/compiler/compiler.py +10 -1
- reflex/compiler/utils.py +3 -30
- reflex/components/__init__.py +1 -0
- reflex/components/chakra/datadisplay/list.py +1 -3
- reflex/components/chakra/datadisplay/list.pyi +3 -3
- reflex/components/chakra/disclosure/accordion.py +1 -1
- reflex/components/chakra/forms/pininput.pyi +1 -1
- reflex/components/chakra/media/icon.py +2 -2
- reflex/components/component.py +279 -32
- reflex/components/core/__init__.py +2 -2
- reflex/components/core/cond.py +1 -10
- reflex/components/core/debounce.py +5 -2
- reflex/components/core/debounce.pyi +4 -2
- reflex/components/core/foreach.py +1 -16
- reflex/components/core/html.py +6 -0
- reflex/components/core/match.py +2 -17
- reflex/components/core/upload.py +42 -1
- reflex/components/core/upload.pyi +199 -1
- reflex/components/datadisplay/code.py +7 -3
- reflex/components/datadisplay/code.pyi +3 -1
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/el/elements/forms.pyi +1 -1
- reflex/components/lucide/icon.py +5 -13
- reflex/components/lucide/icon.pyi +0 -1
- reflex/components/markdown/markdown.py +5 -23
- reflex/components/markdown/markdown.pyi +1 -4
- reflex/components/radix/primitives/accordion.py +227 -406
- reflex/components/radix/primitives/accordion.pyi +369 -28
- reflex/components/radix/primitives/form.py +33 -29
- reflex/components/radix/primitives/form.pyi +7 -2
- reflex/components/radix/primitives/progress.py +17 -9
- reflex/components/radix/primitives/progress.pyi +2 -0
- reflex/components/radix/primitives/slider.py +30 -18
- reflex/components/radix/primitives/slider.pyi +4 -0
- reflex/components/radix/themes/base.py +8 -1
- reflex/components/radix/themes/base.pyi +79 -1
- reflex/components/radix/themes/color_mode.py +74 -30
- reflex/components/radix/themes/color_mode.pyi +26 -185
- reflex/components/radix/themes/components/__init__.py +17 -0
- reflex/components/radix/themes/components/badge.py +2 -1
- reflex/components/radix/themes/components/badge.pyi +3 -1
- reflex/components/radix/themes/components/button.py +3 -1
- reflex/components/radix/themes/components/button.pyi +4 -1
- reflex/components/radix/themes/components/checkbox_cards.py +48 -0
- reflex/components/radix/themes/components/checkbox_cards.pyi +264 -0
- reflex/components/radix/themes/components/checkbox_group.py +42 -0
- reflex/components/radix/themes/components/checkbox_group.pyi +253 -0
- reflex/components/radix/themes/components/data_list.py +63 -0
- reflex/components/radix/themes/components/data_list.pyi +426 -0
- reflex/components/radix/themes/components/icon_button.py +20 -17
- reflex/components/radix/themes/components/icon_button.pyi +5 -1
- reflex/components/radix/themes/components/progress.py +55 -0
- reflex/components/radix/themes/components/progress.pyi +180 -0
- reflex/components/radix/themes/components/radio.py +31 -0
- reflex/components/radix/themes/components/radio.pyi +169 -0
- reflex/components/radix/themes/components/radio_cards.py +48 -0
- reflex/components/radix/themes/components/radio_cards.pyi +264 -0
- reflex/components/radix/themes/components/radio_group.py +2 -4
- reflex/components/radix/themes/components/segmented_control.py +48 -0
- reflex/components/radix/themes/components/segmented_control.pyi +262 -0
- reflex/components/radix/themes/components/skeleton.py +32 -0
- reflex/components/radix/themes/components/skeleton.pyi +106 -0
- reflex/components/radix/themes/components/spinner.py +26 -0
- reflex/components/radix/themes/components/spinner.pyi +101 -0
- reflex/components/radix/themes/components/tabs.py +26 -1
- reflex/components/radix/themes/components/tabs.pyi +69 -9
- reflex/components/radix/themes/components/text_field.py +101 -71
- reflex/components/radix/themes/components/text_field.pyi +81 -499
- reflex/components/radix/themes/layout/base.py +2 -2
- reflex/components/radix/themes/layout/base.pyi +4 -4
- reflex/components/radix/themes/layout/center.py +8 -3
- reflex/components/radix/themes/layout/center.pyi +2 -1
- reflex/components/radix/themes/layout/container.py +30 -2
- reflex/components/radix/themes/layout/container.pyi +9 -30
- reflex/components/radix/themes/layout/list.py +10 -5
- reflex/components/radix/themes/layout/list.pyi +5 -21
- reflex/components/radix/themes/layout/spacer.py +8 -3
- reflex/components/radix/themes/layout/spacer.pyi +2 -1
- reflex/components/radix/themes/layout/stack.py +7 -1
- reflex/components/radix/themes/layout/stack.pyi +3 -3
- reflex/components/radix/themes/typography/link.py +10 -2
- reflex/components/radix/themes/typography/link.pyi +5 -4
- reflex/components/sonner/__init__.py +3 -0
- reflex/components/sonner/toast.py +267 -0
- reflex/components/sonner/toast.pyi +205 -0
- reflex/components/tags/iter_tag.py +9 -6
- reflex/config.py +30 -54
- reflex/constants/__init__.py +0 -2
- reflex/constants/base.py +0 -5
- reflex/constants/colors.py +2 -0
- reflex/constants/installer.py +5 -1
- reflex/constants/route.py +4 -0
- reflex/custom_components/custom_components.py +22 -1
- reflex/event.py +75 -30
- reflex/experimental/__init__.py +5 -0
- reflex/experimental/layout.py +24 -6
- reflex/model.py +2 -1
- reflex/page.py +7 -4
- reflex/reflex.py +8 -3
- reflex/route.py +39 -0
- reflex/state.py +128 -131
- reflex/style.py +20 -1
- reflex/testing.py +10 -6
- reflex/utils/console.py +3 -1
- reflex/utils/exec.py +20 -7
- reflex/utils/format.py +1 -1
- reflex/utils/imports.py +3 -1
- reflex/utils/prerequisites.py +141 -20
- reflex/utils/processes.py +21 -1
- reflex/utils/pyi_generator.py +95 -5
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +26 -4
- reflex/utils/types.py +62 -18
- reflex/vars.py +11 -5
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/METADATA +16 -4
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/RECORD +130 -108
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/WHEEL +1 -1
- reflex/app.pyi +0 -149
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/LICENSE +0 -0
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/entry_points.txt +0 -0
reflex/app.py
CHANGED
|
@@ -63,9 +63,8 @@ from reflex.page import (
|
|
|
63
63
|
DECORATED_PAGES,
|
|
64
64
|
)
|
|
65
65
|
from reflex.route import (
|
|
66
|
-
catchall_in_route,
|
|
67
|
-
catchall_prefix,
|
|
68
66
|
get_route_args,
|
|
67
|
+
replace_brackets_with_keywords,
|
|
69
68
|
verify_route_validity,
|
|
70
69
|
)
|
|
71
70
|
from reflex.state import (
|
|
@@ -102,69 +101,83 @@ class OverlayFragment(Fragment):
|
|
|
102
101
|
|
|
103
102
|
|
|
104
103
|
class App(Base):
|
|
105
|
-
"""
|
|
104
|
+
"""The main Reflex app that encapsulates the backend and frontend.
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
Every Reflex app needs an app defined in its main module.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# app.py
|
|
110
|
+
import reflex as rx
|
|
111
|
+
|
|
112
|
+
# Define state and pages
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
app = rx.App(
|
|
116
|
+
# Set global level style.
|
|
117
|
+
style={...},
|
|
118
|
+
# Set the top level theme.
|
|
119
|
+
theme=rx.theme(accent_color="blue"),
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
# The global [theme](https://reflex.dev/docs/styling/theming/#theme) for the entire app.
|
|
125
|
+
theme: Optional[Component] = themes.theme(accent_color="blue")
|
|
126
|
+
|
|
127
|
+
# The [global style](https://reflex.dev/docs/styling/overview/#global-styles}) for the app.
|
|
128
|
+
style: ComponentStyle = {}
|
|
109
129
|
|
|
110
|
-
# A list of URLs to stylesheets to include in the app.
|
|
130
|
+
# A list of URLs to [stylesheets](https://reflex.dev/docs/styling/custom-stylesheets/) to include in the app.
|
|
111
131
|
stylesheets: List[str] = []
|
|
112
132
|
|
|
113
|
-
#
|
|
114
|
-
|
|
133
|
+
# A component that is present on every page (defaults to the Connection Error banner).
|
|
134
|
+
overlay_component: Optional[
|
|
135
|
+
Union[Component, ComponentCallable]
|
|
136
|
+
] = default_overlay_component
|
|
115
137
|
|
|
116
|
-
#
|
|
138
|
+
# Components to add to the head of every page.
|
|
139
|
+
head_components: List[Component] = []
|
|
140
|
+
|
|
141
|
+
# The Socket.IO AsyncServer instance.
|
|
117
142
|
sio: Optional[AsyncServer] = None
|
|
118
143
|
|
|
119
|
-
# The
|
|
120
|
-
|
|
144
|
+
# The language to add to the html root tag of every page.
|
|
145
|
+
html_lang: Optional[str] = None
|
|
146
|
+
|
|
147
|
+
# Attributes to add to the html root tag of every page.
|
|
148
|
+
html_custom_attrs: Optional[Dict[str, str]] = None
|
|
149
|
+
|
|
150
|
+
# A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
|
|
151
|
+
pages: Dict[str, Component] = {}
|
|
152
|
+
|
|
153
|
+
# The backend API object. PRIVATE.
|
|
154
|
+
api: FastAPI = None # type: ignore
|
|
121
155
|
|
|
122
|
-
# The state class to use for the app.
|
|
156
|
+
# The state class to use for the app. PRIVATE.
|
|
123
157
|
state: Optional[Type[BaseState]] = None
|
|
124
158
|
|
|
125
159
|
# Class to manage many client states.
|
|
126
160
|
_state_manager: Optional[StateManager] = None
|
|
127
161
|
|
|
128
|
-
#
|
|
129
|
-
style: ComponentStyle = {}
|
|
130
|
-
|
|
131
|
-
# Middleware to add to the app.
|
|
162
|
+
# Middleware to add to the app. Users should use `add_middleware`. PRIVATE.
|
|
132
163
|
middleware: List[Middleware] = []
|
|
133
164
|
|
|
134
|
-
#
|
|
165
|
+
# Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
|
|
135
166
|
load_events: Dict[str, List[Union[EventHandler, EventSpec]]] = {}
|
|
136
167
|
|
|
137
|
-
# Admin dashboard
|
|
168
|
+
# Admin dashboard to view and manage the database. PRIVATE.
|
|
138
169
|
admin_dash: Optional[AdminDash] = None
|
|
139
170
|
|
|
140
|
-
# The async server name space
|
|
171
|
+
# The async server name space. PRIVATE.
|
|
141
172
|
event_namespace: Optional[EventNamespace] = None
|
|
142
173
|
|
|
143
|
-
#
|
|
144
|
-
head_components: List[Component] = []
|
|
145
|
-
|
|
146
|
-
# The language to add to the html root tag of every page.
|
|
147
|
-
html_lang: Optional[str] = None
|
|
148
|
-
|
|
149
|
-
# Attributes to add to the html root tag of every page.
|
|
150
|
-
html_custom_attrs: Optional[Dict[str, str]] = None
|
|
151
|
-
|
|
152
|
-
# A component that is present on every page.
|
|
153
|
-
overlay_component: Optional[
|
|
154
|
-
Union[Component, ComponentCallable]
|
|
155
|
-
] = default_overlay_component
|
|
156
|
-
|
|
157
|
-
# Background tasks that are currently running
|
|
174
|
+
# Background tasks that are currently running. PRIVATE.
|
|
158
175
|
background_tasks: Set[asyncio.Task] = set()
|
|
159
176
|
|
|
160
|
-
|
|
161
|
-
theme: Optional[Component] = themes.theme(accent_color="blue")
|
|
162
|
-
|
|
163
|
-
def __init__(self, *args, **kwargs):
|
|
177
|
+
def __init__(self, **kwargs):
|
|
164
178
|
"""Initialize the app.
|
|
165
179
|
|
|
166
180
|
Args:
|
|
167
|
-
*args: Args to initialize the app with.
|
|
168
181
|
**kwargs: Kwargs to initialize the app with.
|
|
169
182
|
|
|
170
183
|
Raises:
|
|
@@ -176,45 +189,41 @@ class App(Base):
|
|
|
176
189
|
raise ValueError(
|
|
177
190
|
"`connect_error_component` is deprecated, use `overlay_component` instead"
|
|
178
191
|
)
|
|
179
|
-
super().__init__(
|
|
192
|
+
super().__init__(**kwargs)
|
|
180
193
|
base_state_subclasses = BaseState.__subclasses__()
|
|
181
194
|
|
|
182
195
|
# Special case to allow test cases have multiple subclasses of rx.BaseState.
|
|
183
|
-
if not is_testing_env():
|
|
196
|
+
if not is_testing_env() and len(base_state_subclasses) > 1:
|
|
184
197
|
# Only one Base State class is allowed.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
)
|
|
198
|
+
raise ValueError(
|
|
199
|
+
"rx.BaseState cannot be subclassed multiple times. use rx.State instead"
|
|
200
|
+
)
|
|
189
201
|
|
|
190
|
-
if "state" in kwargs:
|
|
191
|
-
console.deprecate(
|
|
192
|
-
feature_name="`state` argument for App()",
|
|
193
|
-
reason="due to all `rx.State` subclasses being inferred.",
|
|
194
|
-
deprecation_version="0.3.5",
|
|
195
|
-
removal_version="0.5.0",
|
|
196
|
-
)
|
|
197
202
|
# Add middleware.
|
|
198
203
|
self.middleware.append(HydrateMiddleware())
|
|
199
204
|
|
|
200
205
|
# Set up the API.
|
|
201
206
|
self.api = FastAPI()
|
|
202
|
-
self.
|
|
203
|
-
self.
|
|
207
|
+
self._add_cors()
|
|
208
|
+
self._add_default_endpoints()
|
|
204
209
|
|
|
205
|
-
self.
|
|
210
|
+
self._setup_state()
|
|
206
211
|
|
|
207
212
|
# Set up the admin dash.
|
|
208
|
-
self.
|
|
213
|
+
self._setup_admin_dash()
|
|
209
214
|
|
|
210
|
-
def
|
|
215
|
+
def _enable_state(self) -> None:
|
|
211
216
|
"""Enable state for the app."""
|
|
212
217
|
if not self.state:
|
|
213
218
|
self.state = State
|
|
214
|
-
self.
|
|
219
|
+
self._setup_state()
|
|
220
|
+
|
|
221
|
+
def _setup_state(self) -> None:
|
|
222
|
+
"""Set up the state for the app.
|
|
215
223
|
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
Raises:
|
|
225
|
+
RuntimeError: If the socket server is invalid.
|
|
226
|
+
"""
|
|
218
227
|
if not self.state:
|
|
219
228
|
return
|
|
220
229
|
|
|
@@ -224,21 +233,26 @@ class App(Base):
|
|
|
224
233
|
self._state_manager = StateManager.create(state=self.state)
|
|
225
234
|
|
|
226
235
|
# Set up the Socket.IO AsyncServer.
|
|
227
|
-
self.sio
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
if not self.sio:
|
|
237
|
+
self.sio = AsyncServer(
|
|
238
|
+
async_mode="asgi",
|
|
239
|
+
cors_allowed_origins=(
|
|
240
|
+
"*"
|
|
241
|
+
if config.cors_allowed_origins == ["*"]
|
|
242
|
+
else config.cors_allowed_origins
|
|
243
|
+
),
|
|
244
|
+
cors_credentials=True,
|
|
245
|
+
max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
|
|
246
|
+
ping_interval=constants.Ping.INTERVAL,
|
|
247
|
+
ping_timeout=constants.Ping.TIMEOUT,
|
|
248
|
+
)
|
|
249
|
+
elif getattr(self.sio, "async_mode", "") != "asgi":
|
|
250
|
+
raise RuntimeError(
|
|
251
|
+
f"Custom `sio` must use `async_mode='asgi'`, not '{self.sio.async_mode}'."
|
|
252
|
+
)
|
|
239
253
|
|
|
240
254
|
# Create the socket app. Note event endpoint constant replaces the default 'socket.io' path.
|
|
241
|
-
|
|
255
|
+
socket_app = ASGIApp(self.sio, socketio_path="")
|
|
242
256
|
namespace = config.get_event_namespace()
|
|
243
257
|
|
|
244
258
|
# Create the event namespace and attach the main app. Not related to any paths.
|
|
@@ -247,7 +261,7 @@ class App(Base):
|
|
|
247
261
|
# Register the event namespace with the socket.
|
|
248
262
|
self.sio.register_namespace(self.event_namespace)
|
|
249
263
|
# Mount the socket app with the API.
|
|
250
|
-
self.api.mount(str(constants.Endpoint.EVENT),
|
|
264
|
+
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
|
|
251
265
|
|
|
252
266
|
def __repr__(self) -> str:
|
|
253
267
|
"""Get the string representation of the app.
|
|
@@ -265,12 +279,12 @@ class App(Base):
|
|
|
265
279
|
"""
|
|
266
280
|
return self.api
|
|
267
281
|
|
|
268
|
-
def
|
|
282
|
+
def _add_default_endpoints(self):
|
|
269
283
|
"""Add default api endpoints (ping)."""
|
|
270
284
|
# To test the server.
|
|
271
285
|
self.api.get(str(constants.Endpoint.PING))(ping)
|
|
272
286
|
|
|
273
|
-
def
|
|
287
|
+
def _add_optional_endpoints(self):
|
|
274
288
|
"""Add optional api endpoints (_upload)."""
|
|
275
289
|
# To upload files.
|
|
276
290
|
if Upload.is_used:
|
|
@@ -283,7 +297,7 @@ class App(Base):
|
|
|
283
297
|
name="uploaded_files",
|
|
284
298
|
)
|
|
285
299
|
|
|
286
|
-
def
|
|
300
|
+
def _add_cors(self):
|
|
287
301
|
"""Add CORS middleware to the app."""
|
|
288
302
|
self.api.add_middleware(
|
|
289
303
|
cors.CORSMiddleware,
|
|
@@ -307,7 +321,7 @@ class App(Base):
|
|
|
307
321
|
raise ValueError("The state manager has not been initialized.")
|
|
308
322
|
return self._state_manager
|
|
309
323
|
|
|
310
|
-
async def
|
|
324
|
+
async def _preprocess(self, state: BaseState, event: Event) -> StateUpdate | None:
|
|
311
325
|
"""Preprocess the event.
|
|
312
326
|
|
|
313
327
|
This is where middleware can modify the event before it is processed.
|
|
@@ -331,7 +345,7 @@ class App(Base):
|
|
|
331
345
|
if out is not None:
|
|
332
346
|
return out # type: ignore
|
|
333
347
|
|
|
334
|
-
async def
|
|
348
|
+
async def _postprocess(
|
|
335
349
|
self, state: BaseState, event: Event, update: StateUpdate
|
|
336
350
|
) -> StateUpdate:
|
|
337
351
|
"""Postprocess the event.
|
|
@@ -350,11 +364,17 @@ class App(Base):
|
|
|
350
364
|
for middleware in self.middleware:
|
|
351
365
|
if asyncio.iscoroutinefunction(middleware.postprocess):
|
|
352
366
|
out = await middleware.postprocess(
|
|
353
|
-
app=self,
|
|
367
|
+
app=self, # type: ignore
|
|
368
|
+
state=state,
|
|
369
|
+
event=event,
|
|
370
|
+
update=update,
|
|
354
371
|
)
|
|
355
372
|
else:
|
|
356
373
|
out = middleware.postprocess(
|
|
357
|
-
app=self,
|
|
374
|
+
app=self, # type: ignore
|
|
375
|
+
state=state,
|
|
376
|
+
event=event,
|
|
377
|
+
update=update,
|
|
358
378
|
)
|
|
359
379
|
if out is not None:
|
|
360
380
|
return out # type: ignore
|
|
@@ -411,7 +431,6 @@ class App(Base):
|
|
|
411
431
|
EventHandler | EventSpec | list[EventHandler | EventSpec] | None
|
|
412
432
|
) = None,
|
|
413
433
|
meta: list[dict[str, str]] = constants.DefaultPage.META_LIST,
|
|
414
|
-
script_tags: list[Component] | None = None,
|
|
415
434
|
):
|
|
416
435
|
"""Add a page to the app.
|
|
417
436
|
|
|
@@ -426,7 +445,9 @@ class App(Base):
|
|
|
426
445
|
image: The image to display on the page.
|
|
427
446
|
on_load: The event handler(s) that will be called each time the page load.
|
|
428
447
|
meta: The metadata of the page.
|
|
429
|
-
|
|
448
|
+
|
|
449
|
+
Raises:
|
|
450
|
+
ValueError: When the specified route name already exists.
|
|
430
451
|
"""
|
|
431
452
|
# If the route is not set, get it from the callable.
|
|
432
453
|
if route is None:
|
|
@@ -441,6 +462,23 @@ class App(Base):
|
|
|
441
462
|
# Check if the route given is valid
|
|
442
463
|
verify_route_validity(route)
|
|
443
464
|
|
|
465
|
+
if route in self.pages and os.getenv(constants.RELOAD_CONFIG):
|
|
466
|
+
# when the app is reloaded(typically for app harness tests), we should maintain
|
|
467
|
+
# the latest render function of a route.This applies typically to decorated pages
|
|
468
|
+
# since they are only added when app._compile is called.
|
|
469
|
+
self.pages.pop(route)
|
|
470
|
+
|
|
471
|
+
if route in self.pages:
|
|
472
|
+
route_name = (
|
|
473
|
+
f"`{route}` or `/`"
|
|
474
|
+
if route == constants.PageNames.INDEX_ROUTE
|
|
475
|
+
else f"`{route}`"
|
|
476
|
+
)
|
|
477
|
+
raise ValueError(
|
|
478
|
+
f"Duplicate page route {route_name} already exists. Make sure you do not have two"
|
|
479
|
+
f" pages with the same route"
|
|
480
|
+
)
|
|
481
|
+
|
|
444
482
|
# Setup dynamic args for the route.
|
|
445
483
|
# this state assignment is only required for tests using the deprecated state kwarg for App
|
|
446
484
|
state = self.state if self.state else State
|
|
@@ -456,14 +494,14 @@ class App(Base):
|
|
|
456
494
|
# Ensure state is enabled if this page uses state.
|
|
457
495
|
if self.state is None:
|
|
458
496
|
if on_load or component._has_event_triggers():
|
|
459
|
-
self.
|
|
497
|
+
self._enable_state()
|
|
460
498
|
else:
|
|
461
499
|
for var in component._get_vars(include_children=True):
|
|
462
500
|
if not var._var_data:
|
|
463
501
|
continue
|
|
464
502
|
if not var._var_data.state:
|
|
465
503
|
continue
|
|
466
|
-
self.
|
|
504
|
+
self._enable_state()
|
|
467
505
|
break
|
|
468
506
|
|
|
469
507
|
component = OverlayFragment.create(component)
|
|
@@ -487,16 +525,6 @@ class App(Base):
|
|
|
487
525
|
**meta_args,
|
|
488
526
|
)
|
|
489
527
|
|
|
490
|
-
# Add script tags if given
|
|
491
|
-
if script_tags:
|
|
492
|
-
console.deprecate(
|
|
493
|
-
feature_name="Passing script tags to add_page",
|
|
494
|
-
reason="Add script components as children to the page component instead",
|
|
495
|
-
deprecation_version="0.2.9",
|
|
496
|
-
removal_version="0.5.0",
|
|
497
|
-
)
|
|
498
|
-
component.children.extend(script_tags)
|
|
499
|
-
|
|
500
528
|
# Add the page.
|
|
501
529
|
self._check_routes_conflict(route)
|
|
502
530
|
self.pages[route] = component
|
|
@@ -532,27 +560,31 @@ class App(Base):
|
|
|
532
560
|
Args:
|
|
533
561
|
new_route: the route being newly added.
|
|
534
562
|
"""
|
|
535
|
-
|
|
536
|
-
if not newroute_catchall:
|
|
563
|
+
if "[" not in new_route:
|
|
537
564
|
return
|
|
538
565
|
|
|
566
|
+
segments = (
|
|
567
|
+
constants.RouteRegex.SINGLE_SEGMENT,
|
|
568
|
+
constants.RouteRegex.DOUBLE_SEGMENT,
|
|
569
|
+
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
|
570
|
+
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
|
571
|
+
)
|
|
539
572
|
for route in self.pages:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
raise ValueError(
|
|
544
|
-
f"You cannot define a route with the same specificity as a optional catch-all route ('{route}' and '{new_route}')"
|
|
545
|
-
)
|
|
546
|
-
|
|
547
|
-
route_catchall = catchall_in_route(route)
|
|
548
|
-
if (
|
|
549
|
-
route_catchall
|
|
550
|
-
and newroute_catchall
|
|
551
|
-
and catchall_prefix(route) == catchall_prefix(new_route)
|
|
573
|
+
replaced_route = replace_brackets_with_keywords(route)
|
|
574
|
+
for rw, r, nr in zip(
|
|
575
|
+
replaced_route.split("/"), route.split("/"), new_route.split("/")
|
|
552
576
|
):
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
577
|
+
if rw in segments and r != nr:
|
|
578
|
+
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
|
579
|
+
raise ValueError(
|
|
580
|
+
f"You cannot use different slug names for the same dynamic path in {route} and {new_route} ('{r}' != '{nr}')"
|
|
581
|
+
)
|
|
582
|
+
elif rw not in segments and r != nr:
|
|
583
|
+
# if the section being compared in both routes is not a dynamic segment(i.e not wrapped in brackets)
|
|
584
|
+
# then we are guaranteed that the route is valid and there's no need checking the rest.
|
|
585
|
+
# eg. /posts/[id]/info/[slug1] and /posts/[id]/info1/[slug1] is always going to be valid since
|
|
586
|
+
# info1 will break away into its own tree.
|
|
587
|
+
break
|
|
556
588
|
|
|
557
589
|
def add_custom_404_page(
|
|
558
590
|
self,
|
|
@@ -568,7 +600,7 @@ class App(Base):
|
|
|
568
600
|
"""Define a custom 404 page for any url having no match.
|
|
569
601
|
|
|
570
602
|
If there is no page defined on 'index' route, add the 404 page to it.
|
|
571
|
-
If there is no global catchall defined, add the 404 page with a catchall
|
|
603
|
+
If there is no global catchall defined, add the 404 page with a catchall.
|
|
572
604
|
|
|
573
605
|
Args:
|
|
574
606
|
component: The component to display at the page.
|
|
@@ -590,7 +622,7 @@ class App(Base):
|
|
|
590
622
|
meta=meta,
|
|
591
623
|
)
|
|
592
624
|
|
|
593
|
-
def
|
|
625
|
+
def _setup_admin_dash(self):
|
|
594
626
|
"""Setup the admin dash."""
|
|
595
627
|
# Get the admin dash.
|
|
596
628
|
admin_dash = self.admin_dash
|
|
@@ -613,14 +645,14 @@ class App(Base):
|
|
|
613
645
|
|
|
614
646
|
admin.mount_to(self.api)
|
|
615
647
|
|
|
616
|
-
def
|
|
648
|
+
def _get_frontend_packages(self, imports: Dict[str, set[ImportVar]]):
|
|
617
649
|
"""Gets the frontend packages to be installed and filters out the unnecessary ones.
|
|
618
650
|
|
|
619
651
|
Args:
|
|
620
652
|
imports: A dictionary containing the imports used in the current page.
|
|
621
653
|
|
|
622
654
|
Example:
|
|
623
|
-
>>>
|
|
655
|
+
>>> _get_frontend_packages({"react": "16.14.0", "react-dom": "16.14.0"})
|
|
624
656
|
"""
|
|
625
657
|
page_imports = {
|
|
626
658
|
i
|
|
@@ -703,19 +735,6 @@ class App(Base):
|
|
|
703
735
|
for k, component in self.pages.items():
|
|
704
736
|
self.pages[k] = self._add_overlay_to_component(component)
|
|
705
737
|
|
|
706
|
-
def compile(self):
|
|
707
|
-
"""compile_() is the new function for performing compilation.
|
|
708
|
-
Reflex framework will call it automatically as needed.
|
|
709
|
-
"""
|
|
710
|
-
console.deprecate(
|
|
711
|
-
feature_name="app.compile()",
|
|
712
|
-
reason="Explicit calls to app.compile() are not needed."
|
|
713
|
-
" Method will be removed in 0.4.0",
|
|
714
|
-
deprecation_version="0.3.8",
|
|
715
|
-
removal_version="0.5.0",
|
|
716
|
-
)
|
|
717
|
-
return
|
|
718
|
-
|
|
719
738
|
def _apply_decorated_pages(self):
|
|
720
739
|
"""Add @rx.page decorated pages to the app.
|
|
721
740
|
|
|
@@ -726,10 +745,10 @@ class App(Base):
|
|
|
726
745
|
This can move back into `compile_` when py39 support is dropped.
|
|
727
746
|
"""
|
|
728
747
|
# Add the @rx.page decorated pages to collect on_load events.
|
|
729
|
-
for render, kwargs in DECORATED_PAGES:
|
|
748
|
+
for render, kwargs in DECORATED_PAGES[get_config().app_name]:
|
|
730
749
|
self.add_page(render, **kwargs)
|
|
731
750
|
|
|
732
|
-
def
|
|
751
|
+
def _compile(self, export: bool = False):
|
|
733
752
|
"""Compile the app and output it to the pages folder.
|
|
734
753
|
|
|
735
754
|
Args:
|
|
@@ -743,7 +762,7 @@ class App(Base):
|
|
|
743
762
|
self.add_custom_404_page()
|
|
744
763
|
|
|
745
764
|
# Add the optional endpoints (_upload)
|
|
746
|
-
self.
|
|
765
|
+
self._add_optional_endpoints()
|
|
747
766
|
|
|
748
767
|
if not self._should_compile():
|
|
749
768
|
return
|
|
@@ -794,9 +813,7 @@ class App(Base):
|
|
|
794
813
|
|
|
795
814
|
for _route, component in self.pages.items():
|
|
796
815
|
# Merge the component style with the app style.
|
|
797
|
-
component._add_style_recursive(self.style)
|
|
798
|
-
|
|
799
|
-
component.apply_theme(self.theme)
|
|
816
|
+
component._add_style_recursive(self.style, self.theme)
|
|
800
817
|
|
|
801
818
|
# Add component._get_all_imports() to all_imports.
|
|
802
819
|
all_imports.update(component._get_all_imports())
|
|
@@ -941,7 +958,7 @@ class App(Base):
|
|
|
941
958
|
progress.stop()
|
|
942
959
|
|
|
943
960
|
# Install frontend packages.
|
|
944
|
-
self.
|
|
961
|
+
self._get_frontend_packages(all_imports)
|
|
945
962
|
|
|
946
963
|
# Setup the next.config.js
|
|
947
964
|
transpile_packages = [
|
|
@@ -1016,7 +1033,7 @@ class App(Base):
|
|
|
1016
1033
|
handler=handler, state=substate, payload=event.payload
|
|
1017
1034
|
):
|
|
1018
1035
|
# Postprocess the event.
|
|
1019
|
-
update = await self.
|
|
1036
|
+
update = await self._postprocess(state, event, update)
|
|
1020
1037
|
|
|
1021
1038
|
# Send the update to the client.
|
|
1022
1039
|
await self.event_namespace.emit_update(
|
|
@@ -1067,7 +1084,7 @@ async def process(
|
|
|
1067
1084
|
state.router = RouterData(router_data)
|
|
1068
1085
|
|
|
1069
1086
|
# Preprocess the event.
|
|
1070
|
-
update = await app.
|
|
1087
|
+
update = await app._preprocess(state, event)
|
|
1071
1088
|
|
|
1072
1089
|
# If there was an update, yield it.
|
|
1073
1090
|
if update is not None:
|
|
@@ -1083,7 +1100,7 @@ async def process(
|
|
|
1083
1100
|
# Process the event synchronously.
|
|
1084
1101
|
async for update in state._process(event):
|
|
1085
1102
|
# Postprocess the event.
|
|
1086
|
-
update = await app.
|
|
1103
|
+
update = await app._postprocess(state, event, update)
|
|
1087
1104
|
|
|
1088
1105
|
# Yield the update.
|
|
1089
1106
|
yield update
|
|
@@ -1204,7 +1221,7 @@ def upload(app: App):
|
|
|
1204
1221
|
async with app.state_manager.modify_state(event.substate_token) as state:
|
|
1205
1222
|
async for update in state._process(event):
|
|
1206
1223
|
# Postprocess the event.
|
|
1207
|
-
update = await app.
|
|
1224
|
+
update = await app._postprocess(state, event, update)
|
|
1208
1225
|
yield update.json() + "\n"
|
|
1209
1226
|
|
|
1210
1227
|
# Stream updates to client
|
reflex/app_module_for_backend.py
CHANGED
|
@@ -15,7 +15,7 @@ app = getattr(app_module, constants.CompileVars.APP)
|
|
|
15
15
|
# For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages
|
|
16
16
|
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
|
17
17
|
app._apply_decorated_pages()
|
|
18
|
-
compile_future = ThreadPoolExecutor(max_workers=1).submit(app.
|
|
18
|
+
compile_future = ThreadPoolExecutor(max_workers=1).submit(app._compile)
|
|
19
19
|
compile_future.add_done_callback(
|
|
20
20
|
# Force background compile errors to print eagerly
|
|
21
21
|
lambda f: f.result()
|
reflex/base.py
CHANGED
|
@@ -5,19 +5,14 @@ import os
|
|
|
5
5
|
from typing import TYPE_CHECKING, Any, List, Type
|
|
6
6
|
|
|
7
7
|
try:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if not TYPE_CHECKING:
|
|
12
|
-
import pydantic.v1 as pydantic
|
|
13
|
-
from pydantic.v1 import BaseModel
|
|
14
|
-
from pydantic.v1.fields import ModelField
|
|
15
|
-
else:
|
|
16
|
-
raise ModuleNotFoundError
|
|
8
|
+
import pydantic.v1 as pydantic
|
|
9
|
+
from pydantic.v1 import BaseModel
|
|
10
|
+
from pydantic.v1.fields import ModelField
|
|
17
11
|
except ModuleNotFoundError:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
if not TYPE_CHECKING:
|
|
13
|
+
import pydantic
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
from pydantic.fields import ModelField # type: ignore
|
|
21
16
|
|
|
22
17
|
|
|
23
18
|
from reflex import constants
|
|
@@ -50,7 +45,7 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
|
|
|
50
45
|
pydantic.main.validate_field_name = validate_field_name # type: ignore
|
|
51
46
|
|
|
52
47
|
|
|
53
|
-
class Base(pydantic.BaseModel):
|
|
48
|
+
class Base(pydantic.BaseModel): # pyright: ignore [reportUnboundVariable]
|
|
54
49
|
"""The base class subclassed by all Reflex classes.
|
|
55
50
|
|
|
56
51
|
This class wraps Pydantic and provides common methods such as
|
|
@@ -75,7 +70,10 @@ class Base(pydantic.BaseModel):
|
|
|
75
70
|
"""
|
|
76
71
|
from reflex.utils.serializers import serialize
|
|
77
72
|
|
|
78
|
-
return self.__config__.json_dumps(
|
|
73
|
+
return self.__config__.json_dumps( # type: ignore
|
|
74
|
+
self.dict(),
|
|
75
|
+
default=serialize,
|
|
76
|
+
)
|
|
79
77
|
|
|
80
78
|
def set(self, **kwargs):
|
|
81
79
|
"""Set multiple fields and return the object.
|
|
@@ -114,7 +112,7 @@ class Base(pydantic.BaseModel):
|
|
|
114
112
|
value=default_value,
|
|
115
113
|
annotation=var._var_type,
|
|
116
114
|
class_validators=None,
|
|
117
|
-
config=cls.__config__,
|
|
115
|
+
config=cls.__config__, # type: ignore
|
|
118
116
|
)
|
|
119
117
|
cls.__fields__.update({var._var_name: new_field})
|
|
120
118
|
|
reflex/compiler/compiler.py
CHANGED
|
@@ -174,7 +174,7 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
|
|
|
174
174
|
return templates.STYLE.render(stylesheets=sheets)
|
|
175
175
|
|
|
176
176
|
|
|
177
|
-
def _compile_component(component: Component) -> str:
|
|
177
|
+
def _compile_component(component: Component | StatefulComponent) -> str:
|
|
178
178
|
"""Compile a single component.
|
|
179
179
|
|
|
180
180
|
Args:
|
|
@@ -263,9 +263,18 @@ def _compile_stateful_components(
|
|
|
263
263
|
# Reset this flag to render the actual component.
|
|
264
264
|
component.rendered_as_shared = False
|
|
265
265
|
|
|
266
|
+
# Include dynamic imports in the shared component.
|
|
267
|
+
if dynamic_imports := component._get_all_dynamic_imports():
|
|
268
|
+
rendered_components.update(
|
|
269
|
+
{dynamic_import: None for dynamic_import in dynamic_imports}
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Include custom code in the shared component.
|
|
266
273
|
rendered_components.update(
|
|
267
274
|
{code: None for code in component._get_all_custom_code()},
|
|
268
275
|
)
|
|
276
|
+
|
|
277
|
+
# Include all imports in the shared component.
|
|
269
278
|
all_import_dicts.append(component._get_all_imports())
|
|
270
279
|
|
|
271
280
|
# Indicate that this component now imports from the shared file.
|