reflex 0.6.8a1__py3-none-any.whl → 0.7.0__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 (248) hide show
  1. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
  2. reflex/.templates/jinja/web/pages/_app.js.jinja2 +7 -7
  3. reflex/.templates/jinja/web/pages/utils.js.jinja2 +3 -3
  4. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -4
  5. reflex/.templates/web/utils/state.js +65 -36
  6. reflex/__init__.py +4 -17
  7. reflex/__init__.pyi +1 -2
  8. reflex/app.py +286 -135
  9. reflex/app_mixins/lifespan.py +9 -9
  10. reflex/app_mixins/middleware.py +6 -6
  11. reflex/app_module_for_backend.py +3 -7
  12. reflex/base.py +7 -7
  13. reflex/compiler/compiler.py +8 -0
  14. reflex/compiler/utils.py +57 -18
  15. reflex/components/base/app_wrap.pyi +16 -16
  16. reflex/components/base/bare.py +1 -1
  17. reflex/components/base/body.pyi +16 -16
  18. reflex/components/base/document.pyi +76 -76
  19. reflex/components/base/error_boundary.py +2 -1
  20. reflex/components/base/error_boundary.pyi +19 -22
  21. reflex/components/base/fragment.pyi +16 -16
  22. reflex/components/base/head.pyi +31 -31
  23. reflex/components/base/link.pyi +31 -31
  24. reflex/components/base/meta.py +2 -2
  25. reflex/components/base/meta.pyi +61 -61
  26. reflex/components/base/script.pyi +19 -19
  27. reflex/components/base/strict_mode.py +10 -0
  28. reflex/components/base/strict_mode.pyi +57 -0
  29. reflex/components/component.py +38 -77
  30. reflex/components/core/banner.py +159 -4
  31. reflex/components/core/banner.pyi +162 -76
  32. reflex/components/core/breakpoints.py +3 -1
  33. reflex/components/core/client_side_routing.py +1 -1
  34. reflex/components/core/client_side_routing.pyi +32 -32
  35. reflex/components/core/clipboard.pyi +17 -20
  36. reflex/components/core/cond.py +9 -10
  37. reflex/components/core/debounce.py +1 -1
  38. reflex/components/core/debounce.pyi +17 -17
  39. reflex/components/core/foreach.py +28 -3
  40. reflex/components/core/html.py +1 -1
  41. reflex/components/core/html.pyi +16 -16
  42. reflex/components/core/match.py +5 -5
  43. reflex/components/core/sticky.py +134 -0
  44. reflex/components/core/sticky.pyi +449 -0
  45. reflex/components/core/upload.py +2 -2
  46. reflex/components/core/upload.pyi +80 -88
  47. reflex/components/datadisplay/code.py +5 -14
  48. reflex/components/datadisplay/code.pyi +31 -31
  49. reflex/components/datadisplay/dataeditor.py +7 -4
  50. reflex/components/datadisplay/dataeditor.pyi +40 -54
  51. reflex/components/datadisplay/logo.py +13 -8
  52. reflex/components/datadisplay/shiki_code_block.py +14 -9
  53. reflex/components/datadisplay/shiki_code_block.pyi +46 -46
  54. reflex/components/dynamic.py +22 -3
  55. reflex/components/el/constants/reflex.py +1 -1
  56. reflex/components/el/element.py +1 -1
  57. reflex/components/el/element.pyi +16 -16
  58. reflex/components/el/elements/base.pyi +16 -16
  59. reflex/components/el/elements/forms.py +4 -4
  60. reflex/components/el/elements/forms.pyi +224 -258
  61. reflex/components/el/elements/inline.pyi +421 -421
  62. reflex/components/el/elements/media.pyi +376 -376
  63. reflex/components/el/elements/metadata.pyi +91 -91
  64. reflex/components/el/elements/other.pyi +106 -106
  65. reflex/components/el/elements/scripts.pyi +46 -46
  66. reflex/components/el/elements/sectioning.pyi +226 -226
  67. reflex/components/el/elements/tables.pyi +151 -151
  68. reflex/components/el/elements/typography.pyi +226 -226
  69. reflex/components/gridjs/datatable.pyi +31 -31
  70. reflex/components/lucide/icon.py +46 -8
  71. reflex/components/lucide/icon.pyi +85 -31
  72. reflex/components/markdown/markdown.py +10 -8
  73. reflex/components/markdown/markdown.pyi +16 -16
  74. reflex/components/moment/moment.py +2 -2
  75. reflex/components/moment/moment.pyi +17 -19
  76. reflex/components/next/base.pyi +16 -16
  77. reflex/components/next/image.py +16 -4
  78. reflex/components/next/image.pyi +22 -20
  79. reflex/components/next/link.py +1 -1
  80. reflex/components/next/link.pyi +16 -16
  81. reflex/components/next/video.pyi +16 -16
  82. reflex/components/plotly/__init__.py +29 -2
  83. reflex/components/plotly/plotly.py +240 -5
  84. reflex/components/plotly/plotly.pyi +799 -44
  85. reflex/components/props.py +3 -3
  86. reflex/components/radix/__init__.pyi +1 -1
  87. reflex/components/radix/primitives/accordion.py +9 -5
  88. reflex/components/radix/primitives/accordion.pyi +110 -108
  89. reflex/components/radix/primitives/base.pyi +31 -31
  90. reflex/components/radix/primitives/drawer.py +5 -2
  91. reflex/components/radix/primitives/drawer.pyi +179 -187
  92. reflex/components/radix/primitives/form.pyi +160 -172
  93. reflex/components/radix/primitives/progress.py +1 -1
  94. reflex/components/radix/primitives/progress.pyi +76 -76
  95. reflex/components/radix/primitives/slider.py +1 -1
  96. reflex/components/radix/primitives/slider.pyi +78 -82
  97. reflex/components/radix/themes/base.pyi +121 -121
  98. reflex/components/radix/themes/color_mode.py +11 -9
  99. reflex/components/radix/themes/color_mode.pyi +47 -49
  100. reflex/components/radix/themes/components/alert_dialog.py +3 -0
  101. reflex/components/radix/themes/components/alert_dialog.pyi +110 -112
  102. reflex/components/radix/themes/components/aspect_ratio.pyi +16 -16
  103. reflex/components/radix/themes/components/avatar.pyi +16 -16
  104. reflex/components/radix/themes/components/badge.pyi +16 -16
  105. reflex/components/radix/themes/components/button.pyi +16 -16
  106. reflex/components/radix/themes/components/callout.pyi +76 -76
  107. reflex/components/radix/themes/components/card.py +1 -1
  108. reflex/components/radix/themes/components/card.pyi +17 -17
  109. reflex/components/radix/themes/components/checkbox.pyi +49 -55
  110. reflex/components/radix/themes/components/checkbox_cards.pyi +31 -31
  111. reflex/components/radix/themes/components/checkbox_group.pyi +31 -31
  112. reflex/components/radix/themes/components/context_menu.py +5 -0
  113. reflex/components/radix/themes/components/context_menu.pyi +149 -155
  114. reflex/components/radix/themes/components/data_list.pyi +61 -61
  115. reflex/components/radix/themes/components/dialog.py +3 -0
  116. reflex/components/radix/themes/components/dialog.pyi +113 -117
  117. reflex/components/radix/themes/components/dropdown_menu.py +5 -0
  118. reflex/components/radix/themes/components/dropdown_menu.pyi +133 -137
  119. reflex/components/radix/themes/components/hover_card.py +3 -0
  120. reflex/components/radix/themes/components/hover_card.pyi +63 -67
  121. reflex/components/radix/themes/components/icon_button.py +2 -2
  122. reflex/components/radix/themes/components/icon_button.pyi +17 -16
  123. reflex/components/radix/themes/components/inset.pyi +16 -16
  124. reflex/components/radix/themes/components/popover.py +3 -0
  125. reflex/components/radix/themes/components/popover.pyi +68 -70
  126. reflex/components/radix/themes/components/progress.pyi +16 -16
  127. reflex/components/radix/themes/components/radio.pyi +16 -16
  128. reflex/components/radix/themes/components/radio_cards.py +2 -0
  129. reflex/components/radix/themes/components/radio_cards.pyi +32 -34
  130. reflex/components/radix/themes/components/radio_group.py +1 -1
  131. reflex/components/radix/themes/components/radio_group.pyi +62 -64
  132. reflex/components/radix/themes/components/scroll_area.pyi +16 -16
  133. reflex/components/radix/themes/components/segmented_control.pyi +32 -35
  134. reflex/components/radix/themes/components/select.py +4 -0
  135. reflex/components/radix/themes/components/select.pyi +145 -157
  136. reflex/components/radix/themes/components/separator.pyi +16 -16
  137. reflex/components/radix/themes/components/skeleton.py +3 -0
  138. reflex/components/radix/themes/components/skeleton.pyi +16 -16
  139. reflex/components/radix/themes/components/slider.pyi +22 -28
  140. reflex/components/radix/themes/components/spinner.pyi +16 -16
  141. reflex/components/radix/themes/components/switch.pyi +17 -19
  142. reflex/components/radix/themes/components/table.pyi +106 -106
  143. reflex/components/radix/themes/components/tabs.py +3 -0
  144. reflex/components/radix/themes/components/tabs.pyi +78 -82
  145. reflex/components/radix/themes/components/text_area.py +12 -0
  146. reflex/components/radix/themes/components/text_area.pyi +21 -33
  147. reflex/components/radix/themes/components/text_field.py +1 -1
  148. reflex/components/radix/themes/components/text_field.pyi +52 -80
  149. reflex/components/radix/themes/components/tooltip.py +6 -1
  150. reflex/components/radix/themes/components/tooltip.pyi +20 -21
  151. reflex/components/radix/themes/layout/__init__.pyi +1 -1
  152. reflex/components/radix/themes/layout/base.pyi +16 -16
  153. reflex/components/radix/themes/layout/box.pyi +16 -16
  154. reflex/components/radix/themes/layout/center.pyi +16 -16
  155. reflex/components/radix/themes/layout/container.pyi +16 -16
  156. reflex/components/radix/themes/layout/flex.pyi +16 -16
  157. reflex/components/radix/themes/layout/grid.pyi +16 -16
  158. reflex/components/radix/themes/layout/list.py +2 -2
  159. reflex/components/radix/themes/layout/list.pyi +76 -76
  160. reflex/components/radix/themes/layout/section.pyi +16 -16
  161. reflex/components/radix/themes/layout/spacer.pyi +16 -16
  162. reflex/components/radix/themes/layout/stack.py +2 -2
  163. reflex/components/radix/themes/layout/stack.pyi +46 -46
  164. reflex/components/radix/themes/typography/blockquote.pyi +16 -16
  165. reflex/components/radix/themes/typography/code.pyi +16 -16
  166. reflex/components/radix/themes/typography/heading.pyi +16 -16
  167. reflex/components/radix/themes/typography/link.py +1 -1
  168. reflex/components/radix/themes/typography/link.pyi +16 -16
  169. reflex/components/radix/themes/typography/text.py +2 -2
  170. reflex/components/radix/themes/typography/text.pyi +106 -106
  171. reflex/components/react_player/audio.pyi +33 -39
  172. reflex/components/react_player/react_player.py +1 -1
  173. reflex/components/react_player/react_player.pyi +32 -38
  174. reflex/components/react_player/video.pyi +33 -39
  175. reflex/components/recharts/__init__.py +2 -0
  176. reflex/components/recharts/__init__.pyi +2 -0
  177. reflex/components/recharts/cartesian.pyi +282 -282
  178. reflex/components/recharts/charts.py +15 -15
  179. reflex/components/recharts/charts.pyi +164 -164
  180. reflex/components/recharts/general.py +19 -4
  181. reflex/components/recharts/general.pyi +132 -81
  182. reflex/components/recharts/polar.py +2 -2
  183. reflex/components/recharts/polar.pyi +55 -55
  184. reflex/components/recharts/recharts.py +4 -4
  185. reflex/components/recharts/recharts.pyi +31 -31
  186. reflex/components/sonner/toast.py +15 -13
  187. reflex/components/sonner/toast.pyi +22 -22
  188. reflex/components/suneditor/editor.py +6 -4
  189. reflex/components/suneditor/editor.pyi +26 -40
  190. reflex/components/tags/iter_tag.py +3 -3
  191. reflex/components/tags/tag.py +25 -3
  192. reflex/config.py +48 -20
  193. reflex/constants/__init__.py +1 -0
  194. reflex/constants/base.py +4 -1
  195. reflex/constants/compiler.py +5 -2
  196. reflex/constants/config.py +8 -1
  197. reflex/constants/installer.py +9 -9
  198. reflex/constants/style.py +1 -1
  199. reflex/custom_components/custom_components.py +18 -10
  200. reflex/event.py +228 -233
  201. reflex/experimental/__init__.py +19 -11
  202. reflex/experimental/client_state.py +53 -28
  203. reflex/experimental/hooks.py +5 -5
  204. reflex/experimental/layout.py +8 -5
  205. reflex/experimental/layout.pyi +79 -83
  206. reflex/experimental/misc.py +3 -3
  207. reflex/istate/wrappers.py +1 -1
  208. reflex/middleware/hydrate_middleware.py +2 -2
  209. reflex/model.py +11 -6
  210. reflex/page.py +5 -5
  211. reflex/reflex.py +104 -26
  212. reflex/route.py +1 -1
  213. reflex/state.py +358 -401
  214. reflex/style.py +27 -3
  215. reflex/testing.py +34 -39
  216. reflex/utils/build.py +6 -2
  217. reflex/utils/codespaces.py +1 -4
  218. reflex/utils/compat.py +6 -5
  219. reflex/utils/console.py +71 -21
  220. reflex/utils/exceptions.py +89 -26
  221. reflex/utils/exec.py +69 -74
  222. reflex/utils/export.py +6 -1
  223. reflex/utils/format.py +8 -40
  224. reflex/utils/imports.py +5 -2
  225. reflex/utils/lazy_loader.py +7 -1
  226. reflex/utils/path_ops.py +74 -14
  227. reflex/utils/prerequisites.py +345 -68
  228. reflex/utils/processes.py +45 -32
  229. reflex/utils/pyi_generator.py +39 -33
  230. reflex/utils/registry.py +4 -4
  231. reflex/utils/serializers.py +1 -1
  232. reflex/utils/telemetry.py +5 -4
  233. reflex/utils/types.py +42 -18
  234. reflex/vars/base.py +695 -330
  235. reflex/vars/datetime.py +6 -7
  236. reflex/vars/dep_tracking.py +344 -0
  237. reflex/vars/function.py +11 -5
  238. reflex/vars/number.py +31 -43
  239. reflex/vars/object.py +74 -64
  240. reflex/vars/sequence.py +79 -67
  241. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/METADATA +7 -10
  242. reflex-0.7.0.dist-info/RECORD +401 -0
  243. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/WHEEL +1 -1
  244. reflex/experimental/assets.py +0 -37
  245. reflex/proxy.py +0 -119
  246. reflex-0.6.8a1.dist-info/RECORD +0 -398
  247. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/LICENSE +0 -0
  248. {reflex-0.6.8a1.dist-info → reflex-0.7.0.dist-info}/entry_points.txt +0 -0
reflex/style.py CHANGED
@@ -78,7 +78,7 @@ def set_color_mode(
78
78
  _var_data=VarData.merge(
79
79
  base_setter._get_all_var_data(), new_color_mode._get_all_var_data()
80
80
  ),
81
- ).to(FunctionVar, EventChain) # type: ignore
81
+ ).to(FunctionVar, EventChain)
82
82
 
83
83
 
84
84
  # Var resolves to the current color mode for the app ("light", "dark" or "system")
@@ -182,7 +182,9 @@ def convert(
182
182
  var_data = None # Track import/hook data from any Vars in the style dict.
183
183
  out = {}
184
184
 
185
- def update_out_dict(return_value, keys_to_update):
185
+ def update_out_dict(
186
+ return_value: Var | dict | list | str, keys_to_update: tuple[str, ...]
187
+ ):
186
188
  for k in keys_to_update:
187
189
  out[k] = return_value
188
190
 
@@ -287,9 +289,31 @@ class Style(dict):
287
289
  _var = LiteralVar.create(value)
288
290
  if _var is not None:
289
291
  # Carry the imports/hooks when setting a Var as a value.
290
- self._var_data = VarData.merge(self._var_data, _var._get_all_var_data())
292
+ self._var_data = VarData.merge(
293
+ getattr(self, "_var_data", None), _var._get_all_var_data()
294
+ )
291
295
  super().__setitem__(key, value)
292
296
 
297
+ def __or__(self, other: Style | dict) -> Style:
298
+ """Combine two styles.
299
+
300
+ Args:
301
+ other: The other style to combine.
302
+
303
+ Returns:
304
+ The combined style.
305
+ """
306
+ other_var_data = None
307
+ if not isinstance(other, Style):
308
+ other_dict, other_var_data = convert(other)
309
+ else:
310
+ other_dict, other_var_data = other, other._var_data
311
+
312
+ new_style = Style(super().__or__(other_dict))
313
+ if self._var_data or other_var_data:
314
+ new_style._var_data = VarData.merge(self._var_data, other_var_data)
315
+ return new_style
316
+
293
317
 
294
318
  def _format_emotion_style_pseudo_selector(key: str) -> str:
295
319
  """Format a pseudo selector for emotion CSS-in-JS.
reflex/testing.py CHANGED
@@ -44,7 +44,6 @@ import reflex.utils.format
44
44
  import reflex.utils.prerequisites
45
45
  import reflex.utils.processes
46
46
  from reflex.config import environment
47
- from reflex.proxy import proxy_middleware
48
47
  from reflex.state import (
49
48
  BaseState,
50
49
  StateManager,
@@ -81,17 +80,17 @@ T = TypeVar("T")
81
80
  TimeoutType = Optional[Union[int, float]]
82
81
 
83
82
  if platform.system() == "Windows":
84
- FRONTEND_POPEN_ARGS["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
83
+ FRONTEND_POPEN_ARGS["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # pyright: ignore [reportAttributeAccessIssue]
85
84
  FRONTEND_POPEN_ARGS["shell"] = True
86
85
  else:
87
86
  FRONTEND_POPEN_ARGS["start_new_session"] = True
88
87
 
89
88
 
90
89
  # borrowed from py3.11
91
- class chdir(contextlib.AbstractContextManager):
90
+ class chdir(contextlib.AbstractContextManager): # noqa: N801
92
91
  """Non thread-safe context manager to change the current working directory."""
93
92
 
94
- def __init__(self, path):
93
+ def __init__(self, path: str | Path):
95
94
  """Prepare contextmanager.
96
95
 
97
96
  Args:
@@ -259,7 +258,7 @@ class AppHarness:
259
258
  if self.app_source is not None:
260
259
  app_globals = self._get_globals_from_signature(self.app_source)
261
260
  if isinstance(self.app_source, functools.partial):
262
- self.app_source = self.app_source.func # type: ignore
261
+ self.app_source = self.app_source.func
263
262
  # get the source from a function or module object
264
263
  source_code = "\n".join(
265
264
  [
@@ -283,6 +282,7 @@ class AppHarness:
283
282
  before_decorated_pages = reflex.app.DECORATED_PAGES[self.app_name].copy()
284
283
  # Ensure the AppHarness test does not skip State assignment due to running via pytest
285
284
  os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
285
+ os.environ[reflex.constants.APP_HARNESS_FLAG] = "true"
286
286
  self.app_module = reflex.utils.prerequisites.get_compiled_app(
287
287
  # Do not reload the module for pre-existing apps (only apps generated from source)
288
288
  reload=self.app_source is not None
@@ -294,14 +294,15 @@ class AppHarness:
294
294
  if p not in before_decorated_pages
295
295
  ]
296
296
  self.app_instance = self.app_module.app
297
- if isinstance(self.app_instance._state_manager, StateManagerRedis):
297
+ if self.app_instance and isinstance(
298
+ self.app_instance._state_manager, StateManagerRedis
299
+ ):
298
300
  # Create our own redis connection for testing.
299
- self.state_manager = StateManagerRedis.create(self.app_instance.state)
301
+ self.state_manager = StateManagerRedis.create(self.app_instance._state) # pyright: ignore [reportArgumentType]
300
302
  else:
301
- self.state_manager = self.app_instance._state_manager
302
- # Disable proxy for app harness tests.
303
- if proxy_middleware in self.app_instance.lifespan_tasks:
304
- self.app_instance.lifespan_tasks.remove(proxy_middleware)
303
+ self.state_manager = (
304
+ self.app_instance._state_manager if self.app_instance else None
305
+ )
305
306
 
306
307
  def _reload_state_module(self):
307
308
  """Reload the rx.State module to avoid conflict when reloading."""
@@ -326,8 +327,8 @@ class AppHarness:
326
327
 
327
328
  return _shutdown_redis
328
329
 
329
- def _start_backend(self, port=0):
330
- if self.app_instance is None:
330
+ def _start_backend(self, port: int = 0):
331
+ if self.app_instance is None or self.app_instance.api is None:
331
332
  raise RuntimeError("App was not initialized.")
332
333
  self.backend = uvicorn.Server(
333
334
  uvicorn.Config(
@@ -356,12 +357,12 @@ class AppHarness:
356
357
  self.app_instance.state_manager,
357
358
  StateManagerRedis,
358
359
  )
359
- and self.app_instance.state is not None
360
+ and self.app_instance._state is not None
360
361
  ):
361
362
  with contextlib.suppress(RuntimeError):
362
363
  await self.app_instance.state_manager.close()
363
364
  self.app_instance._state_manager = StateManagerRedis.create(
364
- state=self.app_instance.state,
365
+ state=self.app_instance._state,
365
366
  )
366
367
  if not isinstance(self.app_instance.state_manager, StateManagerRedis):
367
368
  raise RuntimeError("Failed to reset state manager.")
@@ -369,12 +370,9 @@ class AppHarness:
369
370
  def _start_frontend(self):
370
371
  # Set up the frontend.
371
372
  with chdir(self.app_path):
372
- backend_host, backend_port = self._poll_for_servers().getsockname()
373
373
  config = reflex.config.get_config()
374
- config.backend_port = backend_port
375
374
  config.api_url = "http://{0}:{1}".format(
376
- backend_host,
377
- backend_port,
375
+ *self._poll_for_servers().getsockname(),
378
376
  )
379
377
  reflex.utils.build.setup_frontend(self.app_path)
380
378
 
@@ -399,7 +397,6 @@ class AppHarness:
399
397
  self.frontend_url = m.group(1)
400
398
  config = reflex.config.get_config()
401
399
  config.deploy_url = self.frontend_url
402
- config.frontend_port = int(self.frontend_url.rpartition(":")[2])
403
400
  break
404
401
  if self.frontend_url is None:
405
402
  raise RuntimeError("Frontend did not start")
@@ -433,7 +430,7 @@ class AppHarness:
433
430
  return self
434
431
 
435
432
  @staticmethod
436
- def get_app_global_source(key, value):
433
+ def get_app_global_source(key: str, value: Any):
437
434
  """Get the source code of a global object.
438
435
  If value is a function or class we render the actual
439
436
  source of value otherwise we assign value to key.
@@ -628,23 +625,23 @@ class AppHarness:
628
625
  want_headless = True
629
626
  if driver_clz is None:
630
627
  requested_driver = environment.APP_HARNESS_DRIVER.get()
631
- driver_clz = getattr(webdriver, requested_driver)
628
+ driver_clz = getattr(webdriver, requested_driver) # pyright: ignore [reportPossiblyUnboundVariable]
632
629
  if driver_options is None:
633
- driver_options = getattr(webdriver, f"{requested_driver}Options")()
634
- if driver_clz is webdriver.Chrome:
630
+ driver_options = getattr(webdriver, f"{requested_driver}Options")() # pyright: ignore [reportPossiblyUnboundVariable]
631
+ if driver_clz is webdriver.Chrome: # pyright: ignore [reportPossiblyUnboundVariable]
635
632
  if driver_options is None:
636
- driver_options = webdriver.ChromeOptions()
633
+ driver_options = webdriver.ChromeOptions() # pyright: ignore [reportPossiblyUnboundVariable]
637
634
  driver_options.add_argument("--class=AppHarness")
638
635
  if want_headless:
639
636
  driver_options.add_argument("--headless=new")
640
- elif driver_clz is webdriver.Firefox:
637
+ elif driver_clz is webdriver.Firefox: # pyright: ignore [reportPossiblyUnboundVariable]
641
638
  if driver_options is None:
642
- driver_options = webdriver.FirefoxOptions()
639
+ driver_options = webdriver.FirefoxOptions() # pyright: ignore [reportPossiblyUnboundVariable]
643
640
  if want_headless:
644
641
  driver_options.add_argument("-headless")
645
- elif driver_clz is webdriver.Edge:
642
+ elif driver_clz is webdriver.Edge: # pyright: ignore [reportPossiblyUnboundVariable]
646
643
  if driver_options is None:
647
- driver_options = webdriver.EdgeOptions()
644
+ driver_options = webdriver.EdgeOptions() # pyright: ignore [reportPossiblyUnboundVariable]
648
645
  if want_headless:
649
646
  driver_options.add_argument("headless")
650
647
  if driver_options is None:
@@ -660,7 +657,7 @@ class AppHarness:
660
657
  driver_options.set_capability(key, value)
661
658
  if driver_kwargs is None:
662
659
  driver_kwargs = {}
663
- driver = driver_clz(options=driver_options, **driver_kwargs) # type: ignore
660
+ driver = driver_clz(options=driver_options, **driver_kwargs) # pyright: ignore [reportOptionalCall, reportArgumentType]
664
661
  driver.get(self.frontend_url)
665
662
  self._frontends.append(driver)
666
663
  return driver
@@ -892,8 +889,8 @@ class Subdir404TCPServer(socketserver.TCPServer):
892
889
  request,
893
890
  client_address,
894
891
  self,
895
- directory=str(self.root), # type: ignore
896
- error_page_map=self.error_page_map, # type: ignore
892
+ directory=str(self.root), # pyright: ignore [reportCallIssue]
893
+ error_page_map=self.error_page_map, # pyright: ignore [reportCallIssue]
897
894
  )
898
895
 
899
896
 
@@ -923,26 +920,24 @@ class AppHarnessProd(AppHarness):
923
920
  root=web_root,
924
921
  error_page_map=error_page_map,
925
922
  ) as self.frontend_server:
926
- config = reflex.config.get_config()
927
- config.frontend_port = self.frontend_server.server_address[1]
928
- self.frontend_url = f"http://localhost:{config.frontend_port}"
923
+ self.frontend_url = "http://localhost:{1}".format(
924
+ *self.frontend_server.socket.getsockname()
925
+ )
929
926
  self.frontend_server.serve_forever()
930
927
 
931
928
  def _start_frontend(self):
932
929
  # Set up the frontend.
933
930
  with chdir(self.app_path):
934
- backend_host, backend_port = self._poll_for_servers().getsockname()
935
931
  config = reflex.config.get_config()
936
- config.backend_port = backend_port
937
932
  config.api_url = "http://{0}:{1}".format(
938
- backend_host,
939
- backend_port,
933
+ *self._poll_for_servers().getsockname(),
940
934
  )
941
935
  reflex.reflex.export(
942
936
  zipping=False,
943
937
  frontend=True,
944
938
  backend=False,
945
939
  loglevel=reflex.constants.LogLevel.INFO,
940
+ env=reflex.constants.Env.PROD,
946
941
  )
947
942
 
948
943
  self.frontend_thread = threading.Thread(target=self._run_frontend)
reflex/utils/build.py CHANGED
@@ -13,17 +13,21 @@ from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
13
13
  from reflex import constants
14
14
  from reflex.config import get_config
15
15
  from reflex.utils import console, path_ops, prerequisites, processes
16
+ from reflex.utils.exec import is_in_app_harness
16
17
 
17
18
 
18
19
  def set_env_json():
19
20
  """Write the upload url to a REFLEX_JSON."""
20
21
  path_ops.update_json_file(
21
22
  str(prerequisites.get_web_dir() / constants.Dirs.ENV_JSON),
22
- {endpoint.name: endpoint.get_url() for endpoint in constants.Endpoint},
23
+ {
24
+ **{endpoint.name: endpoint.get_url() for endpoint in constants.Endpoint},
25
+ "TEST_MODE": is_in_app_harness(),
26
+ },
23
27
  )
24
28
 
25
29
 
26
- def generate_sitemap_config(deploy_url: str, export=False):
30
+ def generate_sitemap_config(deploy_url: str, export: bool = False):
27
31
  """Generate the sitemap config file.
28
32
 
29
33
  Args:
@@ -42,10 +42,7 @@ def codespaces_port_forwarding_domain() -> str | None:
42
42
  Returns:
43
43
  The domain for port forwarding in Github Codespaces, or None if not running in Codespaces.
44
44
  """
45
- GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
46
- "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
47
- )
48
- return GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
45
+ return os.getenv("GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN")
49
46
 
50
47
 
51
48
  def is_running_in_codespaces() -> bool:
reflex/utils/compat.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import contextlib
4
4
  import sys
5
+ from typing import Any
5
6
 
6
7
 
7
8
  async def windows_hot_reload_lifespan_hack():
@@ -50,11 +51,11 @@ def pydantic_v1_patch():
50
51
  ]
51
52
  originals = {module: sys.modules.get(module) for module in patched_modules}
52
53
  try:
53
- import pydantic.v1 # type: ignore
54
+ import pydantic.v1
54
55
 
55
- sys.modules["pydantic.fields"] = pydantic.v1.fields # type: ignore
56
- sys.modules["pydantic.main"] = pydantic.v1.main # type: ignore
57
- sys.modules["pydantic.errors"] = pydantic.v1.errors # type: ignore
56
+ sys.modules["pydantic.fields"] = pydantic.v1.fields # pyright: ignore [reportAttributeAccessIssue]
57
+ sys.modules["pydantic.main"] = pydantic.v1.main # pyright: ignore [reportAttributeAccessIssue]
58
+ sys.modules["pydantic.errors"] = pydantic.v1.errors # pyright: ignore [reportAttributeAccessIssue]
58
59
  sys.modules["pydantic"] = pydantic.v1
59
60
  yield
60
61
  except (ImportError, AttributeError):
@@ -74,7 +75,7 @@ with pydantic_v1_patch():
74
75
  import sqlmodel as sqlmodel
75
76
 
76
77
 
77
- def sqlmodel_field_has_primary_key(field) -> bool:
78
+ def sqlmodel_field_has_primary_key(field: Any) -> bool:
78
79
  """Determines if a field is a priamary.
79
80
 
80
81
  Args:
reflex/utils/console.py CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
5
+ import contextlib
6
+ import inspect
7
+ import shutil
8
+ import time
9
+ from pathlib import Path
10
+ from types import FrameType
6
11
 
7
12
  from rich.console import Console
8
13
  from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
@@ -14,7 +19,7 @@ from reflex.constants import LogLevel
14
19
  _console = Console()
15
20
 
16
21
  # The current log level.
17
- _LOG_LEVEL = LogLevel.DEFAULT
22
+ _LOG_LEVEL = LogLevel.INFO
18
23
 
19
24
  # Deprecated features who's warning has been printed.
20
25
  _EMITTED_DEPRECATION_WARNINGS = set()
@@ -48,24 +53,13 @@ def set_log_level(log_level: LogLevel):
48
53
  log_level: The log level to set.
49
54
 
50
55
  Raises:
51
- ValueError: If the log level is invalid.
56
+ TypeError: If the log level is a string.
52
57
  """
53
58
  if not isinstance(log_level, LogLevel):
54
- deprecate(
55
- feature_name="Passing a string to set_log_level",
56
- reason="use reflex.constants.LogLevel enum instead",
57
- deprecation_version="0.6.6",
58
- removal_version="0.7.0",
59
+ raise TypeError(
60
+ f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
59
61
  )
60
- try:
61
- log_level = getattr(LogLevel, log_level.upper())
62
- except AttributeError as ae:
63
- raise ValueError(f"Invalid log level: {log_level}") from ae
64
-
65
62
  global _LOG_LEVEL
66
- if log_level != _LOG_LEVEL:
67
- # Set the loglevel persistently for subprocesses
68
- os.environ["LOGLEVEL"] = log_level.value
69
63
  _LOG_LEVEL = log_level
70
64
 
71
65
 
@@ -193,6 +187,33 @@ def warn(msg: str, dedupe: bool = False, **kwargs):
193
187
  print(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
194
188
 
195
189
 
190
+ def _get_first_non_framework_frame() -> FrameType | None:
191
+ import click
192
+ import typer
193
+ import typing_extensions
194
+
195
+ import reflex as rx
196
+
197
+ # Exclude utility modules that should never be the source of deprecated reflex usage.
198
+ exclude_modules = [click, rx, typer, typing_extensions]
199
+ exclude_roots = [
200
+ p.parent.resolve()
201
+ if (p := Path(m.__file__)).name == "__init__.py" # pyright: ignore [reportArgumentType]
202
+ else p.resolve()
203
+ for m in exclude_modules
204
+ ]
205
+ # Specifically exclude the reflex cli module.
206
+ if reflex_bin := shutil.which(b"reflex"):
207
+ exclude_roots.append(Path(reflex_bin.decode()))
208
+
209
+ frame = inspect.currentframe()
210
+ while frame := frame and frame.f_back:
211
+ frame_path = Path(inspect.getfile(frame)).resolve()
212
+ if not any(frame_path.is_relative_to(root) for root in exclude_roots):
213
+ break
214
+ return frame
215
+
216
+
196
217
  def deprecate(
197
218
  feature_name: str,
198
219
  reason: str,
@@ -211,15 +232,27 @@ def deprecate(
211
232
  dedupe: If True, suppress multiple console logs of deprecation message.
212
233
  kwargs: Keyword arguments to pass to the print function.
213
234
  """
214
- if feature_name not in _EMITTED_DEPRECATION_WARNINGS:
235
+ dedupe_key = feature_name
236
+ loc = ""
237
+
238
+ # See if we can find where the deprecation exists in "user code"
239
+ origin_frame = _get_first_non_framework_frame()
240
+ if origin_frame is not None:
241
+ filename = Path(origin_frame.f_code.co_filename)
242
+ if filename.is_relative_to(Path.cwd()):
243
+ filename = filename.relative_to(Path.cwd())
244
+ loc = f"{filename}:{origin_frame.f_lineno}"
245
+ dedupe_key = f"{dedupe_key} {loc}"
246
+
247
+ if dedupe_key not in _EMITTED_DEPRECATION_WARNINGS:
215
248
  msg = (
216
249
  f"{feature_name} has been deprecated in version {deprecation_version} {reason.rstrip('.')}. It will be completely "
217
- f"removed in {removal_version}"
250
+ f"removed in {removal_version}. ({loc})"
218
251
  )
219
252
  if _LOG_LEVEL <= LogLevel.WARNING:
220
253
  print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs)
221
254
  if dedupe:
222
- _EMITTED_DEPRECATION_WARNINGS.add(feature_name)
255
+ _EMITTED_DEPRECATION_WARNINGS.add(dedupe_key)
223
256
 
224
257
 
225
258
  def error(msg: str, dedupe: bool = False, **kwargs):
@@ -244,7 +277,7 @@ def ask(
244
277
  choices: list[str] | None = None,
245
278
  default: str | None = None,
246
279
  show_choices: bool = True,
247
- ) -> str:
280
+ ) -> str | None:
248
281
  """Takes a prompt question and optionally a list of choices
249
282
  and returns the user input.
250
283
 
@@ -259,7 +292,7 @@ def ask(
259
292
  """
260
293
  return Prompt.ask(
261
294
  question, choices=choices, default=default, show_choices=show_choices
262
- ) # type: ignore
295
+ )
263
296
 
264
297
 
265
298
  def progress():
@@ -286,3 +319,20 @@ def status(*args, **kwargs):
286
319
  A new status.
287
320
  """
288
321
  return _console.status(*args, **kwargs)
322
+
323
+
324
+ @contextlib.contextmanager
325
+ def timing(msg: str):
326
+ """Create a context manager to time a block of code.
327
+
328
+ Args:
329
+ msg: The message to display.
330
+
331
+ Yields:
332
+ None.
333
+ """
334
+ start = time.time()
335
+ try:
336
+ yield
337
+ finally:
338
+ debug(f"[white]\\[timing] {msg}: {time.time() - start:.2f}s[/white]")
@@ -1,6 +1,6 @@
1
1
  """Custom Exceptions."""
2
2
 
3
- from typing import NoReturn
3
+ from typing import Any
4
4
 
5
5
 
6
6
  class ReflexError(Exception):
@@ -11,7 +11,7 @@ class ConfigError(ReflexError):
11
11
  """Custom exception for config related errors."""
12
12
 
13
13
 
14
- class InvalidStateManagerMode(ReflexError, ValueError):
14
+ class InvalidStateManagerModeError(ReflexError, ValueError):
15
15
  """Raised when an invalid state manager mode is provided."""
16
16
 
17
17
 
@@ -31,6 +31,22 @@ class ComponentTypeError(ReflexError, TypeError):
31
31
  """Custom TypeError for component related errors."""
32
32
 
33
33
 
34
+ class ChildrenTypeError(ComponentTypeError):
35
+ """Raised when the children prop of a component is not a valid type."""
36
+
37
+ def __init__(self, component: str, child: Any):
38
+ """Initialize the exception.
39
+
40
+ Args:
41
+ component: The name of the component.
42
+ child: The child that caused the error.
43
+ """
44
+ super().__init__(
45
+ f"Component {component} received child {child} of type {type(child)}. "
46
+ "Accepted types are other components, state vars, or primitive Python types (dict excluded)."
47
+ )
48
+
49
+
34
50
  class EventHandlerTypeError(ReflexError, TypeError):
35
51
  """Custom TypeError for event handler related errors."""
36
52
 
@@ -59,6 +75,47 @@ class VarAttributeError(ReflexError, AttributeError):
59
75
  """Custom AttributeError for var related errors."""
60
76
 
61
77
 
78
+ class UntypedVarError(ReflexError, TypeError):
79
+ """Custom TypeError for untyped var errors."""
80
+
81
+
82
+ class UntypedComputedVarError(ReflexError, TypeError):
83
+ """Custom TypeError for untyped computed var errors."""
84
+
85
+ def __init__(self, var_name: str):
86
+ """Initialize the UntypedComputedVarError.
87
+
88
+ Args:
89
+ var_name: The name of the computed var.
90
+ """
91
+ super().__init__(f"Computed var '{var_name}' must have a type annotation.")
92
+
93
+
94
+ class ComputedVarSignatureError(ReflexError, TypeError):
95
+ """Custom TypeError for computed var signature errors."""
96
+
97
+ def __init__(self, var_name: str, signature: str):
98
+ """Initialize the ComputedVarSignatureError.
99
+
100
+ Args:
101
+ var_name: The name of the var.
102
+ signature: The invalid signature.
103
+ """
104
+ super().__init__(f"Computed var `{var_name}{signature}` cannot take arguments.")
105
+
106
+
107
+ class MissingAnnotationError(ReflexError, TypeError):
108
+ """Custom TypeError for missing annotations."""
109
+
110
+ def __init__(self, var_name: str):
111
+ """Initialize the MissingAnnotationError.
112
+
113
+ Args:
114
+ var_name: The name of the var.
115
+ """
116
+ super().__init__(f"Var '{var_name}' must have a type annotation.")
117
+
118
+
62
119
  class UploadValueError(ReflexError, ValueError):
63
120
  """Custom ValueError for upload related errors."""
64
121
 
@@ -95,43 +152,43 @@ class MatchTypeError(ReflexError, TypeError):
95
152
  """Raised when the return types of match cases are different."""
96
153
 
97
154
 
98
- class EventHandlerArgTypeMismatch(ReflexError, TypeError):
155
+ class EventHandlerArgTypeMismatchError(ReflexError, TypeError):
99
156
  """Raised when the annotations of args accepted by an EventHandler differs from the spec of the event trigger."""
100
157
 
101
158
 
102
- class EventFnArgMismatch(ReflexError, TypeError):
159
+ class EventFnArgMismatchError(ReflexError, TypeError):
103
160
  """Raised when the number of args required by an event handler is more than provided by the event trigger."""
104
161
 
105
162
 
106
- class DynamicRouteArgShadowsStateVar(ReflexError, NameError):
163
+ class DynamicRouteArgShadowsStateVarError(ReflexError, NameError):
107
164
  """Raised when a dynamic route arg shadows a state var."""
108
165
 
109
166
 
110
- class ComputedVarShadowsStateVar(ReflexError, NameError):
167
+ class ComputedVarShadowsStateVarError(ReflexError, NameError):
111
168
  """Raised when a computed var shadows a state var."""
112
169
 
113
170
 
114
- class ComputedVarShadowsBaseVars(ReflexError, NameError):
171
+ class ComputedVarShadowsBaseVarsError(ReflexError, NameError):
115
172
  """Raised when a computed var shadows a base var."""
116
173
 
117
174
 
118
- class EventHandlerShadowsBuiltInStateMethod(ReflexError, NameError):
175
+ class EventHandlerShadowsBuiltInStateMethodError(ReflexError, NameError):
119
176
  """Raised when an event handler shadows a built-in state method."""
120
177
 
121
178
 
122
- class GeneratedCodeHasNoFunctionDefs(ReflexError):
179
+ class GeneratedCodeHasNoFunctionDefsError(ReflexError):
123
180
  """Raised when refactored code generated with flexgen has no functions defined."""
124
181
 
125
182
 
126
- class PrimitiveUnserializableToJSON(ReflexError, ValueError):
183
+ class PrimitiveUnserializableToJSONError(ReflexError, ValueError):
127
184
  """Raised when a primitive type is unserializable to JSON. Usually with NaN and Infinity."""
128
185
 
129
186
 
130
- class InvalidLifespanTaskType(ReflexError, TypeError):
187
+ class InvalidLifespanTaskTypeError(ReflexError, TypeError):
131
188
  """Raised when an invalid task type is registered as a lifespan task."""
132
189
 
133
190
 
134
- class DynamicComponentMissingLibrary(ReflexError, ValueError):
191
+ class DynamicComponentMissingLibraryError(ReflexError, ValueError):
135
192
  """Raised when a dynamic component is missing a library."""
136
193
 
137
194
 
@@ -147,7 +204,7 @@ class EnvironmentVarValueError(ReflexError, ValueError):
147
204
  """Raised when an environment variable is set to an invalid value."""
148
205
 
149
206
 
150
- class DynamicComponentInvalidSignature(ReflexError, TypeError):
207
+ class DynamicComponentInvalidSignatureError(ReflexError, TypeError):
151
208
  """Raised when a dynamic component has an invalid signature."""
152
209
 
153
210
 
@@ -163,26 +220,32 @@ class StateSerializationError(ReflexError):
163
220
  """Raised when the state cannot be serialized."""
164
221
 
165
222
 
223
+ class StateMismatchError(ReflexError, ValueError):
224
+ """Raised when the state retrieved does not match the expected state."""
225
+
226
+
166
227
  class SystemPackageMissingError(ReflexError):
167
228
  """Raised when a system package is missing."""
168
229
 
230
+ def __init__(self, package: str):
231
+ """Initialize the SystemPackageMissingError.
169
232
 
170
- def raise_system_package_missing_error(package: str) -> NoReturn:
171
- """Raise a SystemPackageMissingError.
233
+ Args:
234
+ package: The missing package.
235
+ """
236
+ from reflex.constants import IS_MACOS
172
237
 
173
- Args:
174
- package: The name of the missing system package.
238
+ extra = (
239
+ f" You can do so by running 'brew install {package}'." if IS_MACOS else ""
240
+ )
241
+ super().__init__(
242
+ f"System package '{package}' is missing."
243
+ f" Please install it through your system package manager.{extra}"
244
+ )
175
245
 
176
- Raises:
177
- SystemPackageMissingError: The raised exception.
178
- """
179
- from reflex.constants import IS_MACOS
180
246
 
181
- raise SystemPackageMissingError(
182
- f"System package '{package}' is missing."
183
- " Please install it through your system package manager."
184
- + (f" You can do so by running 'brew install {package}'." if IS_MACOS else "")
185
- )
247
+ class EventDeserializationError(ReflexError, ValueError):
248
+ """Raised when an event cannot be deserialized."""
186
249
 
187
250
 
188
251
  class InvalidLockWarningThresholdError(ReflexError):