reflex 0.6.8a2__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 -109
- 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 -15
- 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 +130 -161
- 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 +29 -23
- reflex/utils/build.py +6 -2
- reflex/utils/codespaces.py +1 -4
- reflex/utils/compat.py +6 -5
- reflex/utils/console.py +52 -16
- 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.8a2.dist-info → reflex-0.7.0a1.dist-info}/METADATA +7 -8
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/RECORD +153 -149
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/WHEEL +1 -1
- reflex/experimental/assets.py +0 -37
- {reflex-0.6.8a2.dist-info → reflex-0.7.0a1.dist-info}/LICENSE +0 -0
- {reflex-0.6.8a2.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
|
|
|
@@ -333,8 +362,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
333
362
|
|
|
334
363
|
def _enable_state(self) -> None:
|
|
335
364
|
"""Enable state for the app."""
|
|
336
|
-
if not self.
|
|
337
|
-
self.
|
|
365
|
+
if not self._state:
|
|
366
|
+
self._state = State
|
|
338
367
|
self._setup_state()
|
|
339
368
|
|
|
340
369
|
def _setup_state(self) -> None:
|
|
@@ -343,13 +372,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
343
372
|
Raises:
|
|
344
373
|
RuntimeError: If the socket server is invalid.
|
|
345
374
|
"""
|
|
346
|
-
if not self.
|
|
375
|
+
if not self._state:
|
|
347
376
|
return
|
|
348
377
|
|
|
349
378
|
config = get_config()
|
|
350
379
|
|
|
351
380
|
# Set up the state manager.
|
|
352
|
-
self._state_manager = StateManager.create(state=self.
|
|
381
|
+
self._state_manager = StateManager.create(state=self._state)
|
|
353
382
|
|
|
354
383
|
# Set up the Socket.IO AsyncServer.
|
|
355
384
|
if not self.sio:
|
|
@@ -380,12 +409,42 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
380
409
|
namespace = config.get_event_namespace()
|
|
381
410
|
|
|
382
411
|
# Create the event namespace and attach the main app. Not related to any paths.
|
|
383
|
-
self.
|
|
412
|
+
self._event_namespace = EventNamespace(namespace, self)
|
|
384
413
|
|
|
385
414
|
# Register the event namespace with the socket.
|
|
386
415
|
self.sio.register_namespace(self.event_namespace)
|
|
387
416
|
# Mount the socket app with the API.
|
|
388
|
-
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)
|
|
389
448
|
|
|
390
449
|
# Check the exception handlers
|
|
391
450
|
self._validate_exception_handlers()
|
|
@@ -396,24 +455,35 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
396
455
|
Returns:
|
|
397
456
|
The string representation of the app.
|
|
398
457
|
"""
|
|
399
|
-
return f"<App state={self.
|
|
458
|
+
return f"<App state={self._state.__name__ if self._state else None}>"
|
|
400
459
|
|
|
401
460
|
def __call__(self) -> FastAPI:
|
|
402
461
|
"""Run the backend api instance.
|
|
403
462
|
|
|
463
|
+
Raises:
|
|
464
|
+
ValueError: If the app has not been initialized.
|
|
465
|
+
|
|
404
466
|
Returns:
|
|
405
467
|
The backend api.
|
|
406
468
|
"""
|
|
469
|
+
if not self.api:
|
|
470
|
+
raise ValueError("The app has not been initialized.")
|
|
407
471
|
return self.api
|
|
408
472
|
|
|
409
473
|
def _add_default_endpoints(self):
|
|
410
474
|
"""Add default api endpoints (ping)."""
|
|
411
475
|
# To test the server.
|
|
476
|
+
if not self.api:
|
|
477
|
+
return
|
|
478
|
+
|
|
412
479
|
self.api.get(str(constants.Endpoint.PING))(ping)
|
|
413
480
|
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
|
414
481
|
|
|
415
482
|
def _add_optional_endpoints(self):
|
|
416
483
|
"""Add optional api endpoints (_upload)."""
|
|
484
|
+
if not self.api:
|
|
485
|
+
return
|
|
486
|
+
|
|
417
487
|
if Upload.is_used:
|
|
418
488
|
# To upload files.
|
|
419
489
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
|
@@ -431,6 +501,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
431
501
|
|
|
432
502
|
def _add_cors(self):
|
|
433
503
|
"""Add CORS middleware to the app."""
|
|
504
|
+
if not self.api:
|
|
505
|
+
return
|
|
434
506
|
self.api.add_middleware(
|
|
435
507
|
cors.CORSMiddleware,
|
|
436
508
|
allow_credentials=True,
|
|
@@ -462,14 +534,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
462
534
|
|
|
463
535
|
Returns:
|
|
464
536
|
The generated component.
|
|
465
|
-
|
|
466
|
-
Raises:
|
|
467
|
-
exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
|
|
468
537
|
"""
|
|
469
|
-
|
|
470
|
-
return component if isinstance(component, Component) else component()
|
|
471
|
-
except exceptions.MatchTypeError:
|
|
472
|
-
raise
|
|
538
|
+
return component if isinstance(component, Component) else component()
|
|
473
539
|
|
|
474
540
|
def add_page(
|
|
475
541
|
self,
|
|
@@ -526,13 +592,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
526
592
|
# Check if the route given is valid
|
|
527
593
|
verify_route_validity(route)
|
|
528
594
|
|
|
529
|
-
if route in self.
|
|
595
|
+
if route in self._unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
|
530
596
|
# when the app is reloaded(typically for app harness tests), we should maintain
|
|
531
597
|
# the latest render function of a route.This applies typically to decorated pages
|
|
532
598
|
# since they are only added when app._compile is called.
|
|
533
|
-
self.
|
|
599
|
+
self._unevaluated_pages.pop(route)
|
|
534
600
|
|
|
535
|
-
if route in self.
|
|
601
|
+
if route in self._unevaluated_pages:
|
|
536
602
|
route_name = (
|
|
537
603
|
f"`{route}` or `/`"
|
|
538
604
|
if route == constants.PageNames.INDEX_ROUTE
|
|
@@ -545,15 +611,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
545
611
|
|
|
546
612
|
# Setup dynamic args for the route.
|
|
547
613
|
# this state assignment is only required for tests using the deprecated state kwarg for App
|
|
548
|
-
state = self.
|
|
614
|
+
state = self._state if self._state else State
|
|
549
615
|
state.setup_dynamic_args(get_route_args(route))
|
|
550
616
|
|
|
551
617
|
if on_load:
|
|
552
|
-
self.
|
|
618
|
+
self._load_events[route] = (
|
|
553
619
|
on_load if isinstance(on_load, list) else [on_load]
|
|
554
620
|
)
|
|
555
621
|
|
|
556
|
-
self.
|
|
622
|
+
self._unevaluated_pages[route] = UnevaluatedPage(
|
|
557
623
|
component=component,
|
|
558
624
|
route=route,
|
|
559
625
|
title=title,
|
|
@@ -563,14 +629,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
563
629
|
meta=meta,
|
|
564
630
|
)
|
|
565
631
|
|
|
566
|
-
def _compile_page(self, route: str):
|
|
632
|
+
def _compile_page(self, route: str, save_page: bool = True):
|
|
567
633
|
"""Compile a page.
|
|
568
634
|
|
|
569
635
|
Args:
|
|
570
636
|
route: The route of the page to compile.
|
|
637
|
+
save_page: If True, the compiled page is saved to self._pages.
|
|
571
638
|
"""
|
|
572
639
|
component, enable_state = compiler.compile_unevaluated_page(
|
|
573
|
-
route, self.
|
|
640
|
+
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
|
574
641
|
)
|
|
575
642
|
|
|
576
643
|
if enable_state:
|
|
@@ -578,7 +645,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
578
645
|
|
|
579
646
|
# Add the page.
|
|
580
647
|
self._check_routes_conflict(route)
|
|
581
|
-
|
|
648
|
+
if save_page:
|
|
649
|
+
self._pages[route] = component
|
|
582
650
|
|
|
583
651
|
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
|
584
652
|
"""Get the load events for a route.
|
|
@@ -592,7 +660,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
592
660
|
route = route.lstrip("/")
|
|
593
661
|
if route == "":
|
|
594
662
|
route = constants.PageNames.INDEX_ROUTE
|
|
595
|
-
return self.
|
|
663
|
+
return self._load_events.get(route, [])
|
|
596
664
|
|
|
597
665
|
def _check_routes_conflict(self, new_route: str):
|
|
598
666
|
"""Verify if there is any conflict between the new route and any existing route.
|
|
@@ -616,10 +684,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
616
684
|
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
|
617
685
|
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
|
618
686
|
)
|
|
619
|
-
for route in self.
|
|
687
|
+
for route in self._pages:
|
|
620
688
|
replaced_route = replace_brackets_with_keywords(route)
|
|
621
689
|
for rw, r, nr in zip(
|
|
622
|
-
replaced_route.split("/"),
|
|
690
|
+
replaced_route.split("/"),
|
|
691
|
+
route.split("/"),
|
|
692
|
+
new_route.split("/"),
|
|
693
|
+
strict=False,
|
|
623
694
|
):
|
|
624
695
|
if rw in segments and r != nr:
|
|
625
696
|
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
|
@@ -650,8 +721,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
650
721
|
Args:
|
|
651
722
|
component: The component to display at the page.
|
|
652
723
|
title: The title of the page.
|
|
653
|
-
description: The description of the page.
|
|
654
724
|
image: The image to display on the page.
|
|
725
|
+
description: The description of the page.
|
|
655
726
|
on_load: The event handler(s) that will be called each time the page load.
|
|
656
727
|
meta: The metadata of the page.
|
|
657
728
|
"""
|
|
@@ -674,6 +745,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
674
745
|
def _setup_admin_dash(self):
|
|
675
746
|
"""Setup the admin dash."""
|
|
676
747
|
# Get the admin dash.
|
|
748
|
+
if not self.api:
|
|
749
|
+
return
|
|
750
|
+
|
|
677
751
|
admin_dash = self.admin_dash
|
|
678
752
|
|
|
679
753
|
if admin_dash and admin_dash.models:
|
|
@@ -715,7 +789,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
715
789
|
frontend_packages = get_config().frontend_packages
|
|
716
790
|
_frontend_packages = []
|
|
717
791
|
for package in frontend_packages:
|
|
718
|
-
if package in (get_config().tailwind or {}).get("plugins", []):
|
|
792
|
+
if package in (get_config().tailwind or {}).get("plugins", []):
|
|
719
793
|
console.warn(
|
|
720
794
|
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
|
721
795
|
)
|
|
@@ -778,10 +852,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
778
852
|
|
|
779
853
|
def _setup_overlay_component(self):
|
|
780
854
|
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
|
|
781
|
-
if self.
|
|
855
|
+
if self._state is None and self.overlay_component is default_overlay_component:
|
|
782
856
|
self.overlay_component = None
|
|
783
|
-
for k, component in self.
|
|
784
|
-
self.
|
|
857
|
+
for k, component in self._pages.items():
|
|
858
|
+
self._pages[k] = self._add_overlay_to_component(component)
|
|
785
859
|
|
|
786
860
|
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
|
787
861
|
if self.error_boundary is None:
|
|
@@ -793,14 +867,23 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
793
867
|
|
|
794
868
|
def _setup_error_boundary(self):
|
|
795
869
|
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
|
796
|
-
if self.
|
|
870
|
+
if self._state is None and self.error_boundary is default_error_boundary:
|
|
797
871
|
self.error_boundary = None
|
|
798
872
|
|
|
799
|
-
for k, component in self.
|
|
873
|
+
for k, component in self._pages.items():
|
|
800
874
|
# Skip the 404 page
|
|
801
875
|
if k == constants.Page404.SLUG:
|
|
802
876
|
continue
|
|
803
|
-
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)
|
|
804
887
|
|
|
805
888
|
def _apply_decorated_pages(self):
|
|
806
889
|
"""Add @rx.page decorated pages to the app.
|
|
@@ -826,21 +909,27 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
826
909
|
Raises:
|
|
827
910
|
VarDependencyError: When a computed var has an invalid dependency.
|
|
828
911
|
"""
|
|
829
|
-
if not self.
|
|
912
|
+
if not self._state:
|
|
830
913
|
return
|
|
831
914
|
|
|
832
915
|
if not state:
|
|
833
|
-
state = self.
|
|
916
|
+
state = self._state
|
|
834
917
|
|
|
835
918
|
for var in state.computed_vars.values():
|
|
836
919
|
if not var._cache:
|
|
837
920
|
continue
|
|
838
921
|
deps = var._deps(objclass=state)
|
|
839
|
-
for
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
+
)
|
|
844
933
|
|
|
845
934
|
for substate in state.class_subclasses:
|
|
846
935
|
self._validate_var_dependencies(substate)
|
|
@@ -856,13 +945,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
856
945
|
"""
|
|
857
946
|
from reflex.utils.exceptions import ReflexRuntimeError
|
|
858
947
|
|
|
859
|
-
self.
|
|
948
|
+
self._pages = {}
|
|
860
949
|
|
|
861
950
|
def get_compilation_time() -> str:
|
|
862
951
|
return str(datetime.now().time()).split(".")[0]
|
|
863
952
|
|
|
864
953
|
# Render a default 404 page if the user didn't supply one
|
|
865
|
-
if constants.Page404.SLUG not in self.
|
|
954
|
+
if constants.Page404.SLUG not in self._unevaluated_pages:
|
|
866
955
|
self.add_page(route=constants.Page404.SLUG)
|
|
867
956
|
|
|
868
957
|
# Fix up the style.
|
|
@@ -878,19 +967,23 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
878
967
|
# If a theme component was provided, wrap the app with it
|
|
879
968
|
app_wrappers[(20, "Theme")] = self.theme
|
|
880
969
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
self._compile_page(route)
|
|
970
|
+
# Get the env mode.
|
|
971
|
+
config = get_config()
|
|
884
972
|
|
|
885
|
-
|
|
886
|
-
|
|
973
|
+
if config.react_strict_mode:
|
|
974
|
+
app_wrappers[(200, "StrictMode")] = StrictMode.create()
|
|
887
975
|
|
|
888
|
-
|
|
889
|
-
return
|
|
976
|
+
should_compile = self._should_compile()
|
|
890
977
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
|
894
987
|
|
|
895
988
|
# Create a progress bar.
|
|
896
989
|
progress = Progress(
|
|
@@ -900,18 +993,32 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
900
993
|
)
|
|
901
994
|
|
|
902
995
|
# try to be somewhat accurate - but still not 100%
|
|
903
|
-
adhoc_steps_without_executor =
|
|
996
|
+
adhoc_steps_without_executor = 7
|
|
904
997
|
fixed_pages_within_executor = 5
|
|
905
998
|
progress.start()
|
|
906
999
|
task = progress.add_task(
|
|
907
1000
|
f"[{get_compilation_time()}] Compiling:",
|
|
908
|
-
total=len(self.
|
|
1001
|
+
total=len(self._pages)
|
|
1002
|
+
+ (len(self._unevaluated_pages) * 2)
|
|
909
1003
|
+ fixed_pages_within_executor
|
|
910
1004
|
+ adhoc_steps_without_executor,
|
|
911
1005
|
)
|
|
912
1006
|
|
|
913
|
-
|
|
914
|
-
|
|
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)
|
|
915
1022
|
|
|
916
1023
|
# Store the compile results.
|
|
917
1024
|
compile_results = []
|
|
@@ -924,7 +1031,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
924
1031
|
|
|
925
1032
|
# This has to happen before compiling stateful components as that
|
|
926
1033
|
# prevents recursive functions from reaching all components.
|
|
927
|
-
for component in self.
|
|
1034
|
+
for component in self._pages.values():
|
|
928
1035
|
# Add component._get_all_imports() to all_imports.
|
|
929
1036
|
all_imports.update(component._get_all_imports())
|
|
930
1037
|
|
|
@@ -939,12 +1046,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
939
1046
|
stateful_components_path,
|
|
940
1047
|
stateful_components_code,
|
|
941
1048
|
page_components,
|
|
942
|
-
) = compiler.compile_stateful_components(self.
|
|
1049
|
+
) = compiler.compile_stateful_components(self._pages.values())
|
|
943
1050
|
|
|
944
1051
|
progress.advance(task)
|
|
945
1052
|
|
|
946
1053
|
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
|
947
|
-
if code_uses_state_contexts(stateful_components_code) and self.
|
|
1054
|
+
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
|
948
1055
|
raise ReflexRuntimeError(
|
|
949
1056
|
"To access rx.State in frontend components, at least one "
|
|
950
1057
|
"subclass of rx.State must be defined in the app."
|
|
@@ -958,7 +1065,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
958
1065
|
compiler.compile_document_root(
|
|
959
1066
|
self.head_components,
|
|
960
1067
|
html_lang=self.html_lang,
|
|
961
|
-
html_custom_attrs=self.html_custom_attrs, #
|
|
1068
|
+
html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType]
|
|
962
1069
|
)
|
|
963
1070
|
)
|
|
964
1071
|
|
|
@@ -981,20 +1088,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
981
1088
|
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
|
982
1089
|
)
|
|
983
1090
|
|
|
984
|
-
for route, component in zip(self.
|
|
1091
|
+
for route, component in zip(self._pages, page_components, strict=True):
|
|
985
1092
|
ExecutorSafeFunctions.COMPONENTS[route] = component
|
|
986
1093
|
|
|
987
|
-
ExecutorSafeFunctions.STATE = self.
|
|
1094
|
+
ExecutorSafeFunctions.STATE = self._state
|
|
988
1095
|
|
|
989
1096
|
with executor:
|
|
990
1097
|
result_futures = []
|
|
991
1098
|
|
|
992
|
-
def _submit_work(fn, *args, **kwargs):
|
|
1099
|
+
def _submit_work(fn: Callable, *args, **kwargs):
|
|
993
1100
|
f = executor.submit(fn, *args, **kwargs)
|
|
994
1101
|
result_futures.append(f)
|
|
995
1102
|
|
|
996
1103
|
# Compile the pre-compiled pages.
|
|
997
|
-
for route in self.
|
|
1104
|
+
for route in self._pages:
|
|
998
1105
|
_submit_work(
|
|
999
1106
|
ExecutorSafeFunctions.compile_page,
|
|
1000
1107
|
route,
|
|
@@ -1029,7 +1136,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1029
1136
|
|
|
1030
1137
|
# Compile the contexts.
|
|
1031
1138
|
compile_results.append(
|
|
1032
|
-
compiler.compile_contexts(self.
|
|
1139
|
+
compiler.compile_contexts(self._state, self.theme),
|
|
1033
1140
|
)
|
|
1034
1141
|
if self.theme is not None:
|
|
1035
1142
|
# Fix #2992 by removing the top-level appearance prop
|
|
@@ -1151,9 +1258,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1151
1258
|
)
|
|
1152
1259
|
|
|
1153
1260
|
task = asyncio.create_task(_coro())
|
|
1154
|
-
self.
|
|
1261
|
+
self._background_tasks.add(task)
|
|
1155
1262
|
# Clean up task from background_tasks set when complete.
|
|
1156
|
-
task.add_done_callback(self.
|
|
1263
|
+
task.add_done_callback(self._background_tasks.discard)
|
|
1157
1264
|
return task
|
|
1158
1265
|
|
|
1159
1266
|
def _validate_exception_handlers(self):
|
|
@@ -1163,11 +1270,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1163
1270
|
ValueError: If the custom exception handlers are invalid.
|
|
1164
1271
|
|
|
1165
1272
|
"""
|
|
1166
|
-
|
|
1273
|
+
frontend_arg_spec = {
|
|
1167
1274
|
"exception": Exception,
|
|
1168
1275
|
}
|
|
1169
1276
|
|
|
1170
|
-
|
|
1277
|
+
backend_arg_spec = {
|
|
1171
1278
|
"exception": Exception,
|
|
1172
1279
|
}
|
|
1173
1280
|
|
|
@@ -1175,9 +1282,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1175
1282
|
["frontend", "backend"],
|
|
1176
1283
|
[self.frontend_exception_handler, self.backend_exception_handler],
|
|
1177
1284
|
[
|
|
1178
|
-
|
|
1179
|
-
|
|
1285
|
+
frontend_arg_spec,
|
|
1286
|
+
backend_arg_spec,
|
|
1180
1287
|
],
|
|
1288
|
+
strict=True,
|
|
1181
1289
|
):
|
|
1182
1290
|
if hasattr(handler_fn, "__name__"):
|
|
1183
1291
|
_fn_name = handler_fn.__name__
|
|
@@ -1218,7 +1326,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
1218
1326
|
):
|
|
1219
1327
|
raise ValueError(
|
|
1220
1328
|
f"Provided custom {handler_domain} exception handler `{_fn_name}` has the wrong argument order."
|
|
1221
|
-
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]}`"
|
|
1222
1330
|
)
|
|
1223
1331
|
|
|
1224
1332
|
if not issubclass(arg_annotations[required_arg], Exception):
|
|
@@ -1319,15 +1427,14 @@ async def process(
|
|
|
1319
1427
|
if app._process_background(state, event) is not None:
|
|
1320
1428
|
# `final=True` allows the frontend send more events immediately.
|
|
1321
1429
|
yield StateUpdate(final=True)
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
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
|
|
1331
1438
|
except Exception as ex:
|
|
1332
1439
|
telemetry.send_error(ex, context="backend")
|
|
1333
1440
|
|
|
@@ -1522,16 +1629,20 @@ class EventNamespace(AsyncNamespace):
|
|
|
1522
1629
|
self.sid_to_token = {}
|
|
1523
1630
|
self.app = app
|
|
1524
1631
|
|
|
1525
|
-
def on_connect(self, sid, environ):
|
|
1632
|
+
def on_connect(self, sid: str, environ: dict):
|
|
1526
1633
|
"""Event for when the websocket is connected.
|
|
1527
1634
|
|
|
1528
1635
|
Args:
|
|
1529
1636
|
sid: The Socket.IO session id.
|
|
1530
1637
|
environ: The request information, including HTTP headers.
|
|
1531
1638
|
"""
|
|
1532
|
-
|
|
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
|
+
)
|
|
1533
1644
|
|
|
1534
|
-
def on_disconnect(self, sid):
|
|
1645
|
+
def on_disconnect(self, sid: str):
|
|
1535
1646
|
"""Event for when the websocket disconnects.
|
|
1536
1647
|
|
|
1537
1648
|
Args:
|
|
@@ -1553,7 +1664,7 @@ class EventNamespace(AsyncNamespace):
|
|
|
1553
1664
|
self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
|
|
1554
1665
|
)
|
|
1555
1666
|
|
|
1556
|
-
async def on_event(self, sid, data):
|
|
1667
|
+
async def on_event(self, sid: str, data: Any):
|
|
1557
1668
|
"""Event for receiving front-end websocket events.
|
|
1558
1669
|
|
|
1559
1670
|
Raises:
|
|
@@ -1562,12 +1673,36 @@ class EventNamespace(AsyncNamespace):
|
|
|
1562
1673
|
Args:
|
|
1563
1674
|
sid: The Socket.IO session id.
|
|
1564
1675
|
data: The event data.
|
|
1676
|
+
|
|
1677
|
+
Raises:
|
|
1678
|
+
EventDeserializationError: If the event data is not a dictionary.
|
|
1565
1679
|
"""
|
|
1566
1680
|
fields = data
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
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
|
|
1571
1706
|
|
|
1572
1707
|
self.token_to_sid[event.token] = sid
|
|
1573
1708
|
self.sid_to_token[sid] = event.token
|
|
@@ -1596,7 +1731,7 @@ class EventNamespace(AsyncNamespace):
|
|
|
1596
1731
|
# Emit the update from processing the event.
|
|
1597
1732
|
await self.emit_update(update=update, sid=sid)
|
|
1598
1733
|
|
|
1599
|
-
async def on_ping(self, sid):
|
|
1734
|
+
async def on_ping(self, sid: str):
|
|
1600
1735
|
"""Event for testing the API endpoint.
|
|
1601
1736
|
|
|
1602
1737
|
Args:
|