reflex 0.3.9a3__py3-none-any.whl → 0.3.10__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 (247) hide show
  1. reflex/__init__.py +3 -1
  2. reflex/__init__.pyi +3 -1
  3. reflex/app.py +116 -86
  4. reflex/compiler/compiler.py +110 -0
  5. reflex/compiler/utils.py +13 -12
  6. reflex/components/base/app_wrap.pyi +1 -0
  7. reflex/components/base/body.pyi +2 -0
  8. reflex/components/base/document.pyi +10 -0
  9. reflex/components/base/fragment.pyi +2 -0
  10. reflex/components/base/head.pyi +4 -0
  11. reflex/components/base/link.pyi +4 -0
  12. reflex/components/base/meta.pyi +8 -0
  13. reflex/components/base/script.pyi +2 -0
  14. reflex/components/chakra/base.pyi +7 -0
  15. reflex/components/chakra/datadisplay/badge.pyi +2 -0
  16. reflex/components/chakra/datadisplay/code.pyi +4 -0
  17. reflex/components/chakra/datadisplay/divider.pyi +2 -0
  18. reflex/components/chakra/datadisplay/keyboard_key.pyi +2 -0
  19. reflex/components/chakra/datadisplay/list.pyi +8 -0
  20. reflex/components/chakra/datadisplay/stat.pyi +12 -0
  21. reflex/components/chakra/datadisplay/table.pyi +18 -0
  22. reflex/components/chakra/datadisplay/tag.pyi +9 -0
  23. reflex/components/chakra/disclosure/accordion.pyi +10 -0
  24. reflex/components/chakra/disclosure/tabs.py +8 -0
  25. reflex/components/chakra/disclosure/tabs.pyi +10 -0
  26. reflex/components/chakra/disclosure/transition.pyi +12 -0
  27. reflex/components/chakra/disclosure/visuallyhidden.pyi +2 -0
  28. reflex/components/chakra/feedback/alert.pyi +8 -0
  29. reflex/components/chakra/feedback/circularprogress.pyi +4 -0
  30. reflex/components/chakra/feedback/progress.pyi +2 -0
  31. reflex/components/chakra/feedback/skeleton.pyi +6 -0
  32. reflex/components/chakra/feedback/spinner.pyi +2 -0
  33. reflex/components/chakra/forms/button.pyi +4 -0
  34. reflex/components/chakra/forms/checkbox.pyi +4 -0
  35. reflex/components/chakra/forms/colormodeswitch.pyi +7 -0
  36. reflex/components/chakra/forms/date_picker.pyi +2 -0
  37. reflex/components/chakra/forms/date_time_picker.pyi +2 -0
  38. reflex/components/chakra/forms/editable.pyi +8 -0
  39. reflex/components/chakra/forms/email.pyi +2 -0
  40. reflex/components/chakra/forms/form.pyi +10 -0
  41. reflex/components/chakra/forms/iconbutton.py +1 -0
  42. reflex/components/chakra/forms/iconbutton.pyi +2 -0
  43. reflex/components/chakra/forms/input.pyi +12 -0
  44. reflex/components/chakra/forms/numberinput.pyi +10 -0
  45. reflex/components/chakra/forms/password.pyi +2 -0
  46. reflex/components/chakra/forms/pininput.pyi +4 -0
  47. reflex/components/chakra/forms/radio.pyi +4 -0
  48. reflex/components/chakra/forms/rangeslider.pyi +8 -0
  49. reflex/components/chakra/forms/select.pyi +4 -0
  50. reflex/components/chakra/forms/slider.pyi +10 -0
  51. reflex/components/chakra/forms/switch.pyi +2 -0
  52. reflex/components/chakra/forms/textarea.pyi +2 -0
  53. reflex/components/chakra/forms/time_picker.pyi +2 -0
  54. reflex/components/chakra/layout/aspect_ratio.pyi +2 -0
  55. reflex/components/chakra/layout/box.pyi +2 -0
  56. reflex/components/chakra/layout/card.pyi +7 -0
  57. reflex/components/chakra/layout/center.pyi +6 -0
  58. reflex/components/chakra/layout/container.pyi +2 -0
  59. reflex/components/chakra/layout/flex.pyi +2 -0
  60. reflex/components/chakra/layout/grid.pyi +6 -0
  61. reflex/components/chakra/layout/html.pyi +2 -0
  62. reflex/components/chakra/layout/spacer.pyi +2 -0
  63. reflex/components/chakra/layout/stack.pyi +6 -0
  64. reflex/components/chakra/layout/wrap.pyi +4 -0
  65. reflex/components/chakra/media/avatar.pyi +6 -0
  66. reflex/components/chakra/media/icon.pyi +4 -0
  67. reflex/components/chakra/media/image.pyi +2 -0
  68. reflex/components/chakra/navigation/breadcrumb.pyi +8 -0
  69. reflex/components/chakra/navigation/link.pyi +2 -0
  70. reflex/components/chakra/navigation/linkoverlay.pyi +4 -0
  71. reflex/components/chakra/navigation/stepper.pyi +18 -0
  72. reflex/components/chakra/overlay/alertdialog.pyi +14 -0
  73. reflex/components/chakra/overlay/drawer.pyi +14 -0
  74. reflex/components/chakra/overlay/menu.pyi +16 -0
  75. reflex/components/chakra/overlay/modal.pyi +14 -0
  76. reflex/components/chakra/overlay/popover.pyi +18 -0
  77. reflex/components/chakra/overlay/tooltip.pyi +2 -0
  78. reflex/components/chakra/typography/heading.pyi +2 -0
  79. reflex/components/chakra/typography/highlight.pyi +2 -0
  80. reflex/components/chakra/typography/span.pyi +2 -0
  81. reflex/components/chakra/typography/text.pyi +2 -0
  82. reflex/components/component.py +41 -3
  83. reflex/components/core/__init__.py +2 -0
  84. reflex/components/core/banner.pyi +3 -0
  85. reflex/components/core/client_side_routing.pyi +4 -0
  86. reflex/components/core/colors.py +21 -0
  87. reflex/components/core/cond.py +11 -2
  88. reflex/components/core/debounce.pyi +1 -0
  89. reflex/components/core/layout/__init__.py +1 -0
  90. reflex/components/core/match.py +44 -18
  91. reflex/components/core/upload.py +8 -2
  92. reflex/components/core/upload.pyi +7 -1
  93. reflex/components/datadisplay/dataeditor.pyi +2 -0
  94. reflex/components/el/element.pyi +2 -0
  95. reflex/components/el/elements/base.pyi +2 -0
  96. reflex/components/el/elements/forms.py +3 -0
  97. reflex/components/el/elements/forms.pyi +32 -0
  98. reflex/components/el/elements/inline.pyi +56 -0
  99. reflex/components/el/elements/media.pyi +28 -0
  100. reflex/components/el/elements/metadata.pyi +10 -0
  101. reflex/components/el/elements/other.pyi +14 -0
  102. reflex/components/el/elements/scripts.pyi +6 -0
  103. reflex/components/el/elements/sectioning.pyi +30 -0
  104. reflex/components/el/elements/tables.pyi +20 -0
  105. reflex/components/el/elements/typography.pyi +30 -0
  106. reflex/components/gridjs/datatable.pyi +4 -0
  107. reflex/components/lucide/__init__.py +5 -0
  108. reflex/components/lucide/icon.py +1484 -0
  109. reflex/components/lucide/icon.pyi +1594 -0
  110. reflex/components/markdown/markdown.pyi +2 -0
  111. reflex/components/moment/moment.pyi +2 -0
  112. reflex/components/next/base.pyi +2 -0
  113. reflex/components/next/image.pyi +2 -0
  114. reflex/components/next/link.pyi +2 -0
  115. reflex/components/next/video.pyi +2 -0
  116. reflex/components/plotly/plotly.pyi +4 -0
  117. reflex/components/radix/primitives/__init__.py +10 -0
  118. reflex/components/radix/primitives/accordion.py +51 -32
  119. reflex/components/radix/primitives/accordion.pyi +16 -3
  120. reflex/components/radix/primitives/base.pyi +4 -0
  121. reflex/components/radix/primitives/drawer.py +240 -0
  122. reflex/components/radix/primitives/drawer.pyi +814 -0
  123. reflex/components/radix/primitives/form.py +40 -7
  124. reflex/components/radix/primitives/form.pyi +32 -10
  125. reflex/components/radix/primitives/progress.py +2 -2
  126. reflex/components/radix/primitives/progress.pyi +6 -0
  127. reflex/components/radix/primitives/slider.pyi +10 -0
  128. reflex/components/radix/themes/base.py +46 -12
  129. reflex/components/radix/themes/base.pyi +23 -9
  130. reflex/components/radix/themes/components/__init__.py +4 -2
  131. reflex/components/radix/themes/components/alertdialog.py +13 -12
  132. reflex/components/radix/themes/components/alertdialog.pyi +23 -351
  133. reflex/components/radix/themes/components/aspectratio.py +2 -5
  134. reflex/components/radix/themes/components/aspectratio.pyi +4 -51
  135. reflex/components/radix/themes/components/avatar.py +3 -4
  136. reflex/components/radix/themes/components/avatar.pyi +4 -57
  137. reflex/components/radix/themes/components/badge.py +3 -4
  138. reflex/components/radix/themes/components/badge.pyi +4 -56
  139. reflex/components/radix/themes/components/button.py +2 -3
  140. reflex/components/radix/themes/components/button.pyi +3 -51
  141. reflex/components/radix/themes/components/callout.py +9 -12
  142. reflex/components/radix/themes/components/callout.pyi +24 -217
  143. reflex/components/radix/themes/components/card.py +1 -2
  144. reflex/components/radix/themes/components/card.pyi +4 -51
  145. reflex/components/radix/themes/components/checkbox.py +11 -5
  146. reflex/components/radix/themes/components/checkbox.pyi +13 -112
  147. reflex/components/radix/themes/components/contextmenu.py +21 -21
  148. reflex/components/radix/themes/components/contextmenu.pyi +26 -401
  149. reflex/components/radix/themes/components/dialog.py +13 -16
  150. reflex/components/radix/themes/components/dialog.pyi +20 -303
  151. reflex/components/radix/themes/components/dropdownmenu.py +196 -32
  152. reflex/components/radix/themes/components/dropdownmenu.pyi +147 -389
  153. reflex/components/radix/themes/components/hovercard.py +5 -5
  154. reflex/components/radix/themes/components/hovercard.pyi +11 -151
  155. reflex/components/radix/themes/components/iconbutton.py +56 -4
  156. reflex/components/radix/themes/components/iconbutton.pyi +25 -71
  157. reflex/components/radix/themes/components/icons.pyi +4 -0
  158. reflex/components/radix/themes/components/inset.py +1 -2
  159. reflex/components/radix/themes/components/inset.pyi +4 -51
  160. reflex/components/radix/themes/components/popover.py +12 -12
  161. reflex/components/radix/themes/components/popover.pyi +14 -201
  162. reflex/components/radix/themes/components/radiogroup.py +47 -20
  163. reflex/components/radix/themes/components/radiogroup.pyi +26 -171
  164. reflex/components/radix/themes/components/scrollarea.py +2 -3
  165. reflex/components/radix/themes/components/scrollarea.pyi +4 -51
  166. reflex/components/radix/themes/components/select.py +28 -25
  167. reflex/components/radix/themes/components/select.pyi +43 -419
  168. reflex/components/radix/themes/components/separator.py +4 -5
  169. reflex/components/radix/themes/components/separator.pyi +5 -52
  170. reflex/components/radix/themes/components/slider.py +8 -5
  171. reflex/components/radix/themes/components/slider.pyi +9 -60
  172. reflex/components/radix/themes/components/switch.py +6 -4
  173. reflex/components/radix/themes/components/switch.pyi +5 -53
  174. reflex/components/radix/themes/components/table.py +14 -15
  175. reflex/components/radix/themes/components/table.pyi +22 -351
  176. reflex/components/radix/themes/components/tabs.py +9 -6
  177. reflex/components/radix/themes/components/tabs.pyi +18 -205
  178. reflex/components/radix/themes/components/textarea.py +2 -3
  179. reflex/components/radix/themes/components/textarea.pyi +10 -53
  180. reflex/components/radix/themes/components/textfield.py +105 -4
  181. reflex/components/radix/themes/components/textfield.pyi +200 -108
  182. reflex/components/radix/themes/components/tooltip.py +102 -2
  183. reflex/components/radix/themes/components/tooltip.pyi +66 -110
  184. reflex/components/radix/themes/layout/__init__.py +7 -0
  185. reflex/components/radix/themes/layout/base.pyi +2 -0
  186. reflex/components/radix/themes/layout/box.py +2 -2
  187. reflex/components/radix/themes/layout/box.pyi +4 -104
  188. reflex/components/radix/themes/layout/center.py +19 -0
  189. reflex/components/radix/themes/layout/center.pyi +261 -0
  190. reflex/components/radix/themes/layout/container.py +2 -2
  191. reflex/components/radix/themes/layout/container.pyi +4 -104
  192. reflex/components/radix/themes/layout/flex.py +2 -2
  193. reflex/components/radix/themes/layout/flex.pyi +4 -105
  194. reflex/components/radix/themes/layout/grid.pyi +2 -0
  195. reflex/components/radix/themes/layout/section.py +2 -2
  196. reflex/components/radix/themes/layout/section.pyi +4 -104
  197. reflex/components/radix/themes/layout/spacer.py +19 -0
  198. reflex/components/radix/themes/layout/spacer.pyi +261 -0
  199. reflex/components/radix/themes/layout/stack.py +60 -0
  200. reflex/components/radix/themes/layout/stack.pyi +537 -0
  201. reflex/components/radix/themes/typography/blockquote.py +2 -3
  202. reflex/components/radix/themes/typography/blockquote.pyi +4 -51
  203. reflex/components/radix/themes/typography/code.py +2 -3
  204. reflex/components/radix/themes/typography/code.pyi +4 -56
  205. reflex/components/radix/themes/typography/em.py +1 -2
  206. reflex/components/radix/themes/typography/em.pyi +4 -51
  207. reflex/components/radix/themes/typography/heading.py +2 -3
  208. reflex/components/radix/themes/typography/heading.pyi +4 -51
  209. reflex/components/radix/themes/typography/kbd.py +1 -2
  210. reflex/components/radix/themes/typography/kbd.pyi +4 -51
  211. reflex/components/radix/themes/typography/link.py +34 -3
  212. reflex/components/radix/themes/typography/link.pyi +41 -86
  213. reflex/components/radix/themes/typography/quote.py +1 -2
  214. reflex/components/radix/themes/typography/quote.pyi +4 -51
  215. reflex/components/radix/themes/typography/strong.py +1 -2
  216. reflex/components/radix/themes/typography/strong.pyi +4 -51
  217. reflex/components/radix/themes/typography/text.py +2 -3
  218. reflex/components/radix/themes/typography/text.pyi +4 -51
  219. reflex/components/radix/themes/typography.py +10 -11
  220. reflex/components/react_player/audio.pyi +2 -0
  221. reflex/components/react_player/react_player.pyi +2 -0
  222. reflex/components/react_player/video.pyi +2 -0
  223. reflex/components/recharts/cartesian.pyi +38 -0
  224. reflex/components/recharts/charts.pyi +22 -0
  225. reflex/components/recharts/general.pyi +10 -0
  226. reflex/components/recharts/polar.pyi +12 -0
  227. reflex/components/recharts/recharts.pyi +4 -0
  228. reflex/components/suneditor/editor.pyi +2 -0
  229. reflex/components/tags/tag.py +1 -1
  230. reflex/constants/base.py +5 -1
  231. reflex/constants/colors.py +80 -0
  232. reflex/constants/event.py +10 -1
  233. reflex/page.py +1 -1
  234. reflex/reflex.py +4 -0
  235. reflex/style.py +8 -3
  236. reflex/testing.py +21 -10
  237. reflex/utils/format.py +13 -9
  238. reflex/utils/prerequisites.py +41 -3
  239. reflex/utils/serializers.py +14 -0
  240. reflex/utils/telemetry.py +8 -2
  241. reflex/utils/types.py +36 -2
  242. reflex/vars.py +53 -18
  243. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/METADATA +1 -2
  244. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/RECORD +247 -233
  245. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/WHEEL +1 -1
  246. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/LICENSE +0 -0
  247. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/entry_points.txt +0 -0
reflex/__init__.py CHANGED
@@ -253,12 +253,14 @@ _MAPPING = {
253
253
  "reflex.compiler.utils": ["get_asset_path"],
254
254
  "reflex.components": _ALL_COMPONENTS + ["chakra", "next"],
255
255
  "reflex.components.component": ["memo"],
256
+ "reflex.components.core": ["color"],
257
+ "reflex.components.el": ["el"],
258
+ "reflex.components.lucide": ["lucide"],
256
259
  "reflex.components.radix": ["radix"],
257
260
  "reflex.components.recharts": ["recharts"],
258
261
  "reflex.components.moment.moment": ["MomentDelta"],
259
262
  "reflex.config": ["config", "Config", "DBConfig"],
260
263
  "reflex.constants": ["constants", "Env"],
261
- "reflex.components.el": ["el"],
262
264
  "reflex.event": [
263
265
  "event",
264
266
  "EventChain",
reflex/__init__.pyi CHANGED
@@ -447,6 +447,9 @@ from reflex.components import NoSSRComponent as NoSSRComponent
447
447
  from reflex.components import chakra as chakra
448
448
  from reflex.components import next as next
449
449
  from reflex.components.component import memo as memo
450
+ from reflex.components.core import color as color
451
+ from reflex.components import el as el
452
+ from reflex.components import lucide as lucide
450
453
  from reflex.components import radix as radix
451
454
  from reflex.components import recharts as recharts
452
455
  from reflex.components.moment.moment import MomentDelta as MomentDelta
@@ -455,7 +458,6 @@ from reflex.config import Config as Config
455
458
  from reflex.config import DBConfig as DBConfig
456
459
  from reflex import constants as constants
457
460
  from reflex.constants import Env as Env
458
- from reflex.components import el as el
459
461
  from reflex import event as event
460
462
  from reflex.event import EventChain as EventChain
461
463
  from reflex.event import background as background
reflex/app.py CHANGED
@@ -6,7 +6,9 @@ import concurrent.futures
6
6
  import contextlib
7
7
  import copy
8
8
  import functools
9
+ import multiprocessing
9
10
  import os
11
+ import platform
10
12
  from typing import (
11
13
  Any,
12
14
  AsyncIterator,
@@ -35,6 +37,7 @@ from reflex.admin import AdminDash
35
37
  from reflex.base import Base
36
38
  from reflex.compiler import compiler
37
39
  from reflex.compiler import utils as compiler_utils
40
+ from reflex.compiler.compiler import ExecutorSafeFunctions
38
41
  from reflex.components import connection_modal
39
42
  from reflex.components.base.app_wrap import AppWrap
40
43
  from reflex.components.base.fragment import Fragment
@@ -43,6 +46,8 @@ from reflex.components.core.client_side_routing import (
43
46
  Default404Page,
44
47
  wait_for_client_redirect,
45
48
  )
49
+ from reflex.components.core.upload import Upload
50
+ from reflex.components.radix import themes
46
51
  from reflex.config import get_config
47
52
  from reflex.event import Event, EventHandler, EventSpec
48
53
  from reflex.middleware import HydrateMiddleware, Middleware
@@ -128,11 +133,11 @@ class App(Base):
128
133
  Union[Component, ComponentCallable]
129
134
  ] = default_overlay_component
130
135
 
131
- # Background tasks that are currently running.
136
+ # Background tasks that are currently running
132
137
  background_tasks: Set[asyncio.Task] = set()
133
138
 
134
- # The radix theme for the entire app.
135
- theme: Optional[Component]
139
+ # The radix theme for the entire app
140
+ theme: Optional[Component] = themes.theme(accent_color="blue")
136
141
 
137
142
  def __init__(self, *args, **kwargs):
138
143
  """Initialize the app.
@@ -237,12 +242,15 @@ class App(Base):
237
242
  return self.api
238
243
 
239
244
  def add_default_endpoints(self):
240
- """Add the default endpoints."""
245
+ """Add default api endpoints (ping)."""
241
246
  # To test the server.
242
247
  self.api.get(str(constants.Endpoint.PING))(ping)
243
248
 
249
+ def add_optional_endpoints(self):
250
+ """Add optional api endpoints (_upload)."""
244
251
  # To upload files.
245
- self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
252
+ if Upload.is_used:
253
+ self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
246
254
 
247
255
  def add_cors(self):
248
256
  """Add CORS middleware to the app."""
@@ -650,6 +658,9 @@ class App(Base):
650
658
  if constants.Page404.SLUG not in self.pages:
651
659
  self.add_custom_404_page()
652
660
 
661
+ # Add the optional endpoints (_upload)
662
+ self.add_optional_endpoints()
663
+
653
664
  if not self._should_compile():
654
665
  return
655
666
 
@@ -660,15 +671,24 @@ class App(Base):
660
671
  TimeElapsedColumn(),
661
672
  )
662
673
 
674
+ # try to be somewhat accurate - but still not 100%
675
+ adhoc_steps_without_executor = 6
676
+ fixed_pages_within_executor = 7
677
+ progress.start()
678
+ task = progress.add_task(
679
+ "Compiling:",
680
+ total=len(self.pages)
681
+ + fixed_pages_within_executor
682
+ + adhoc_steps_without_executor,
683
+ )
684
+
663
685
  # Get the env mode.
664
686
  config = get_config()
665
687
 
666
688
  # Store the compile results.
667
689
  compile_results = []
668
690
 
669
- # Compile the pages in parallel.
670
691
  custom_components = set()
671
- # TODO Anecdotally, processes=2 works 10% faster (cpu_count=12)
672
692
  all_imports = {}
673
693
  app_wrappers: Dict[tuple[int, str], Component] = {
674
694
  # Default app wrap component renders {children}
@@ -678,127 +698,137 @@ class App(Base):
678
698
  # If a theme component was provided, wrap the app with it
679
699
  app_wrappers[(20, "Theme")] = self.theme
680
700
 
681
- with progress, concurrent.futures.ThreadPoolExecutor() as thread_pool:
682
- fixed_pages = 7
683
- task = progress.add_task("Compiling:", total=len(self.pages) + fixed_pages)
701
+ progress.advance(task)
684
702
 
685
- def mark_complete(_=None):
686
- progress.advance(task)
703
+ for _route, component in self.pages.items():
704
+ # Merge the component style with the app style.
705
+ component.add_style(self.style)
687
706
 
688
- for _route, component in self.pages.items():
689
- # Merge the component style with the app style.
690
- component.add_style(self.style)
707
+ component.apply_theme(self.theme)
691
708
 
692
- component.apply_theme(self.theme)
709
+ # Add component.get_imports() to all_imports.
710
+ all_imports.update(component.get_imports())
693
711
 
694
- # Add component.get_imports() to all_imports.
695
- all_imports.update(component.get_imports())
712
+ # Add the app wrappers from this component.
713
+ app_wrappers.update(component.get_app_wrap_components())
696
714
 
697
- # Add the app wrappers from this component.
698
- app_wrappers.update(component.get_app_wrap_components())
715
+ # Add the custom components from the page to the set.
716
+ custom_components |= component.get_custom_components()
699
717
 
700
- # Add the custom components from the page to the set.
701
- custom_components |= component.get_custom_components()
718
+ progress.advance(task)
702
719
 
703
- # Perform auto-memoization of stateful components.
704
- (
705
- stateful_components_path,
706
- stateful_components_code,
707
- page_components,
708
- ) = compiler.compile_stateful_components(self.pages.values())
720
+ # Perform auto-memoization of stateful components.
721
+ (
722
+ stateful_components_path,
723
+ stateful_components_code,
724
+ page_components,
725
+ ) = compiler.compile_stateful_components(self.pages.values())
709
726
 
710
- # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
711
- if (
712
- code_uses_state_contexts(stateful_components_code)
713
- and self.state is None
714
- ):
715
- raise RuntimeError(
716
- "To access rx.State in frontend components, at least one "
717
- "subclass of rx.State must be defined in the app."
718
- )
719
- compile_results.append((stateful_components_path, stateful_components_code))
727
+ progress.advance(task)
720
728
 
721
- result_futures = []
729
+ # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
730
+ if code_uses_state_contexts(stateful_components_code) and self.state is None:
731
+ raise RuntimeError(
732
+ "To access rx.State in frontend components, at least one "
733
+ "subclass of rx.State must be defined in the app."
734
+ )
735
+ compile_results.append((stateful_components_path, stateful_components_code))
722
736
 
723
- def submit_work(fn, *args, **kwargs):
724
- """Submit work to the thread pool and add a callback to mark the task as complete.
737
+ app_root = self._app_root(app_wrappers=app_wrappers)
725
738
 
726
- The Future will be added to the `result_futures` list.
739
+ progress.advance(task)
727
740
 
728
- Args:
729
- fn: The function to submit.
730
- *args: The args to submit.
731
- **kwargs: The kwargs to submit.
732
- """
733
- f = thread_pool.submit(fn, *args, **kwargs)
734
- f.add_done_callback(mark_complete)
741
+ # Prepopulate the global ExecutorSafeFunctions class with input data required by the compile functions.
742
+ # This is required for multiprocessing to work, in presence of non-picklable inputs.
743
+ for route, component in zip(self.pages, page_components):
744
+ ExecutorSafeFunctions.COMPILE_PAGE_ARGS_BY_ROUTE[route] = (
745
+ route,
746
+ component,
747
+ self.state,
748
+ )
749
+
750
+ ExecutorSafeFunctions.COMPILE_APP_APP_ROOT = app_root
751
+ ExecutorSafeFunctions.CUSTOM_COMPONENTS = custom_components
752
+ ExecutorSafeFunctions.HEAD_COMPONENTS = self.head_components
753
+ ExecutorSafeFunctions.STYLE = self.style
754
+ ExecutorSafeFunctions.STATE = self.state
755
+
756
+ # Use a forking process pool, if possible. Much faster, especially for large sites.
757
+ # Fallback to ThreadPoolExecutor as something that will always work.
758
+ executor = None
759
+ if platform.system() in ("Linux", "Darwin"):
760
+ executor = concurrent.futures.ProcessPoolExecutor(
761
+ mp_context=multiprocessing.get_context("fork")
762
+ )
763
+ else:
764
+ executor = concurrent.futures.ThreadPoolExecutor()
765
+
766
+ with executor:
767
+ result_futures = []
768
+
769
+ def _mark_complete(_=None):
770
+ progress.advance(task)
771
+
772
+ def _submit_work(fn, *args, **kwargs):
773
+ f = executor.submit(fn, *args, **kwargs)
774
+ f.add_done_callback(_mark_complete)
735
775
  result_futures.append(f)
736
776
 
737
777
  # Compile all page components.
738
- for route, component in zip(self.pages, page_components):
739
- submit_work(
740
- compiler.compile_page,
741
- route,
742
- component,
743
- self.state,
744
- )
778
+ for route in self.pages:
779
+ _submit_work(ExecutorSafeFunctions.compile_page, route)
745
780
 
746
781
  # Compile the app wrapper.
747
- app_root = self._app_root(app_wrappers=app_wrappers)
748
- submit_work(compiler.compile_app, app_root)
782
+ _submit_work(ExecutorSafeFunctions.compile_app)
749
783
 
750
784
  # Compile the custom components.
751
- submit_work(compiler.compile_components, custom_components)
785
+ _submit_work(ExecutorSafeFunctions.compile_custom_components)
752
786
 
753
787
  # Compile the root stylesheet with base styles.
754
- submit_work(compiler.compile_root_stylesheet, self.stylesheets)
788
+ _submit_work(compiler.compile_root_stylesheet, self.stylesheets)
755
789
 
756
790
  # Compile the root document.
757
- submit_work(compiler.compile_document_root, self.head_components)
791
+ _submit_work(ExecutorSafeFunctions.compile_document_root)
758
792
 
759
793
  # Compile the theme.
760
- submit_work(compiler.compile_theme, style=self.style)
794
+ _submit_work(ExecutorSafeFunctions.compile_theme)
761
795
 
762
796
  # Compile the contexts.
763
- submit_work(compiler.compile_contexts, self.state)
797
+ _submit_work(ExecutorSafeFunctions.compile_contexts)
764
798
 
765
799
  # Compile the Tailwind config.
766
800
  if config.tailwind is not None:
767
801
  config.tailwind["content"] = config.tailwind.get(
768
802
  "content", constants.Tailwind.CONTENT
769
803
  )
770
- submit_work(compiler.compile_tailwind, config.tailwind)
804
+ _submit_work(compiler.compile_tailwind, config.tailwind)
771
805
  else:
772
- submit_work(compiler.remove_tailwind_from_postcss)
773
-
774
- # Get imports from AppWrap components.
775
- all_imports.update(app_root.get_imports())
776
-
777
- # Iterate through all the custom components and add their imports to the all_imports.
778
- for component in custom_components:
779
- all_imports.update(component.get_imports())
806
+ _submit_work(compiler.remove_tailwind_from_postcss)
780
807
 
781
808
  # Wait for all compilation tasks to complete.
782
809
  for future in concurrent.futures.as_completed(result_futures):
783
810
  compile_results.append(future.result())
784
811
 
785
- # Empty the .web pages directory.
786
- compiler.purge_web_pages_dir()
812
+ # Get imports from AppWrap components.
813
+ all_imports.update(app_root.get_imports())
787
814
 
788
- # Avoid flickering when installing frontend packages
789
- progress.stop()
815
+ # Iterate through all the custom components and add their imports to the all_imports.
816
+ for component in custom_components:
817
+ all_imports.update(component.get_imports())
790
818
 
791
- # Install frontend packages.
792
- self.get_frontend_packages(all_imports)
819
+ progress.advance(task)
793
820
 
794
- # Write the pages at the end to trigger the NextJS hot reload only once.
795
- write_page_futures = []
796
- for output_path, code in compile_results:
797
- write_page_futures.append(
798
- thread_pool.submit(compiler_utils.write_page, output_path, code)
799
- )
800
- for future in concurrent.futures.as_completed(write_page_futures):
801
- future.result()
821
+ # Empty the .web pages directory.
822
+ compiler.purge_web_pages_dir()
823
+
824
+ progress.advance(task)
825
+ progress.stop()
826
+
827
+ # Install frontend packages.
828
+ self.get_frontend_packages(all_imports)
829
+
830
+ for output_path, code in compile_results:
831
+ compiler_utils.write_page(output_path, code)
802
832
 
803
833
  @contextlib.asynccontextmanager
804
834
  async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
@@ -454,3 +454,113 @@ def remove_tailwind_from_postcss() -> tuple[str, str]:
454
454
  def purge_web_pages_dir():
455
455
  """Empty out .web directory."""
456
456
  utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
457
+
458
+
459
+ class ExecutorSafeFunctions:
460
+ """Helper class to allow parallelisation of parts of the compilation process.
461
+
462
+ This class (and its class attributes) are available at global scope.
463
+
464
+ In a multiprocessing context (like when using a ProcessPoolExecutor), the content of this
465
+ global class is logically replicated to any FORKED process.
466
+
467
+ How it works:
468
+ * Before the child process is forked, ensure that we stash any input data required by any future
469
+ function call in the child process.
470
+ * After the child process is forked, the child process will have a copy of the global class, which
471
+ includes the previously stashed input data.
472
+ * Any task submitted to the child process simply needs a way to communicate which input data the
473
+ requested function call requires.
474
+
475
+ Why do we need this? Passing input data directly to child process often not possible because the input data is not picklable.
476
+ The mechanic described here removes the need to pickle the input data at all.
477
+
478
+ Limitations:
479
+ * This can never support returning unpicklable OUTPUT data.
480
+ * Any object mutations done by the child process will not propagate back to the parent process (fork goes one way!).
481
+
482
+ """
483
+
484
+ COMPILE_PAGE_ARGS_BY_ROUTE = {}
485
+ COMPILE_APP_APP_ROOT: Component | None = None
486
+ CUSTOM_COMPONENTS: set[CustomComponent] | None = None
487
+ HEAD_COMPONENTS: list[Component] | None = None
488
+ STYLE: ComponentStyle | None = None
489
+ STATE: type[BaseState] | None = None
490
+
491
+ @classmethod
492
+ def compile_page(cls, route: str):
493
+ """Compile a page.
494
+
495
+ Args:
496
+ route: The route of the page to compile.
497
+
498
+ Returns:
499
+ The path and code of the compiled page.
500
+ """
501
+ return compile_page(*cls.COMPILE_PAGE_ARGS_BY_ROUTE[route])
502
+
503
+ @classmethod
504
+ def compile_app(cls):
505
+ """Compile the app.
506
+
507
+ Returns:
508
+ The path and code of the compiled app.
509
+
510
+ Raises:
511
+ ValueError: If the app root is not set.
512
+ """
513
+ if cls.COMPILE_APP_APP_ROOT is None:
514
+ raise ValueError("COMPILE_APP_APP_ROOT should be set")
515
+ return compile_app(cls.COMPILE_APP_APP_ROOT)
516
+
517
+ @classmethod
518
+ def compile_custom_components(cls):
519
+ """Compile the custom components.
520
+
521
+ Returns:
522
+ The path and code of the compiled custom components.
523
+
524
+ Raises:
525
+ ValueError: If the custom components are not set.
526
+ """
527
+ if cls.CUSTOM_COMPONENTS is None:
528
+ raise ValueError("CUSTOM_COMPONENTS should be set")
529
+ return compile_components(cls.CUSTOM_COMPONENTS)
530
+
531
+ @classmethod
532
+ def compile_document_root(cls):
533
+ """Compile the document root.
534
+
535
+ Returns:
536
+ The path and code of the compiled document root.
537
+
538
+ Raises:
539
+ ValueError: If the head components are not set.
540
+ """
541
+ if cls.HEAD_COMPONENTS is None:
542
+ raise ValueError("HEAD_COMPONENTS should be set")
543
+ return compile_document_root(cls.HEAD_COMPONENTS)
544
+
545
+ @classmethod
546
+ def compile_theme(cls):
547
+ """Compile the theme.
548
+
549
+ Returns:
550
+ The path and code of the compiled theme.
551
+
552
+ Raises:
553
+ ValueError: If the style is not set.
554
+ """
555
+ if cls.STYLE is None:
556
+ raise ValueError("STYLE should be set")
557
+ return compile_theme(cls.STYLE)
558
+
559
+ @classmethod
560
+ def compile_contexts(cls):
561
+ """Compile the contexts.
562
+
563
+ Returns:
564
+ The path and code of the compiled contexts.
565
+ """
566
+ return compile_contexts(cls.STATE)
reflex/compiler/utils.py CHANGED
@@ -2,7 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import os
5
- from typing import Any, Type
5
+ from typing import Any, Callable, Type
6
6
  from urllib.parse import urlparse
7
7
 
8
8
  from pydantic.fields import ModelField
@@ -290,20 +290,21 @@ def create_theme(style: ComponentStyle) -> dict:
290
290
  The base style for the app.
291
291
  """
292
292
  # Get the global style from the style dict.
293
- global_style = Style({k: v for k, v in style.items() if not isinstance(k, type)})
293
+ style_rules = Style({k: v for k, v in style.items() if not isinstance(k, Callable)})
294
294
 
295
- # Root styles.
296
- root_style = Style({k: v for k, v in global_style.items() if k.startswith("::")})
297
-
298
- # Body styles.
299
- root_style["body"] = Style(
300
- {k: v for k, v in global_style.items() if k not in root_style}
301
- )
295
+ root_style = {
296
+ # Root styles.
297
+ ":root": Style(
298
+ {f"*{k}": v for k, v in style_rules.items() if k.startswith(":")}
299
+ ),
300
+ # Body styles.
301
+ "body": Style(
302
+ {k: v for k, v in style_rules.items() if not k.startswith(":")},
303
+ ),
304
+ }
302
305
 
303
306
  # Return the theme.
304
- return {
305
- "styles": {"global": root_style},
306
- }
307
+ return {"styles": {"global": root_style}}
307
308
 
308
309
 
309
310
  def get_page_path(path: str) -> str:
@@ -22,6 +22,7 @@ class AppWrap(Fragment):
22
22
  id: Optional[Any] = None,
23
23
  class_name: Optional[Any] = None,
24
24
  autofocus: Optional[bool] = None,
25
+ _rename_props: Optional[Dict[str, str]] = None,
25
26
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
26
27
  on_blur: Optional[
27
28
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -20,6 +20,7 @@ class Body(Component):
20
20
  id: Optional[Any] = None,
21
21
  class_name: Optional[Any] = None,
22
22
  autofocus: Optional[bool] = None,
23
+ _rename_props: Optional[Dict[str, str]] = None,
23
24
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
24
25
  on_blur: Optional[
25
26
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -77,6 +78,7 @@ class Body(Component):
77
78
  id: The id for the component.
78
79
  class_name: The class name for the component.
79
80
  autofocus: Whether the component should take the focus once the page is loaded
81
+ _rename_props: props to change the name of
80
82
  custom_attrs: custom attribute
81
83
  **props: The props of the component.
82
84
 
@@ -20,6 +20,7 @@ class NextDocumentLib(Component):
20
20
  id: Optional[Any] = None,
21
21
  class_name: Optional[Any] = None,
22
22
  autofocus: Optional[bool] = None,
23
+ _rename_props: Optional[Dict[str, str]] = None,
23
24
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
24
25
  on_blur: Optional[
25
26
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -77,6 +78,7 @@ class NextDocumentLib(Component):
77
78
  id: The id for the component.
78
79
  class_name: The class name for the component.
79
80
  autofocus: Whether the component should take the focus once the page is loaded
81
+ _rename_props: props to change the name of
80
82
  custom_attrs: custom attribute
81
83
  **props: The props of the component.
82
84
 
@@ -99,6 +101,7 @@ class Html(NextDocumentLib):
99
101
  id: Optional[Any] = None,
100
102
  class_name: Optional[Any] = None,
101
103
  autofocus: Optional[bool] = None,
104
+ _rename_props: Optional[Dict[str, str]] = None,
102
105
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
103
106
  on_blur: Optional[
104
107
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -156,6 +159,7 @@ class Html(NextDocumentLib):
156
159
  id: The id for the component.
157
160
  class_name: The class name for the component.
158
161
  autofocus: Whether the component should take the focus once the page is loaded
162
+ _rename_props: props to change the name of
159
163
  custom_attrs: custom attribute
160
164
  **props: The props of the component.
161
165
 
@@ -178,6 +182,7 @@ class DocumentHead(NextDocumentLib):
178
182
  id: Optional[Any] = None,
179
183
  class_name: Optional[Any] = None,
180
184
  autofocus: Optional[bool] = None,
185
+ _rename_props: Optional[Dict[str, str]] = None,
181
186
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
182
187
  on_blur: Optional[
183
188
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -235,6 +240,7 @@ class DocumentHead(NextDocumentLib):
235
240
  id: The id for the component.
236
241
  class_name: The class name for the component.
237
242
  autofocus: Whether the component should take the focus once the page is loaded
243
+ _rename_props: props to change the name of
238
244
  custom_attrs: custom attribute
239
245
  **props: The props of the component.
240
246
 
@@ -257,6 +263,7 @@ class Main(NextDocumentLib):
257
263
  id: Optional[Any] = None,
258
264
  class_name: Optional[Any] = None,
259
265
  autofocus: Optional[bool] = None,
266
+ _rename_props: Optional[Dict[str, str]] = None,
260
267
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
261
268
  on_blur: Optional[
262
269
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -314,6 +321,7 @@ class Main(NextDocumentLib):
314
321
  id: The id for the component.
315
322
  class_name: The class name for the component.
316
323
  autofocus: Whether the component should take the focus once the page is loaded
324
+ _rename_props: props to change the name of
317
325
  custom_attrs: custom attribute
318
326
  **props: The props of the component.
319
327
 
@@ -336,6 +344,7 @@ class NextScript(NextDocumentLib):
336
344
  id: Optional[Any] = None,
337
345
  class_name: Optional[Any] = None,
338
346
  autofocus: Optional[bool] = None,
347
+ _rename_props: Optional[Dict[str, str]] = None,
339
348
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
340
349
  on_blur: Optional[
341
350
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -393,6 +402,7 @@ class NextScript(NextDocumentLib):
393
402
  id: The id for the component.
394
403
  class_name: The class name for the component.
395
404
  autofocus: Whether the component should take the focus once the page is loaded
405
+ _rename_props: props to change the name of
396
406
  custom_attrs: custom attribute
397
407
  **props: The props of the component.
398
408
 
@@ -20,6 +20,7 @@ class Fragment(Component):
20
20
  id: Optional[Any] = None,
21
21
  class_name: Optional[Any] = None,
22
22
  autofocus: Optional[bool] = None,
23
+ _rename_props: Optional[Dict[str, str]] = None,
23
24
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
24
25
  on_blur: Optional[
25
26
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -77,6 +78,7 @@ class Fragment(Component):
77
78
  id: The id for the component.
78
79
  class_name: The class name for the component.
79
80
  autofocus: Whether the component should take the focus once the page is loaded
81
+ _rename_props: props to change the name of
80
82
  custom_attrs: custom attribute
81
83
  **props: The props of the component.
82
84
 
@@ -20,6 +20,7 @@ class NextHeadLib(Component):
20
20
  id: Optional[Any] = None,
21
21
  class_name: Optional[Any] = None,
22
22
  autofocus: Optional[bool] = None,
23
+ _rename_props: Optional[Dict[str, str]] = None,
23
24
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
24
25
  on_blur: Optional[
25
26
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -77,6 +78,7 @@ class NextHeadLib(Component):
77
78
  id: The id for the component.
78
79
  class_name: The class name for the component.
79
80
  autofocus: Whether the component should take the focus once the page is loaded
81
+ _rename_props: props to change the name of
80
82
  custom_attrs: custom attribute
81
83
  **props: The props of the component.
82
84
 
@@ -99,6 +101,7 @@ class Head(NextHeadLib, MemoizationLeaf):
99
101
  id: Optional[Any] = None,
100
102
  class_name: Optional[Any] = None,
101
103
  autofocus: Optional[bool] = None,
104
+ _rename_props: Optional[Dict[str, str]] = None,
102
105
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
103
106
  on_blur: Optional[
104
107
  Union[EventHandler, EventSpec, list, function, BaseVar]
@@ -156,6 +159,7 @@ class Head(NextHeadLib, MemoizationLeaf):
156
159
  id: The id for the component.
157
160
  class_name: The class name for the component.
158
161
  autofocus: Whether the component should take the focus once the page is loaded
162
+ _rename_props: props to change the name of
159
163
  custom_attrs: custom attribute
160
164
  **props: The props of the component.
161
165