reflex 0.6.8a1__py3-none-any.whl → 0.7.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/jinja/custom_components/pyproject.toml.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +7 -7
- reflex/.templates/jinja/web/pages/utils.js.jinja2 +2 -2
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -4
- reflex/.templates/web/utils/state.js +65 -36
- reflex/__init__.py +4 -17
- reflex/__init__.pyi +1 -2
- reflex/app.py +244 -115
- reflex/app_mixins/lifespan.py +9 -9
- reflex/app_mixins/middleware.py +6 -6
- reflex/app_module_for_backend.py +3 -7
- reflex/base.py +7 -7
- reflex/compiler/compiler.py +8 -0
- reflex/compiler/utils.py +35 -6
- reflex/components/base/bare.py +1 -1
- reflex/components/base/error_boundary.py +2 -1
- reflex/components/base/error_boundary.pyi +2 -1
- reflex/components/base/meta.py +2 -2
- reflex/components/base/strict_mode.py +10 -0
- reflex/components/base/strict_mode.pyi +57 -0
- reflex/components/component.py +38 -77
- reflex/components/core/banner.py +83 -4
- reflex/components/core/banner.pyi +86 -0
- reflex/components/core/breakpoints.py +3 -1
- reflex/components/core/client_side_routing.py +1 -1
- reflex/components/core/client_side_routing.pyi +1 -1
- reflex/components/core/cond.py +9 -10
- reflex/components/core/debounce.py +1 -1
- reflex/components/core/foreach.py +23 -3
- reflex/components/core/html.py +1 -1
- reflex/components/core/match.py +5 -5
- reflex/components/core/sticky.py +160 -0
- reflex/components/core/sticky.pyi +449 -0
- reflex/components/core/upload.py +2 -2
- reflex/components/datadisplay/code.py +5 -14
- reflex/components/datadisplay/dataeditor.py +7 -4
- reflex/components/datadisplay/logo.py +13 -8
- reflex/components/datadisplay/shiki_code_block.py +14 -9
- reflex/components/dynamic.py +22 -3
- reflex/components/el/constants/reflex.py +1 -1
- reflex/components/el/element.py +1 -1
- reflex/components/el/elements/forms.py +4 -4
- reflex/components/el/elements/forms.pyi +4 -4
- reflex/components/lucide/icon.py +46 -8
- reflex/components/lucide/icon.pyi +54 -0
- reflex/components/markdown/markdown.py +10 -8
- reflex/components/moment/moment.py +2 -2
- reflex/components/next/image.py +16 -4
- reflex/components/next/image.pyi +4 -2
- reflex/components/next/link.py +1 -1
- reflex/components/plotly/plotly.py +5 -5
- reflex/components/props.py +3 -3
- reflex/components/radix/__init__.pyi +1 -1
- reflex/components/radix/primitives/accordion.py +9 -5
- reflex/components/radix/primitives/accordion.pyi +3 -1
- reflex/components/radix/primitives/drawer.py +5 -2
- reflex/components/radix/primitives/drawer.pyi +4 -4
- reflex/components/radix/primitives/form.pyi +6 -6
- reflex/components/radix/primitives/progress.py +1 -1
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/radix/themes/color_mode.py +11 -9
- reflex/components/radix/themes/components/alert_dialog.py +3 -0
- reflex/components/radix/themes/components/card.py +1 -1
- reflex/components/radix/themes/components/card.pyi +1 -1
- reflex/components/radix/themes/components/context_menu.py +5 -0
- reflex/components/radix/themes/components/dialog.py +3 -0
- reflex/components/radix/themes/components/dropdown_menu.py +5 -0
- reflex/components/radix/themes/components/hover_card.py +3 -0
- reflex/components/radix/themes/components/icon_button.py +2 -2
- reflex/components/radix/themes/components/icon_button.pyi +1 -0
- reflex/components/radix/themes/components/popover.py +3 -0
- reflex/components/radix/themes/components/radio_cards.py +2 -0
- reflex/components/radix/themes/components/radio_group.py +1 -1
- reflex/components/radix/themes/components/select.py +3 -0
- reflex/components/radix/themes/components/tabs.py +3 -0
- reflex/components/radix/themes/components/text_area.py +12 -0
- reflex/components/radix/themes/components/text_area.pyi +2 -0
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/tooltip.py +3 -1
- reflex/components/radix/themes/components/tooltip.pyi +1 -0
- reflex/components/radix/themes/layout/__init__.pyi +1 -1
- reflex/components/radix/themes/layout/list.py +2 -2
- reflex/components/radix/themes/layout/stack.py +2 -2
- reflex/components/radix/themes/typography/link.py +1 -1
- reflex/components/radix/themes/typography/text.py +2 -2
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/recharts/__init__.py +2 -0
- reflex/components/recharts/__init__.pyi +2 -0
- reflex/components/recharts/charts.py +15 -15
- reflex/components/recharts/general.py +19 -4
- reflex/components/recharts/general.pyi +55 -4
- reflex/components/recharts/polar.py +2 -2
- reflex/components/recharts/recharts.py +4 -4
- reflex/components/sonner/toast.py +15 -13
- reflex/components/sonner/toast.pyi +6 -6
- reflex/components/suneditor/editor.py +6 -4
- reflex/components/suneditor/editor.pyi +2 -2
- reflex/components/tags/iter_tag.py +3 -3
- reflex/components/tags/tag.py +25 -3
- reflex/config.py +48 -20
- reflex/constants/__init__.py +1 -0
- reflex/constants/base.py +4 -1
- reflex/constants/compiler.py +5 -2
- reflex/constants/config.py +8 -1
- reflex/constants/installer.py +9 -9
- reflex/constants/style.py +1 -1
- reflex/custom_components/custom_components.py +9 -7
- reflex/event.py +137 -163
- reflex/experimental/__init__.py +19 -11
- reflex/experimental/client_state.py +53 -28
- reflex/experimental/hooks.py +5 -5
- reflex/experimental/layout.py +8 -5
- reflex/experimental/layout.pyi +1 -1
- reflex/experimental/misc.py +3 -3
- reflex/istate/wrappers.py +1 -1
- reflex/middleware/hydrate_middleware.py +2 -2
- reflex/model.py +11 -6
- reflex/page.py +3 -3
- reflex/reflex.py +90 -19
- reflex/route.py +1 -1
- reflex/state.py +358 -401
- reflex/style.py +27 -3
- reflex/testing.py +34 -39
- reflex/utils/build.py +6 -2
- reflex/utils/codespaces.py +1 -4
- reflex/utils/compat.py +6 -5
- reflex/utils/console.py +52 -21
- reflex/utils/exceptions.py +76 -26
- reflex/utils/exec.py +69 -74
- reflex/utils/export.py +6 -1
- reflex/utils/format.py +7 -39
- reflex/utils/imports.py +2 -2
- reflex/utils/lazy_loader.py +7 -1
- reflex/utils/path_ops.py +28 -14
- reflex/utils/prerequisites.py +324 -65
- reflex/utils/processes.py +45 -32
- reflex/utils/pyi_generator.py +30 -25
- reflex/utils/registry.py +4 -4
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +5 -4
- reflex/utils/types.py +42 -18
- reflex/vars/base.py +650 -333
- reflex/vars/datetime.py +6 -7
- reflex/vars/dep_tracking.py +344 -0
- reflex/vars/function.py +11 -5
- reflex/vars/number.py +31 -43
- reflex/vars/object.py +63 -62
- reflex/vars/sequence.py +79 -67
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/METADATA +7 -10
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/RECORD +153 -150
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/WHEEL +1 -1
- reflex/experimental/assets.py +0 -37
- reflex/proxy.py +0 -119
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/LICENSE +0 -0
- {reflex-0.6.8a1.dist-info → reflex-0.7.0a1.dist-info}/entry_points.txt +0 -0
reflex/app.py
CHANGED
|
@@ -27,6 +27,7 @@ from typing import (
|
|
|
27
27
|
Dict,
|
|
28
28
|
Generic,
|
|
29
29
|
List,
|
|
30
|
+
MutableMapping,
|
|
30
31
|
Optional,
|
|
31
32
|
Set,
|
|
32
33
|
Type,
|
|
@@ -53,21 +54,28 @@ from reflex.compiler.compiler import ExecutorSafeFunctions, compile_theme
|
|
|
53
54
|
from reflex.components.base.app_wrap import AppWrap
|
|
54
55
|
from reflex.components.base.error_boundary import ErrorBoundary
|
|
55
56
|
from reflex.components.base.fragment import Fragment
|
|
57
|
+
from reflex.components.base.strict_mode import StrictMode
|
|
56
58
|
from reflex.components.component import (
|
|
57
59
|
Component,
|
|
58
60
|
ComponentStyle,
|
|
59
61
|
evaluate_style_namespaces,
|
|
60
62
|
)
|
|
61
|
-
from reflex.components.core.banner import
|
|
63
|
+
from reflex.components.core.banner import (
|
|
64
|
+
backend_disabled,
|
|
65
|
+
connection_pulser,
|
|
66
|
+
connection_toaster,
|
|
67
|
+
)
|
|
62
68
|
from reflex.components.core.breakpoints import set_breakpoints
|
|
63
69
|
from reflex.components.core.client_side_routing import (
|
|
64
70
|
Default404Page,
|
|
65
71
|
wait_for_client_redirect,
|
|
66
72
|
)
|
|
73
|
+
from reflex.components.core.sticky import sticky
|
|
67
74
|
from reflex.components.core.upload import Upload, get_upload_dir
|
|
68
75
|
from reflex.components.radix import themes
|
|
69
76
|
from reflex.config import environment, get_config
|
|
70
77
|
from reflex.event import (
|
|
78
|
+
_EVENT_FIELDS,
|
|
71
79
|
BASE_STATE,
|
|
72
80
|
Event,
|
|
73
81
|
EventHandler,
|
|
@@ -144,7 +152,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
|
|
144
152
|
position="top-center",
|
|
145
153
|
id="backend_error",
|
|
146
154
|
style={"width": "500px"},
|
|
147
|
-
)
|
|
155
|
+
)
|
|
148
156
|
else:
|
|
149
157
|
error_message.insert(0, "An error occurred.")
|
|
150
158
|
return window_alert("\n".join(error_message))
|
|
@@ -156,9 +164,12 @@ def default_overlay_component() -> Component:
|
|
|
156
164
|
Returns:
|
|
157
165
|
The default overlay_component, which is a connection_modal.
|
|
158
166
|
"""
|
|
167
|
+
config = get_config()
|
|
168
|
+
|
|
159
169
|
return Fragment.create(
|
|
160
170
|
connection_pulser(),
|
|
161
171
|
connection_toaster(),
|
|
172
|
+
*([backend_disabled()] if config.is_reflex_cloud else []),
|
|
162
173
|
*codespaces.codespaces_auto_redirect(),
|
|
163
174
|
)
|
|
164
175
|
|
|
@@ -250,36 +261,36 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
250
261
|
# Attributes to add to the html root tag of every page.
|
|
251
262
|
html_custom_attrs: Optional[Dict[str, str]] = None
|
|
252
263
|
|
|
253
|
-
# A map from a route to an unevaluated page.
|
|
254
|
-
|
|
264
|
+
# A map from a route to an unevaluated page.
|
|
265
|
+
_unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
|
255
266
|
default_factory=dict
|
|
256
267
|
)
|
|
257
268
|
|
|
258
|
-
# A map from a page route to the component to render. Users should use `add_page`.
|
|
259
|
-
|
|
269
|
+
# A map from a page route to the component to render. Users should use `add_page`.
|
|
270
|
+
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
|
260
271
|
|
|
261
|
-
# The backend API object.
|
|
262
|
-
|
|
272
|
+
# The backend API object.
|
|
273
|
+
_api: FastAPI | None = None
|
|
263
274
|
|
|
264
|
-
# The state class to use for the app.
|
|
265
|
-
|
|
275
|
+
# The state class to use for the app.
|
|
276
|
+
_state: Optional[Type[BaseState]] = None
|
|
266
277
|
|
|
267
278
|
# Class to manage many client states.
|
|
268
279
|
_state_manager: Optional[StateManager] = None
|
|
269
280
|
|
|
270
|
-
# Mapping from a route to event handlers to trigger when the page loads.
|
|
271
|
-
|
|
281
|
+
# Mapping from a route to event handlers to trigger when the page loads.
|
|
282
|
+
_load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
|
272
283
|
default_factory=dict
|
|
273
284
|
)
|
|
274
285
|
|
|
275
|
-
# Admin dashboard to view and manage the database.
|
|
286
|
+
# Admin dashboard to view and manage the database.
|
|
276
287
|
admin_dash: Optional[AdminDash] = None
|
|
277
288
|
|
|
278
|
-
# The async server name space.
|
|
279
|
-
|
|
289
|
+
# The async server name space.
|
|
290
|
+
_event_namespace: Optional[EventNamespace] = None
|
|
280
291
|
|
|
281
|
-
# Background tasks that are currently running.
|
|
282
|
-
|
|
292
|
+
# Background tasks that are currently running.
|
|
293
|
+
_background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
|
283
294
|
|
|
284
295
|
# Frontend Error Handler Function
|
|
285
296
|
frontend_exception_handler: Callable[[Exception], None] = (
|
|
@@ -291,6 +302,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
291
302
|
[Exception], Union[EventSpec, List[EventSpec], None]
|
|
292
303
|
] = default_backend_exception_handler
|
|
293
304
|
|
|
305
|
+
@property
|
|
306
|
+
def api(self) -> FastAPI | None:
|
|
307
|
+
"""Get the backend api.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
The backend api.
|
|
311
|
+
"""
|
|
312
|
+
return self._api
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def event_namespace(self) -> EventNamespace | None:
|
|
316
|
+
"""Get the event namespace.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
The event namespace.
|
|
320
|
+
"""
|
|
321
|
+
return self._event_namespace
|
|
322
|
+
|
|
294
323
|
def __post_init__(self):
|
|
295
324
|
"""Initialize the app.
|
|
296
325
|
|
|
@@ -310,7 +339,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
310
339
|
set_breakpoints(self.style.pop("breakpoints"))
|
|
311
340
|
|
|
312
341
|
# Set up the API.
|
|
313
|
-
self.
|
|
342
|
+
self._api = FastAPI(lifespan=self._run_lifespan_tasks)
|
|
314
343
|
self._add_cors()
|
|
315
344
|
self._add_default_endpoints()
|
|
316
345
|
|
|
@@ -331,16 +360,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
331
360
|
|
|
332
361
|
self.register_lifespan_task(windows_hot_reload_lifespan_hack)
|
|
333
362
|
|
|
334
|
-
# Enable proxying to frontend server.
|
|
335
|
-
if not environment.REFLEX_BACKEND_ONLY.get():
|
|
336
|
-
from reflex.proxy import proxy_middleware
|
|
337
|
-
|
|
338
|
-
self.register_lifespan_task(proxy_middleware)
|
|
339
|
-
|
|
340
363
|
def _enable_state(self) -> None:
|
|
341
364
|
"""Enable state for the app."""
|
|
342
|
-
if not self.
|
|
343
|
-
self.
|
|
365
|
+
if not self._state:
|
|
366
|
+
self._state = State
|
|
344
367
|
self._setup_state()
|
|
345
368
|
|
|
346
369
|
def _setup_state(self) -> None:
|
|
@@ -349,13 +372,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
349
372
|
Raises:
|
|
350
373
|
RuntimeError: If the socket server is invalid.
|
|
351
374
|
"""
|
|
352
|
-
if not self.
|
|
375
|
+
if not self._state:
|
|
353
376
|
return
|
|
354
377
|
|
|
355
378
|
config = get_config()
|
|
356
379
|
|
|
357
380
|
# Set up the state manager.
|
|
358
|
-
self._state_manager = StateManager.create(state=self.
|
|
381
|
+
self._state_manager = StateManager.create(state=self._state)
|
|
359
382
|
|
|
360
383
|
# Set up the Socket.IO AsyncServer.
|
|
361
384
|
if not self.sio:
|
|
@@ -386,12 +409,42 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
386
409
|
namespace = config.get_event_namespace()
|
|
387
410
|
|
|
388
411
|
# Create the event namespace and attach the main app. Not related to any paths.
|
|
389
|
-
self.
|
|
412
|
+
self._event_namespace = EventNamespace(namespace, self)
|
|
390
413
|
|
|
391
414
|
# Register the event namespace with the socket.
|
|
392
415
|
self.sio.register_namespace(self.event_namespace)
|
|
393
416
|
# Mount the socket app with the API.
|
|
394
|
-
self.api
|
|
417
|
+
if self.api:
|
|
418
|
+
|
|
419
|
+
class HeaderMiddleware:
|
|
420
|
+
def __init__(self, app: ASGIApp):
|
|
421
|
+
self.app = app
|
|
422
|
+
|
|
423
|
+
async def __call__(
|
|
424
|
+
self, scope: MutableMapping[str, Any], receive: Any, send: Callable
|
|
425
|
+
):
|
|
426
|
+
original_send = send
|
|
427
|
+
|
|
428
|
+
async def modified_send(message: dict):
|
|
429
|
+
if message["type"] == "websocket.accept":
|
|
430
|
+
if scope.get("subprotocols"):
|
|
431
|
+
# The following *does* say "subprotocol" instead of "subprotocols", intentionally.
|
|
432
|
+
message["subprotocol"] = scope["subprotocols"][0]
|
|
433
|
+
|
|
434
|
+
headers = dict(message.get("headers", []))
|
|
435
|
+
header_key = b"sec-websocket-protocol"
|
|
436
|
+
if subprotocol := headers.get(header_key):
|
|
437
|
+
message["headers"] = [
|
|
438
|
+
*message.get("headers", []),
|
|
439
|
+
(header_key, subprotocol),
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
return await original_send(message)
|
|
443
|
+
|
|
444
|
+
return await self.app(scope, receive, modified_send)
|
|
445
|
+
|
|
446
|
+
socket_app_with_headers = HeaderMiddleware(socket_app)
|
|
447
|
+
self.api.mount(str(constants.Endpoint.EVENT), socket_app_with_headers)
|
|
395
448
|
|
|
396
449
|
# Check the exception handlers
|
|
397
450
|
self._validate_exception_handlers()
|
|
@@ -402,24 +455,35 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
402
455
|
Returns:
|
|
403
456
|
The string representation of the app.
|
|
404
457
|
"""
|
|
405
|
-
return f"<App state={self.
|
|
458
|
+
return f"<App state={self._state.__name__ if self._state else None}>"
|
|
406
459
|
|
|
407
460
|
def __call__(self) -> FastAPI:
|
|
408
461
|
"""Run the backend api instance.
|
|
409
462
|
|
|
463
|
+
Raises:
|
|
464
|
+
ValueError: If the app has not been initialized.
|
|
465
|
+
|
|
410
466
|
Returns:
|
|
411
467
|
The backend api.
|
|
412
468
|
"""
|
|
469
|
+
if not self.api:
|
|
470
|
+
raise ValueError("The app has not been initialized.")
|
|
413
471
|
return self.api
|
|
414
472
|
|
|
415
473
|
def _add_default_endpoints(self):
|
|
416
474
|
"""Add default api endpoints (ping)."""
|
|
417
475
|
# To test the server.
|
|
476
|
+
if not self.api:
|
|
477
|
+
return
|
|
478
|
+
|
|
418
479
|
self.api.get(str(constants.Endpoint.PING))(ping)
|
|
419
480
|
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
|
420
481
|
|
|
421
482
|
def _add_optional_endpoints(self):
|
|
422
483
|
"""Add optional api endpoints (_upload)."""
|
|
484
|
+
if not self.api:
|
|
485
|
+
return
|
|
486
|
+
|
|
423
487
|
if Upload.is_used:
|
|
424
488
|
# To upload files.
|
|
425
489
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
|
@@ -437,6 +501,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
437
501
|
|
|
438
502
|
def _add_cors(self):
|
|
439
503
|
"""Add CORS middleware to the app."""
|
|
504
|
+
if not self.api:
|
|
505
|
+
return
|
|
440
506
|
self.api.add_middleware(
|
|
441
507
|
cors.CORSMiddleware,
|
|
442
508
|
allow_credentials=True,
|
|
@@ -468,14 +534,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
468
534
|
|
|
469
535
|
Returns:
|
|
470
536
|
The generated component.
|
|
471
|
-
|
|
472
|
-
Raises:
|
|
473
|
-
exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
|
|
474
537
|
"""
|
|
475
|
-
|
|
476
|
-
return component if isinstance(component, Component) else component()
|
|
477
|
-
except exceptions.MatchTypeError:
|
|
478
|
-
raise
|
|
538
|
+
return component if isinstance(component, Component) else component()
|
|
479
539
|
|
|
480
540
|
def add_page(
|
|
481
541
|
self,
|
|
@@ -532,13 +592,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
532
592
|
# Check if the route given is valid
|
|
533
593
|
verify_route_validity(route)
|
|
534
594
|
|
|
535
|
-
if route in self.
|
|
595
|
+
if route in self._unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
|
536
596
|
# when the app is reloaded(typically for app harness tests), we should maintain
|
|
537
597
|
# the latest render function of a route.This applies typically to decorated pages
|
|
538
598
|
# since they are only added when app._compile is called.
|
|
539
|
-
self.
|
|
599
|
+
self._unevaluated_pages.pop(route)
|
|
540
600
|
|
|
541
|
-
if route in self.
|
|
601
|
+
if route in self._unevaluated_pages:
|
|
542
602
|
route_name = (
|
|
543
603
|
f"`{route}` or `/`"
|
|
544
604
|
if route == constants.PageNames.INDEX_ROUTE
|
|
@@ -551,15 +611,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
551
611
|
|
|
552
612
|
# Setup dynamic args for the route.
|
|
553
613
|
# this state assignment is only required for tests using the deprecated state kwarg for App
|
|
554
|
-
state = self.
|
|
614
|
+
state = self._state if self._state else State
|
|
555
615
|
state.setup_dynamic_args(get_route_args(route))
|
|
556
616
|
|
|
557
617
|
if on_load:
|
|
558
|
-
self.
|
|
618
|
+
self._load_events[route] = (
|
|
559
619
|
on_load if isinstance(on_load, list) else [on_load]
|
|
560
620
|
)
|
|
561
621
|
|
|
562
|
-
self.
|
|
622
|
+
self._unevaluated_pages[route] = UnevaluatedPage(
|
|
563
623
|
component=component,
|
|
564
624
|
route=route,
|
|
565
625
|
title=title,
|
|
@@ -569,14 +629,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
569
629
|
meta=meta,
|
|
570
630
|
)
|
|
571
631
|
|
|
572
|
-
def _compile_page(self, route: str):
|
|
632
|
+
def _compile_page(self, route: str, save_page: bool = True):
|
|
573
633
|
"""Compile a page.
|
|
574
634
|
|
|
575
635
|
Args:
|
|
576
636
|
route: The route of the page to compile.
|
|
637
|
+
save_page: If True, the compiled page is saved to self._pages.
|
|
577
638
|
"""
|
|
578
639
|
component, enable_state = compiler.compile_unevaluated_page(
|
|
579
|
-
route, self.
|
|
640
|
+
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
|
580
641
|
)
|
|
581
642
|
|
|
582
643
|
if enable_state:
|
|
@@ -584,7 +645,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
584
645
|
|
|
585
646
|
# Add the page.
|
|
586
647
|
self._check_routes_conflict(route)
|
|
587
|
-
|
|
648
|
+
if save_page:
|
|
649
|
+
self._pages[route] = component
|
|
588
650
|
|
|
589
651
|
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
|
590
652
|
"""Get the load events for a route.
|
|
@@ -598,7 +660,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
598
660
|
route = route.lstrip("/")
|
|
599
661
|
if route == "":
|
|
600
662
|
route = constants.PageNames.INDEX_ROUTE
|
|
601
|
-
return self.
|
|
663
|
+
return self._load_events.get(route, [])
|
|
602
664
|
|
|
603
665
|
def _check_routes_conflict(self, new_route: str):
|
|
604
666
|
"""Verify if there is any conflict between the new route and any existing route.
|
|
@@ -622,10 +684,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
622
684
|
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
|
623
685
|
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
|
624
686
|
)
|
|
625
|
-
for route in self.
|
|
687
|
+
for route in self._pages:
|
|
626
688
|
replaced_route = replace_brackets_with_keywords(route)
|
|
627
689
|
for rw, r, nr in zip(
|
|
628
|
-
replaced_route.split("/"),
|
|
690
|
+
replaced_route.split("/"),
|
|
691
|
+
route.split("/"),
|
|
692
|
+
new_route.split("/"),
|
|
693
|
+
strict=False,
|
|
629
694
|
):
|
|
630
695
|
if rw in segments and r != nr:
|
|
631
696
|
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
|
@@ -656,8 +721,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
656
721
|
Args:
|
|
657
722
|
component: The component to display at the page.
|
|
658
723
|
title: The title of the page.
|
|
659
|
-
description: The description of the page.
|
|
660
724
|
image: The image to display on the page.
|
|
725
|
+
description: The description of the page.
|
|
661
726
|
on_load: The event handler(s) that will be called each time the page load.
|
|
662
727
|
meta: The metadata of the page.
|
|
663
728
|
"""
|
|
@@ -680,6 +745,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
680
745
|
def _setup_admin_dash(self):
|
|
681
746
|
"""Setup the admin dash."""
|
|
682
747
|
# Get the admin dash.
|
|
748
|
+
if not self.api:
|
|
749
|
+
return
|
|
750
|
+
|
|
683
751
|
admin_dash = self.admin_dash
|
|
684
752
|
|
|
685
753
|
if admin_dash and admin_dash.models:
|
|
@@ -721,7 +789,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
721
789
|
frontend_packages = get_config().frontend_packages
|
|
722
790
|
_frontend_packages = []
|
|
723
791
|
for package in frontend_packages:
|
|
724
|
-
if package in (get_config().tailwind or {}).get("plugins", []):
|
|
792
|
+
if package in (get_config().tailwind or {}).get("plugins", []):
|
|
725
793
|
console.warn(
|
|
726
794
|
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
|
727
795
|
)
|
|
@@ -784,10 +852,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
784
852
|
|
|
785
853
|
def _setup_overlay_component(self):
|
|
786
854
|
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
|
|
787
|
-
if self.
|
|
855
|
+
if self._state is None and self.overlay_component is default_overlay_component:
|
|
788
856
|
self.overlay_component = None
|
|
789
|
-
for k, component in self.
|
|
790
|
-
self.
|
|
857
|
+
for k, component in self._pages.items():
|
|
858
|
+
self._pages[k] = self._add_overlay_to_component(component)
|
|
791
859
|
|
|
792
860
|
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
|
793
861
|
if self.error_boundary is None:
|
|
@@ -799,14 +867,23 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
799
867
|
|
|
800
868
|
def _setup_error_boundary(self):
|
|
801
869
|
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
|
802
|
-
if self.
|
|
870
|
+
if self._state is None and self.error_boundary is default_error_boundary:
|
|
803
871
|
self.error_boundary = None
|
|
804
872
|
|
|
805
|
-
for k, component in self.
|
|
873
|
+
for k, component in self._pages.items():
|
|
806
874
|
# Skip the 404 page
|
|
807
875
|
if k == constants.Page404.SLUG:
|
|
808
876
|
continue
|
|
809
|
-
self.
|
|
877
|
+
self._pages[k] = self._add_error_boundary_to_component(component)
|
|
878
|
+
|
|
879
|
+
def _setup_sticky_badge(self):
|
|
880
|
+
"""Add the sticky badge to the app."""
|
|
881
|
+
for k, component in self._pages.items():
|
|
882
|
+
# Would be nice to share single sticky_badge across all pages, but
|
|
883
|
+
# it bungles the StatefulComponent compile step.
|
|
884
|
+
sticky_badge = sticky()
|
|
885
|
+
sticky_badge._add_style_recursive({})
|
|
886
|
+
self._pages[k] = Fragment.create(sticky_badge, component)
|
|
810
887
|
|
|
811
888
|
def _apply_decorated_pages(self):
|
|
812
889
|
"""Add @rx.page decorated pages to the app.
|
|
@@ -832,21 +909,27 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
832
909
|
Raises:
|
|
833
910
|
VarDependencyError: When a computed var has an invalid dependency.
|
|
834
911
|
"""
|
|
835
|
-
if not self.
|
|
912
|
+
if not self._state:
|
|
836
913
|
return
|
|
837
914
|
|
|
838
915
|
if not state:
|
|
839
|
-
state = self.
|
|
916
|
+
state = self._state
|
|
840
917
|
|
|
841
918
|
for var in state.computed_vars.values():
|
|
842
919
|
if not var._cache:
|
|
843
920
|
continue
|
|
844
921
|
deps = var._deps(objclass=state)
|
|
845
|
-
for
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
922
|
+
for state_name, dep_set in deps.items():
|
|
923
|
+
state_cls = (
|
|
924
|
+
state.get_root_state().get_class_substate(state_name)
|
|
925
|
+
if state_name != state.get_full_name()
|
|
926
|
+
else state
|
|
927
|
+
)
|
|
928
|
+
for dep in dep_set:
|
|
929
|
+
if dep not in state_cls.vars and dep not in state_cls.backend_vars:
|
|
930
|
+
raise exceptions.VarDependencyError(
|
|
931
|
+
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
|
|
932
|
+
)
|
|
850
933
|
|
|
851
934
|
for substate in state.class_subclasses:
|
|
852
935
|
self._validate_var_dependencies(substate)
|
|
@@ -862,13 +945,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
862
945
|
"""
|
|
863
946
|
from reflex.utils.exceptions import ReflexRuntimeError
|
|
864
947
|
|
|
865
|
-
self.
|
|
948
|
+
self._pages = {}
|
|
866
949
|
|
|
867
950
|
def get_compilation_time() -> str:
|
|
868
951
|
return str(datetime.now().time()).split(".")[0]
|
|
869
952
|
|
|
870
953
|
# Render a default 404 page if the user didn't supply one
|
|
871
|
-
if constants.Page404.SLUG not in self.
|
|
954
|
+
if constants.Page404.SLUG not in self._unevaluated_pages:
|
|
872
955
|
self.add_page(route=constants.Page404.SLUG)
|
|
873
956
|
|
|
874
957
|
# Fix up the style.
|
|
@@ -884,19 +967,23 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
884
967
|
# If a theme component was provided, wrap the app with it
|
|
885
968
|
app_wrappers[(20, "Theme")] = self.theme
|
|
886
969
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
self._compile_page(route)
|
|
970
|
+
# Get the env mode.
|
|
971
|
+
config = get_config()
|
|
890
972
|
|
|
891
|
-
|
|
892
|
-
|
|
973
|
+
if config.react_strict_mode:
|
|
974
|
+
app_wrappers[(200, "StrictMode")] = StrictMode.create()
|
|
893
975
|
|
|
894
|
-
|
|
895
|
-
return
|
|
976
|
+
should_compile = self._should_compile()
|
|
896
977
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
978
|
+
if not should_compile:
|
|
979
|
+
for route in self._unevaluated_pages:
|
|
980
|
+
console.debug(f"Evaluating page: {route}")
|
|
981
|
+
self._compile_page(route, save_page=should_compile)
|
|
982
|
+
|
|
983
|
+
# Add the optional endpoints (_upload)
|
|
984
|
+
self._add_optional_endpoints()
|
|
985
|
+
|
|
986
|
+
return
|
|
900
987
|
|
|
901
988
|
# Create a progress bar.
|
|
902
989
|
progress = Progress(
|
|
@@ -906,18 +993,32 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
906
993
|
)
|
|
907
994
|
|
|
908
995
|
# try to be somewhat accurate - but still not 100%
|
|
909
|
-
adhoc_steps_without_executor =
|
|
996
|
+
adhoc_steps_without_executor = 7
|
|
910
997
|
fixed_pages_within_executor = 5
|
|
911
998
|
progress.start()
|
|
912
999
|
task = progress.add_task(
|
|
913
1000
|
f"[{get_compilation_time()}] Compiling:",
|
|
914
|
-
total=len(self.
|
|
1001
|
+
total=len(self._pages)
|
|
1002
|
+
+ (len(self._unevaluated_pages) * 2)
|
|
915
1003
|
+ fixed_pages_within_executor
|
|
916
1004
|
+ adhoc_steps_without_executor,
|
|
917
1005
|
)
|
|
918
1006
|
|
|
919
|
-
|
|
920
|
-
|
|
1007
|
+
for route in self._unevaluated_pages:
|
|
1008
|
+
console.debug(f"Evaluating page: {route}")
|
|
1009
|
+
self._compile_page(route, save_page=should_compile)
|
|
1010
|
+
progress.advance(task)
|
|
1011
|
+
|
|
1012
|
+
# Add the optional endpoints (_upload)
|
|
1013
|
+
self._add_optional_endpoints()
|
|
1014
|
+
|
|
1015
|
+
self._validate_var_dependencies()
|
|
1016
|
+
self._setup_overlay_component()
|
|
1017
|
+
self._setup_error_boundary()
|
|
1018
|
+
if config.show_built_with_reflex:
|
|
1019
|
+
self._setup_sticky_badge()
|
|
1020
|
+
|
|
1021
|
+
progress.advance(task)
|
|
921
1022
|
|
|
922
1023
|
# Store the compile results.
|
|
923
1024
|
compile_results = []
|
|
@@ -930,7 +1031,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
930
1031
|
|
|
931
1032
|
# This has to happen before compiling stateful components as that
|
|
932
1033
|
# prevents recursive functions from reaching all components.
|
|
933
|
-
for component in self.
|
|
1034
|
+
for component in self._pages.values():
|
|
934
1035
|
# Add component._get_all_imports() to all_imports.
|
|
935
1036
|
all_imports.update(component._get_all_imports())
|
|
936
1037
|
|
|
@@ -945,12 +1046,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
945
1046
|
stateful_components_path,
|
|
946
1047
|
stateful_components_code,
|
|
947
1048
|
page_components,
|
|
948
|
-
) = compiler.compile_stateful_components(self.
|
|
1049
|
+
) = compiler.compile_stateful_components(self._pages.values())
|
|
949
1050
|
|
|
950
1051
|
progress.advance(task)
|
|
951
1052
|
|
|
952
1053
|
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
|
953
|
-
if code_uses_state_contexts(stateful_components_code) and self.
|
|
1054
|
+
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
|
954
1055
|
raise ReflexRuntimeError(
|
|
955
1056
|
"To access rx.State in frontend components, at least one "
|
|
956
1057
|
"subclass of rx.State must be defined in the app."
|
|
@@ -964,7 +1065,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
964
1065
|
compiler.compile_document_root(
|
|
965
1066
|
self.head_components,
|
|
966
1067
|
html_lang=self.html_lang,
|
|
967
|
-
html_custom_attrs=self.html_custom_attrs, #
|
|
1068
|
+
html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType]
|
|
968
1069
|
)
|
|
969
1070
|
)
|
|
970
1071
|
|
|
@@ -987,20 +1088,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
987
1088
|
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
|
988
1089
|
)
|
|
989
1090
|
|
|
990
|
-
for route, component in zip(self.
|
|
1091
|
+
for route, component in zip(self._pages, page_components, strict=True):
|
|
991
1092
|
ExecutorSafeFunctions.COMPONENTS[route] = component
|
|
992
1093
|
|
|
993
|
-
ExecutorSafeFunctions.STATE = self.
|
|
1094
|
+
ExecutorSafeFunctions.STATE = self._state
|
|
994
1095
|
|
|
995
1096
|
with executor:
|
|
996
1097
|
result_futures = []
|
|
997
1098
|
|
|
998
|
-
def _submit_work(fn, *args, **kwargs):
|
|
1099
|
+
def _submit_work(fn: Callable, *args, **kwargs):
|
|
999
1100
|
f = executor.submit(fn, *args, **kwargs)
|
|
1000
1101
|
result_futures.append(f)
|
|
1001
1102
|
|
|
1002
1103
|
# Compile the pre-compiled pages.
|
|
1003
|
-
for route in self.
|
|
1104
|
+
for route in self._pages:
|
|
1004
1105
|
_submit_work(
|
|
1005
1106
|
ExecutorSafeFunctions.compile_page,
|
|
1006
1107
|
route,
|
|
@@ -1035,7 +1136,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1035
1136
|
|
|
1036
1137
|
# Compile the contexts.
|
|
1037
1138
|
compile_results.append(
|
|
1038
|
-
compiler.compile_contexts(self.
|
|
1139
|
+
compiler.compile_contexts(self._state, self.theme),
|
|
1039
1140
|
)
|
|
1040
1141
|
if self.theme is not None:
|
|
1041
1142
|
# Fix #2992 by removing the top-level appearance prop
|
|
@@ -1157,9 +1258,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1157
1258
|
)
|
|
1158
1259
|
|
|
1159
1260
|
task = asyncio.create_task(_coro())
|
|
1160
|
-
self.
|
|
1261
|
+
self._background_tasks.add(task)
|
|
1161
1262
|
# Clean up task from background_tasks set when complete.
|
|
1162
|
-
task.add_done_callback(self.
|
|
1263
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
1163
1264
|
return task
|
|
1164
1265
|
|
|
1165
1266
|
def _validate_exception_handlers(self):
|
|
@@ -1169,11 +1270,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1169
1270
|
ValueError: If the custom exception handlers are invalid.
|
|
1170
1271
|
|
|
1171
1272
|
"""
|
|
1172
|
-
|
|
1273
|
+
frontend_arg_spec = {
|
|
1173
1274
|
"exception": Exception,
|
|
1174
1275
|
}
|
|
1175
1276
|
|
|
1176
|
-
|
|
1277
|
+
backend_arg_spec = {
|
|
1177
1278
|
"exception": Exception,
|
|
1178
1279
|
}
|
|
1179
1280
|
|
|
@@ -1181,9 +1282,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1181
1282
|
["frontend", "backend"],
|
|
1182
1283
|
[self.frontend_exception_handler, self.backend_exception_handler],
|
|
1183
1284
|
[
|
|
1184
|
-
|
|
1185
|
-
|
|
1285
|
+
frontend_arg_spec,
|
|
1286
|
+
backend_arg_spec,
|
|
1186
1287
|
],
|
|
1288
|
+
strict=True,
|
|
1187
1289
|
):
|
|
1188
1290
|
if hasattr(handler_fn, "__name__"):
|
|
1189
1291
|
_fn_name = handler_fn.__name__
|
|
@@ -1224,7 +1326,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1224
1326
|
):
|
|
1225
1327
|
raise ValueError(
|
|
1226
1328
|
f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong argument order."
|
|
1227
|
-
f"Expected `{required_arg}` as the {required_arg_index+1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
|
|
1329
|
+
f"Expected `{required_arg}` as the {required_arg_index + 1} argument but got `{list(arg_annotations.keys())[required_arg_index]}`"
|
|
1228
1330
|
)
|
|
1229
1331
|
|
|
1230
1332
|
if not issubclass(arg_annotations[required_arg], Exception):
|
|
@@ -1325,15 +1427,14 @@ async def process(
|
|
|
1325
1427
|
if app._process_background(state, event) is not None:
|
|
1326
1428
|
# `final=True` allows the frontend send more events immediately.
|
|
1327
1429
|
yield StateUpdate(final=True)
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
yield update
|
|
1430
|
+
else:
|
|
1431
|
+
# Process the event synchronously.
|
|
1432
|
+
async for update in state._process(event):
|
|
1433
|
+
# Postprocess the event.
|
|
1434
|
+
update = await app._postprocess(state, event, update)
|
|
1435
|
+
|
|
1436
|
+
# Yield the update.
|
|
1437
|
+
yield update
|
|
1337
1438
|
except Exception as ex:
|
|
1338
1439
|
telemetry.send_error(ex, context="backend")
|
|
1339
1440
|
|
|
@@ -1528,16 +1629,20 @@ class EventNamespace(AsyncNamespace):
|
|
|
1528
1629
|
self.sid_to_token = {}
|
|
1529
1630
|
self.app = app
|
|
1530
1631
|
|
|
1531
|
-
def on_connect(self, sid, environ):
|
|
1632
|
+
def on_connect(self, sid: str, environ: dict):
|
|
1532
1633
|
"""Event for when the websocket is connected.
|
|
1533
1634
|
|
|
1534
1635
|
Args:
|
|
1535
1636
|
sid: The Socket.IO session id.
|
|
1536
1637
|
environ: The request information, including HTTP headers.
|
|
1537
1638
|
"""
|
|
1538
|
-
|
|
1639
|
+
subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL")
|
|
1640
|
+
if subprotocol and subprotocol != constants.Reflex.VERSION:
|
|
1641
|
+
console.warn(
|
|
1642
|
+
f"Frontend version {subprotocol} for session {sid} does not match the backend version {constants.Reflex.VERSION}."
|
|
1643
|
+
)
|
|
1539
1644
|
|
|
1540
|
-
def on_disconnect(self, sid):
|
|
1645
|
+
def on_disconnect(self, sid: str):
|
|
1541
1646
|
"""Event for when the websocket disconnects.
|
|
1542
1647
|
|
|
1543
1648
|
Args:
|
|
@@ -1559,7 +1664,7 @@ class EventNamespace(AsyncNamespace):
|
|
|
1559
1664
|
self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
|
|
1560
1665
|
)
|
|
1561
1666
|
|
|
1562
|
-
async def on_event(self, sid, data):
|
|
1667
|
+
async def on_event(self, sid: str, data: Any):
|
|
1563
1668
|
"""Event for receiving front-end websocket events.
|
|
1564
1669
|
|
|
1565
1670
|
Raises:
|
|
@@ -1568,12 +1673,36 @@ class EventNamespace(AsyncNamespace):
|
|
|
1568
1673
|
Args:
|
|
1569
1674
|
sid: The Socket.IO session id.
|
|
1570
1675
|
data: The event data.
|
|
1676
|
+
|
|
1677
|
+
Raises:
|
|
1678
|
+
EventDeserializationError: If the event data is not a dictionary.
|
|
1571
1679
|
"""
|
|
1572
1680
|
fields = data
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1681
|
+
|
|
1682
|
+
if isinstance(fields, str):
|
|
1683
|
+
console.warn(
|
|
1684
|
+
"Received event data as a string. This generally should not happen and may indicate a bug."
|
|
1685
|
+
f" Event data: {fields}"
|
|
1686
|
+
)
|
|
1687
|
+
try:
|
|
1688
|
+
fields = json.loads(fields)
|
|
1689
|
+
except json.JSONDecodeError as ex:
|
|
1690
|
+
raise exceptions.EventDeserializationError(
|
|
1691
|
+
f"Failed to deserialize event data: {fields}."
|
|
1692
|
+
) from ex
|
|
1693
|
+
|
|
1694
|
+
if not isinstance(fields, dict):
|
|
1695
|
+
raise exceptions.EventDeserializationError(
|
|
1696
|
+
f"Event data must be a dictionary, but received {fields} of type {type(fields)}."
|
|
1697
|
+
)
|
|
1698
|
+
|
|
1699
|
+
try:
|
|
1700
|
+
# Get the event.
|
|
1701
|
+
event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS})
|
|
1702
|
+
except (TypeError, ValueError) as ex:
|
|
1703
|
+
raise exceptions.EventDeserializationError(
|
|
1704
|
+
f"Failed to deserialize event data: {fields}."
|
|
1705
|
+
) from ex
|
|
1577
1706
|
|
|
1578
1707
|
self.token_to_sid[event.token] = sid
|
|
1579
1708
|
self.sid_to_token[sid] = event.token
|
|
@@ -1602,7 +1731,7 @@ class EventNamespace(AsyncNamespace):
|
|
|
1602
1731
|
# Emit the update from processing the event.
|
|
1603
1732
|
await self.emit_update(update=update, sid=sid)
|
|
1604
1733
|
|
|
1605
|
-
async def on_ping(self, sid):
|
|
1734
|
+
async def on_ping(self, sid: str):
|
|
1606
1735
|
"""Event for testing the API endpoint.
|
|
1607
1736
|
|
|
1608
1737
|
Args:
|