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.

Files changed (131) hide show
  1. reflex/.templates/apps/blank/code/blank.py +19 -16
  2. reflex/.templates/apps/demo/code/pages/datatable.py +4 -4
  3. reflex/.templates/apps/demo/code/pages/forms.py +2 -2
  4. reflex/.templates/web/utils/helpers/debounce.js +17 -0
  5. reflex/.templates/web/utils/helpers/throttle.js +22 -0
  6. reflex/.templates/web/utils/state.js +21 -3
  7. reflex/__init__.py +6 -1
  8. reflex/__init__.pyi +4 -1
  9. reflex/app.py +157 -140
  10. reflex/app_module_for_backend.py +1 -1
  11. reflex/base.py +13 -15
  12. reflex/compiler/compiler.py +10 -1
  13. reflex/compiler/utils.py +3 -30
  14. reflex/components/__init__.py +1 -0
  15. reflex/components/chakra/datadisplay/list.py +1 -3
  16. reflex/components/chakra/datadisplay/list.pyi +3 -3
  17. reflex/components/chakra/disclosure/accordion.py +1 -1
  18. reflex/components/chakra/forms/pininput.pyi +1 -1
  19. reflex/components/chakra/media/icon.py +2 -2
  20. reflex/components/component.py +279 -32
  21. reflex/components/core/__init__.py +2 -2
  22. reflex/components/core/cond.py +1 -10
  23. reflex/components/core/debounce.py +5 -2
  24. reflex/components/core/debounce.pyi +4 -2
  25. reflex/components/core/foreach.py +1 -16
  26. reflex/components/core/html.py +6 -0
  27. reflex/components/core/match.py +2 -17
  28. reflex/components/core/upload.py +42 -1
  29. reflex/components/core/upload.pyi +199 -1
  30. reflex/components/datadisplay/code.py +7 -3
  31. reflex/components/datadisplay/code.pyi +3 -1
  32. reflex/components/el/elements/forms.py +1 -1
  33. reflex/components/el/elements/forms.pyi +1 -1
  34. reflex/components/lucide/icon.py +5 -13
  35. reflex/components/lucide/icon.pyi +0 -1
  36. reflex/components/markdown/markdown.py +5 -23
  37. reflex/components/markdown/markdown.pyi +1 -4
  38. reflex/components/radix/primitives/accordion.py +227 -406
  39. reflex/components/radix/primitives/accordion.pyi +369 -28
  40. reflex/components/radix/primitives/form.py +33 -29
  41. reflex/components/radix/primitives/form.pyi +7 -2
  42. reflex/components/radix/primitives/progress.py +17 -9
  43. reflex/components/radix/primitives/progress.pyi +2 -0
  44. reflex/components/radix/primitives/slider.py +30 -18
  45. reflex/components/radix/primitives/slider.pyi +4 -0
  46. reflex/components/radix/themes/base.py +8 -1
  47. reflex/components/radix/themes/base.pyi +79 -1
  48. reflex/components/radix/themes/color_mode.py +74 -30
  49. reflex/components/radix/themes/color_mode.pyi +26 -185
  50. reflex/components/radix/themes/components/__init__.py +17 -0
  51. reflex/components/radix/themes/components/badge.py +2 -1
  52. reflex/components/radix/themes/components/badge.pyi +3 -1
  53. reflex/components/radix/themes/components/button.py +3 -1
  54. reflex/components/radix/themes/components/button.pyi +4 -1
  55. reflex/components/radix/themes/components/checkbox_cards.py +48 -0
  56. reflex/components/radix/themes/components/checkbox_cards.pyi +264 -0
  57. reflex/components/radix/themes/components/checkbox_group.py +42 -0
  58. reflex/components/radix/themes/components/checkbox_group.pyi +253 -0
  59. reflex/components/radix/themes/components/data_list.py +63 -0
  60. reflex/components/radix/themes/components/data_list.pyi +426 -0
  61. reflex/components/radix/themes/components/icon_button.py +20 -17
  62. reflex/components/radix/themes/components/icon_button.pyi +5 -1
  63. reflex/components/radix/themes/components/progress.py +55 -0
  64. reflex/components/radix/themes/components/progress.pyi +180 -0
  65. reflex/components/radix/themes/components/radio.py +31 -0
  66. reflex/components/radix/themes/components/radio.pyi +169 -0
  67. reflex/components/radix/themes/components/radio_cards.py +48 -0
  68. reflex/components/radix/themes/components/radio_cards.pyi +264 -0
  69. reflex/components/radix/themes/components/radio_group.py +2 -4
  70. reflex/components/radix/themes/components/segmented_control.py +48 -0
  71. reflex/components/radix/themes/components/segmented_control.pyi +262 -0
  72. reflex/components/radix/themes/components/skeleton.py +32 -0
  73. reflex/components/radix/themes/components/skeleton.pyi +106 -0
  74. reflex/components/radix/themes/components/spinner.py +26 -0
  75. reflex/components/radix/themes/components/spinner.pyi +101 -0
  76. reflex/components/radix/themes/components/tabs.py +26 -1
  77. reflex/components/radix/themes/components/tabs.pyi +69 -9
  78. reflex/components/radix/themes/components/text_field.py +101 -71
  79. reflex/components/radix/themes/components/text_field.pyi +81 -499
  80. reflex/components/radix/themes/layout/base.py +2 -2
  81. reflex/components/radix/themes/layout/base.pyi +4 -4
  82. reflex/components/radix/themes/layout/center.py +8 -3
  83. reflex/components/radix/themes/layout/center.pyi +2 -1
  84. reflex/components/radix/themes/layout/container.py +30 -2
  85. reflex/components/radix/themes/layout/container.pyi +9 -30
  86. reflex/components/radix/themes/layout/list.py +10 -5
  87. reflex/components/radix/themes/layout/list.pyi +5 -21
  88. reflex/components/radix/themes/layout/spacer.py +8 -3
  89. reflex/components/radix/themes/layout/spacer.pyi +2 -1
  90. reflex/components/radix/themes/layout/stack.py +7 -1
  91. reflex/components/radix/themes/layout/stack.pyi +3 -3
  92. reflex/components/radix/themes/typography/link.py +10 -2
  93. reflex/components/radix/themes/typography/link.pyi +5 -4
  94. reflex/components/sonner/__init__.py +3 -0
  95. reflex/components/sonner/toast.py +267 -0
  96. reflex/components/sonner/toast.pyi +205 -0
  97. reflex/components/tags/iter_tag.py +9 -6
  98. reflex/config.py +30 -54
  99. reflex/constants/__init__.py +0 -2
  100. reflex/constants/base.py +0 -5
  101. reflex/constants/colors.py +2 -0
  102. reflex/constants/installer.py +5 -1
  103. reflex/constants/route.py +4 -0
  104. reflex/custom_components/custom_components.py +22 -1
  105. reflex/event.py +75 -30
  106. reflex/experimental/__init__.py +5 -0
  107. reflex/experimental/layout.py +24 -6
  108. reflex/model.py +2 -1
  109. reflex/page.py +7 -4
  110. reflex/reflex.py +8 -3
  111. reflex/route.py +39 -0
  112. reflex/state.py +128 -131
  113. reflex/style.py +20 -1
  114. reflex/testing.py +10 -6
  115. reflex/utils/console.py +3 -1
  116. reflex/utils/exec.py +20 -7
  117. reflex/utils/format.py +1 -1
  118. reflex/utils/imports.py +3 -1
  119. reflex/utils/prerequisites.py +141 -20
  120. reflex/utils/processes.py +21 -1
  121. reflex/utils/pyi_generator.py +95 -5
  122. reflex/utils/serializers.py +1 -1
  123. reflex/utils/telemetry.py +26 -4
  124. reflex/utils/types.py +62 -18
  125. reflex/vars.py +11 -5
  126. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/METADATA +16 -4
  127. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/RECORD +130 -108
  128. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/WHEEL +1 -1
  129. reflex/app.pyi +0 -149
  130. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/LICENSE +0 -0
  131. {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
- """A Reflex application."""
104
+ """The main Reflex app that encapsulates the backend and frontend.
106
105
 
107
- # A map from a page route to the component to render.
108
- pages: Dict[str, Component] = {}
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
- # The backend API object.
114
- api: FastAPI = None # type: ignore
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
- # The Socket.IO AsyncServer.
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 socket app.
120
- socket_app: Optional[ASGIApp] = None
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
- # The styling to apply to each component.
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
- # List of event handlers to trigger when a page loads.
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
- # Components to add to the head of every page.
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
- # The radix theme for the entire app
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__(*args, **kwargs)
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
- if len(base_state_subclasses) > 1:
186
- raise ValueError(
187
- "rx.BaseState cannot be subclassed multiple times. use rx.State instead"
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.add_cors()
203
- self.add_default_endpoints()
207
+ self._add_cors()
208
+ self._add_default_endpoints()
204
209
 
205
- self.setup_state()
210
+ self._setup_state()
206
211
 
207
212
  # Set up the admin dash.
208
- self.setup_admin_dash()
213
+ self._setup_admin_dash()
209
214
 
210
- def enable_state(self) -> None:
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.setup_state()
219
+ self._setup_state()
220
+
221
+ def _setup_state(self) -> None:
222
+ """Set up the state for the app.
215
223
 
216
- def setup_state(self) -> None:
217
- """Set up the state for the app."""
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 = AsyncServer(
228
- async_mode="asgi",
229
- cors_allowed_origins=(
230
- "*"
231
- if config.cors_allowed_origins == ["*"]
232
- else config.cors_allowed_origins
233
- ),
234
- cors_credentials=True,
235
- max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
236
- ping_interval=constants.Ping.INTERVAL,
237
- ping_timeout=constants.Ping.TIMEOUT,
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
- self.socket_app = ASGIApp(self.sio, socketio_path="")
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), self.socket_app)
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 add_default_endpoints(self):
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 add_optional_endpoints(self):
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 add_cors(self):
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 preprocess(self, state: BaseState, event: Event) -> StateUpdate | None:
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 postprocess(
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, state=state, event=event, update=update # type: ignore
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, state=state, event=event, update=update # type: ignore
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
- script_tags: List of script tags to be added to component
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.enable_state()
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.enable_state()
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
- newroute_catchall = catchall_in_route(new_route)
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
- route = "" if route == "index" else route
541
-
542
- if new_route.startswith(f"{route}/[[..."):
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
- raise ValueError(
554
- f"You cannot use multiple catchall for the same dynamic route ({route} !== {new_route})"
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 setup_admin_dash(self):
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 get_frontend_packages(self, imports: Dict[str, set[ImportVar]]):
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
- >>> get_frontend_packages({"react": "16.14.0", "react-dom": "16.14.0"})
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 compile_(self, export: bool = False):
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.add_optional_endpoints()
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.get_frontend_packages(all_imports)
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.postprocess(state, event, update)
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.preprocess(state, event)
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.postprocess(state, event, update)
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.postprocess(state, event, update)
1224
+ update = await app._postprocess(state, event, update)
1208
1225
  yield update.json() + "\n"
1209
1226
 
1210
1227
  # Stream updates to client
@@ -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.compile_)
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
- # TODO The type checking guard can be removed once
9
- # reflex-hosting-cli tools are compatible with pydantic v2
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
- import pydantic
19
- from pydantic import BaseModel
20
- from pydantic.fields import ModelField
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(self.dict(), default=serialize)
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
 
@@ -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.