reflex 0.7.0a4__py3-none-any.whl → 0.7.1a1__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 (127) hide show
  1. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
  2. reflex/__init__.py +1 -0
  3. reflex/__init__.pyi +1 -0
  4. reflex/app.py +251 -68
  5. reflex/base.py +4 -10
  6. reflex/compiler/compiler.py +46 -12
  7. reflex/compiler/templates.py +1 -2
  8. reflex/compiler/utils.py +23 -14
  9. reflex/components/base/bare.py +109 -16
  10. reflex/components/component.py +179 -124
  11. reflex/components/core/__init__.py +1 -0
  12. reflex/components/core/__init__.pyi +1 -0
  13. reflex/components/core/auto_scroll.py +111 -0
  14. reflex/components/core/auto_scroll.pyi +284 -0
  15. reflex/components/core/banner.py +35 -5
  16. reflex/components/core/banner.pyi +398 -36
  17. reflex/components/core/breakpoints.py +1 -1
  18. reflex/components/core/cond.py +0 -8
  19. reflex/components/core/foreach.py +12 -2
  20. reflex/components/core/html.pyi +200 -19
  21. reflex/components/core/match.py +4 -4
  22. reflex/components/core/sticky.py +4 -30
  23. reflex/components/core/sticky.pyi +874 -90
  24. reflex/components/core/upload.py +3 -5
  25. reflex/components/core/upload.pyi +2 -4
  26. reflex/components/datadisplay/code.py +36 -10
  27. reflex/components/datadisplay/code.pyi +1 -1
  28. reflex/components/datadisplay/dataeditor.py +1 -3
  29. reflex/components/datadisplay/dataeditor.pyi +1 -3
  30. reflex/components/el/elements/base.py +95 -17
  31. reflex/components/el/elements/base.pyi +278 -19
  32. reflex/components/el/elements/forms.py +124 -102
  33. reflex/components/el/elements/forms.pyi +2787 -365
  34. reflex/components/el/elements/inline.py +24 -15
  35. reflex/components/el/elements/inline.pyi +5655 -546
  36. reflex/components/el/elements/media.py +79 -95
  37. reflex/components/el/elements/media.pyi +5167 -565
  38. reflex/components/el/elements/metadata.py +19 -17
  39. reflex/components/el/elements/metadata.pyi +841 -89
  40. reflex/components/el/elements/other.py +3 -5
  41. reflex/components/el/elements/other.pyi +1404 -137
  42. reflex/components/el/elements/scripts.py +10 -13
  43. reflex/components/el/elements/scripts.pyi +634 -65
  44. reflex/components/el/elements/sectioning.pyi +3001 -286
  45. reflex/components/el/elements/tables.py +14 -35
  46. reflex/components/el/elements/tables.pyi +2029 -218
  47. reflex/components/el/elements/typography.py +10 -13
  48. reflex/components/el/elements/typography.pyi +3014 -297
  49. reflex/components/lucide/icon.py +22 -6
  50. reflex/components/markdown/markdown.py +30 -10
  51. reflex/components/markdown/markdown.pyi +3 -2
  52. reflex/components/plotly/plotly.py +1 -3
  53. reflex/components/plotly/plotly.pyi +1 -3
  54. reflex/components/radix/primitives/form.pyi +624 -93
  55. reflex/components/radix/themes/color_mode.py +1 -1
  56. reflex/components/radix/themes/color_mode.pyi +213 -31
  57. reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
  58. reflex/components/radix/themes/components/badge.pyi +199 -18
  59. reflex/components/radix/themes/components/button.pyi +213 -31
  60. reflex/components/radix/themes/components/callout.pyi +1000 -95
  61. reflex/components/radix/themes/components/card.pyi +199 -18
  62. reflex/components/radix/themes/components/context_menu.py +79 -1
  63. reflex/components/radix/themes/components/context_menu.pyi +320 -1
  64. reflex/components/radix/themes/components/dialog.pyi +199 -18
  65. reflex/components/radix/themes/components/hover_card.pyi +199 -18
  66. reflex/components/radix/themes/components/icon_button.pyi +213 -31
  67. reflex/components/radix/themes/components/inset.pyi +199 -18
  68. reflex/components/radix/themes/components/popover.pyi +199 -18
  69. reflex/components/radix/themes/components/table.pyi +1437 -154
  70. reflex/components/radix/themes/components/text_area.py +2 -2
  71. reflex/components/radix/themes/components/text_area.pyi +201 -20
  72. reflex/components/radix/themes/components/text_field.py +1 -1
  73. reflex/components/radix/themes/components/text_field.pyi +444 -88
  74. reflex/components/radix/themes/layout/box.pyi +200 -19
  75. reflex/components/radix/themes/layout/center.pyi +199 -18
  76. reflex/components/radix/themes/layout/container.pyi +199 -18
  77. reflex/components/radix/themes/layout/flex.pyi +199 -18
  78. reflex/components/radix/themes/layout/grid.pyi +199 -18
  79. reflex/components/radix/themes/layout/list.pyi +604 -57
  80. reflex/components/radix/themes/layout/section.pyi +199 -18
  81. reflex/components/radix/themes/layout/spacer.pyi +199 -18
  82. reflex/components/radix/themes/layout/stack.pyi +597 -54
  83. reflex/components/radix/themes/typography/blockquote.pyi +200 -19
  84. reflex/components/radix/themes/typography/code.pyi +199 -18
  85. reflex/components/radix/themes/typography/heading.pyi +199 -18
  86. reflex/components/radix/themes/typography/link.pyi +238 -28
  87. reflex/components/radix/themes/typography/text.pyi +1394 -127
  88. reflex/components/react_player/react_player.py +1 -1
  89. reflex/components/react_player/react_player.pyi +1 -3
  90. reflex/components/sonner/toast.py +19 -1
  91. reflex/components/sonner/toast.pyi +10 -1
  92. reflex/components/tags/iter_tag.py +4 -0
  93. reflex/components/tags/tag.py +3 -3
  94. reflex/config.py +187 -28
  95. reflex/constants/__init__.py +2 -0
  96. reflex/constants/base.py +6 -0
  97. reflex/constants/compiler.py +9 -0
  98. reflex/constants/event.py +1 -0
  99. reflex/constants/installer.py +4 -5
  100. reflex/constants/utils.py +1 -3
  101. reflex/event.py +7 -16
  102. reflex/experimental/layout.pyi +597 -54
  103. reflex/py.typed +0 -0
  104. reflex/reflex.py +44 -48
  105. reflex/state.py +49 -44
  106. reflex/style.py +6 -4
  107. reflex/testing.py +2 -0
  108. reflex/utils/build.py +12 -0
  109. reflex/utils/console.py +4 -0
  110. reflex/utils/decorator.py +25 -0
  111. reflex/utils/exec.py +92 -34
  112. reflex/utils/format.py +35 -6
  113. reflex/utils/path_ops.py +32 -1
  114. reflex/utils/prerequisites.py +54 -10
  115. reflex/utils/processes.py +12 -13
  116. reflex/utils/serializers.py +20 -43
  117. reflex/utils/telemetry.py +4 -15
  118. reflex/utils/types.py +36 -66
  119. reflex/vars/base.py +53 -76
  120. reflex/vars/function.py +17 -5
  121. reflex/vars/number.py +1 -1
  122. reflex/vars/sequence.py +80 -4
  123. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/METADATA +4 -5
  124. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/RECORD +127 -123
  125. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/LICENSE +0 -0
  126. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/WHEEL +0 -0
  127. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/entry_points.txt +0 -0
reflex/py.typed ADDED
File without changes
reflex/reflex.py CHANGED
@@ -20,13 +20,8 @@ from reflex.utils import console, telemetry
20
20
  typer.core.rich = None # pyright: ignore [reportPrivateImportUsage]
21
21
 
22
22
  # Create the app.
23
- try:
24
- cli = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
25
- except TypeError:
26
- # Fallback for older typer versions.
27
- cli = typer.Typer(add_completion=False)
23
+ cli = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
28
24
 
29
- SHOW_BUILT_WITH_REFLEX_INFO = "https://reflex.dev/docs/hosting/reflex-branding/"
30
25
 
31
26
  # Get the config.
32
27
  config = get_config()
@@ -127,8 +122,8 @@ def _run(
127
122
  env: constants.Env = constants.Env.DEV,
128
123
  frontend: bool = True,
129
124
  backend: bool = True,
130
- frontend_port: int = config.frontend_port,
131
- backend_port: int = config.backend_port,
125
+ frontend_port: int | None = None,
126
+ backend_port: int | None = None,
132
127
  backend_host: str = config.backend_host,
133
128
  loglevel: constants.LogLevel = config.loglevel,
134
129
  ):
@@ -145,10 +140,7 @@ def _run(
145
140
  exec.output_system_info()
146
141
 
147
142
  # If no --frontend-only and no --backend-only, then turn on frontend and backend both
148
- if not frontend and not backend:
149
- frontend = True
150
- backend = True
151
-
143
+ frontend, backend = prerequisites.check_running_mode(frontend, backend)
152
144
  if not frontend and backend:
153
145
  _skip_compile()
154
146
 
@@ -161,17 +153,28 @@ def _run(
161
153
 
162
154
  # Find the next available open port if applicable.
163
155
  if frontend:
156
+ auto_increment_frontend = not bool(frontend_port or config.frontend_port)
164
157
  frontend_port = processes.handle_port(
165
158
  "frontend",
166
- frontend_port,
167
- constants.DefaultPorts.FRONTEND_PORT,
159
+ (
160
+ frontend_port
161
+ or config.frontend_port
162
+ or constants.DefaultPorts.FRONTEND_PORT
163
+ ),
164
+ auto_increment=auto_increment_frontend,
168
165
  )
169
166
 
170
167
  if backend:
168
+ auto_increment_backend = not bool(backend_port or config.backend_port)
169
+
171
170
  backend_port = processes.handle_port(
172
171
  "backend",
173
- backend_port,
174
- constants.DefaultPorts.BACKEND_PORT,
172
+ (
173
+ backend_port
174
+ or config.backend_port
175
+ or constants.DefaultPorts.BACKEND_PORT
176
+ ),
177
+ auto_increment=auto_increment_backend,
175
178
  )
176
179
 
177
180
  # Apply the new ports to the config.
@@ -188,15 +191,6 @@ def _run(
188
191
  prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
189
192
 
190
193
  if frontend:
191
- if not config.show_built_with_reflex:
192
- # The sticky badge may be disabled at runtime for team/enterprise tiers.
193
- prerequisites.check_config_option_in_tier(
194
- option_name="show_built_with_reflex",
195
- allowed_tiers=["team", "enterprise"],
196
- fallback_value=True,
197
- help_link=SHOW_BUILT_WITH_REFLEX_INFO,
198
- )
199
-
200
194
  # Get the app module.
201
195
  prerequisites.get_compiled_app()
202
196
 
@@ -249,7 +243,7 @@ def _run(
249
243
  # Start the frontend and backend.
250
244
  with processes.run_concurrently_context(*commands):
251
245
  # In dev mode, run the backend on the main thread.
252
- if backend and env == constants.Env.DEV:
246
+ if backend and backend_port and env == constants.Env.DEV:
253
247
  backend_cmd(
254
248
  backend_host, int(backend_port), loglevel.subprocess_level(), frontend
255
249
  )
@@ -278,10 +272,14 @@ def run(
278
272
  envvar=environment.REFLEX_BACKEND_ONLY.name,
279
273
  ),
280
274
  frontend_port: int = typer.Option(
281
- config.frontend_port, help="Specify a different frontend port."
275
+ config.frontend_port,
276
+ help="Specify a different frontend port.",
277
+ envvar=environment.REFLEX_FRONTEND_PORT.name,
282
278
  ),
283
279
  backend_port: int = typer.Option(
284
- config.backend_port, help="Specify a different backend port."
280
+ config.backend_port,
281
+ help="Specify a different backend port.",
282
+ envvar=environment.REFLEX_BACKEND_PORT.name,
285
283
  ),
286
284
  backend_host: str = typer.Option(
287
285
  config.backend_host, help="Specify the backend host."
@@ -294,6 +292,8 @@ def run(
294
292
  if frontend and backend:
295
293
  console.error("Cannot use both --frontend-only and --backend-only options.")
296
294
  raise typer.Exit(1)
295
+
296
+ environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.RUN)
297
297
  environment.REFLEX_BACKEND_ONLY.set(backend)
298
298
  environment.REFLEX_FRONTEND_ONLY.set(frontend)
299
299
 
@@ -306,10 +306,18 @@ def export(
306
306
  True, "--no-zip", help="Disable zip for backend and frontend exports."
307
307
  ),
308
308
  frontend: bool = typer.Option(
309
- True, "--backend-only", help="Export only backend.", show_default=False
309
+ False,
310
+ "--frontend-only",
311
+ help="Export only frontend.",
312
+ show_default=False,
313
+ envvar=environment.REFLEX_FRONTEND_ONLY.name,
310
314
  ),
311
315
  backend: bool = typer.Option(
312
- True, "--frontend-only", help="Export only frontend.", show_default=False
316
+ False,
317
+ "--backend-only",
318
+ help="Export only backend.",
319
+ show_default=False,
320
+ envvar=environment.REFLEX_BACKEND_ONLY.name,
313
321
  ),
314
322
  zip_dest_dir: str = typer.Option(
315
323
  str(Path.cwd()),
@@ -332,17 +340,12 @@ def export(
332
340
  from reflex.utils import export as export_utils
333
341
  from reflex.utils import prerequisites
334
342
 
335
- if prerequisites.needs_reinit(frontend=True):
336
- _init(name=config.app_name, loglevel=loglevel)
343
+ environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.EXPORT)
337
344
 
338
- if frontend and not config.show_built_with_reflex:
339
- # The sticky badge may be disabled on export for team/enterprise tiers.
340
- prerequisites.check_config_option_in_tier(
341
- option_name="show_built_with_reflex",
342
- allowed_tiers=["team", "enterprise"],
343
- fallback_value=False,
344
- help_link=SHOW_BUILT_WITH_REFLEX_INFO,
345
- )
345
+ frontend, backend = prerequisites.check_running_mode(frontend, backend)
346
+
347
+ if prerequisites.needs_reinit(frontend=frontend or not backend):
348
+ _init(name=config.app_name, loglevel=loglevel)
346
349
 
347
350
  export_utils.export(
348
351
  zipping=zipping,
@@ -538,14 +541,7 @@ def deploy(
538
541
 
539
542
  check_version()
540
543
 
541
- if not config.show_built_with_reflex:
542
- # The sticky badge may be disabled on deploy for pro/team/enterprise tiers.
543
- prerequisites.check_config_option_in_tier(
544
- option_name="show_built_with_reflex",
545
- allowed_tiers=["pro", "team", "enterprise"],
546
- fallback_value=True,
547
- help_link=SHOW_BUILT_WITH_REFLEX_INFO,
548
- )
544
+ environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.DEPLOY)
549
545
 
550
546
  # Set the log level.
551
547
  console.set_log_level(loglevel)
reflex/state.py CHANGED
@@ -40,50 +40,22 @@ from typing import (
40
40
  get_type_hints,
41
41
  )
42
42
 
43
- from redis.asyncio.client import PubSub
44
- from sqlalchemy.orm import DeclarativeBase
45
- from typing_extensions import Self
46
-
47
- from reflex import event
48
- from reflex.config import PerformanceMode, get_config
49
- from reflex.istate.data import RouterData
50
- from reflex.istate.storage import ClientStorageBase
51
- from reflex.model import Model
52
- from reflex.vars.base import (
53
- ComputedVar,
54
- DynamicRouteVar,
55
- Var,
56
- computed_var,
57
- dispatch,
58
- get_unique_variable_name,
59
- is_computed_var,
60
- )
61
-
62
- try:
63
- import pydantic.v1 as pydantic
64
- except ModuleNotFoundError:
65
- import pydantic
66
-
67
- from pydantic import BaseModel as BaseModelV2
68
-
69
- try:
70
- from pydantic.v1 import BaseModel as BaseModelV1
71
- except ModuleNotFoundError:
72
- BaseModelV1 = BaseModelV2
73
-
74
- try:
75
- from pydantic.v1 import validator
76
- except ModuleNotFoundError:
77
- from pydantic import validator
78
-
43
+ import pydantic.v1 as pydantic
79
44
  import wrapt
45
+ from pydantic import BaseModel as BaseModelV2
46
+ from pydantic.v1 import BaseModel as BaseModelV1
47
+ from pydantic.v1 import validator
48
+ from pydantic.v1.fields import ModelField
80
49
  from redis.asyncio import Redis
50
+ from redis.asyncio.client import PubSub
81
51
  from redis.exceptions import ResponseError
52
+ from sqlalchemy.orm import DeclarativeBase
53
+ from typing_extensions import Self
82
54
 
83
55
  import reflex.istate.dynamic
84
- from reflex import constants
56
+ from reflex import constants, event
85
57
  from reflex.base import Base
86
- from reflex.config import environment
58
+ from reflex.config import PerformanceMode, environment, get_config
87
59
  from reflex.event import (
88
60
  BACKGROUND_TASK_MARKER,
89
61
  Event,
@@ -91,6 +63,9 @@ from reflex.event import (
91
63
  EventSpec,
92
64
  fix_events,
93
65
  )
66
+ from reflex.istate.data import RouterData
67
+ from reflex.istate.storage import ClientStorageBase
68
+ from reflex.model import Model
94
69
  from reflex.utils import console, format, path_ops, prerequisites, types
95
70
  from reflex.utils.exceptions import (
96
71
  ComputedVarShadowsBaseVarsError,
@@ -121,6 +96,15 @@ from reflex.utils.types import (
121
96
  value_inside_optional,
122
97
  )
123
98
  from reflex.vars import VarData
99
+ from reflex.vars.base import (
100
+ ComputedVar,
101
+ DynamicRouteVar,
102
+ Var,
103
+ computed_var,
104
+ dispatch,
105
+ get_unique_variable_name,
106
+ is_computed_var,
107
+ )
124
108
 
125
109
  if TYPE_CHECKING:
126
110
  from reflex.components.component import Component
@@ -289,10 +273,6 @@ class EventHandlerSetVar(EventHandler):
289
273
  return super().__call__(*args)
290
274
 
291
275
 
292
- if TYPE_CHECKING:
293
- from pydantic.v1.fields import ModelField
294
-
295
-
296
276
  def _unwrap_field_type(type_: Type) -> Type:
297
277
  """Unwrap rx.Field type annotations.
298
278
 
@@ -347,6 +327,9 @@ async def _resolve_delta(delta: Delta) -> Delta:
347
327
  return delta
348
328
 
349
329
 
330
+ all_base_state_classes: dict[str, None] = {}
331
+
332
+
350
333
  class BaseState(Base, ABC, extra=pydantic.Extra.allow):
351
334
  """The state of the app."""
352
335
 
@@ -644,6 +627,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
644
627
  cls._var_dependencies = {}
645
628
  cls._init_var_dependency_dicts()
646
629
 
630
+ all_base_state_classes[cls.get_full_name()] = None
631
+
647
632
  @staticmethod
648
633
  def _copy_fn(fn: Callable) -> Callable:
649
634
  """Copy a function. Used to copy ComputedVars and EventHandlers from mixins.
@@ -1742,6 +1727,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1742
1727
 
1743
1728
  Yields:
1744
1729
  StateUpdate object
1730
+
1731
+ Raises:
1732
+ ValueError: If a string value is received for an int or float type and cannot be converted.
1745
1733
  """
1746
1734
  from reflex.utils import telemetry
1747
1735
 
@@ -1779,12 +1767,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1779
1767
  hinted_args, (Base, BaseModelV1, BaseModelV2)
1780
1768
  ):
1781
1769
  payload[arg] = hinted_args(**value)
1782
- if isinstance(value, list) and (hinted_args is set or hinted_args is Set):
1770
+ elif isinstance(value, list) and (hinted_args is set or hinted_args is Set):
1783
1771
  payload[arg] = set(value)
1784
- if isinstance(value, list) and (
1772
+ elif isinstance(value, list) and (
1785
1773
  hinted_args is tuple or hinted_args is Tuple
1786
1774
  ):
1787
1775
  payload[arg] = tuple(value)
1776
+ elif isinstance(value, str) and (
1777
+ hinted_args is int or hinted_args is float
1778
+ ):
1779
+ try:
1780
+ payload[arg] = hinted_args(value)
1781
+ except ValueError:
1782
+ raise ValueError(
1783
+ f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
1784
+ ) from None
1785
+ else:
1786
+ console.warn(
1787
+ f"Received a string value ({value}) for {arg} but expected a {hinted_args}. A simple conversion was successful."
1788
+ )
1788
1789
 
1789
1790
  # Wrap the function in a try/except block.
1790
1791
  try:
@@ -2459,6 +2460,8 @@ class ComponentState(State, mixin=True):
2459
2460
  Returns:
2460
2461
  A new instance of the Component with an independent copy of the State.
2461
2462
  """
2463
+ from reflex.compiler.compiler import into_component
2464
+
2462
2465
  cls._per_component_state_instance_count += 1
2463
2466
  state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
2464
2467
  component_state = type(
@@ -2470,6 +2473,7 @@ class ComponentState(State, mixin=True):
2470
2473
  # Save a reference to the dynamic state for pickle/unpickle.
2471
2474
  setattr(reflex.istate.dynamic, state_cls_name, component_state)
2472
2475
  component = component_state.get_component(*children, **props)
2476
+ component = into_component(component)
2473
2477
  component.State = component_state
2474
2478
  return component
2475
2479
 
@@ -4088,6 +4092,7 @@ def reload_state_module(
4088
4092
  for subclass in tuple(state.class_subclasses):
4089
4093
  reload_state_module(module=module, state=subclass)
4090
4094
  if subclass.__module__ == module and module is not None:
4095
+ all_base_state_classes.pop(subclass.get_full_name(), None)
4091
4096
  state.class_subclasses.remove(subclass)
4092
4097
  state._always_dirty_substates.discard(subclass.get_name())
4093
4098
  state._var_dependencies = {}
reflex/style.py CHANGED
@@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError
12
12
  from reflex.utils.imports import ImportVar
13
13
  from reflex.utils.types import get_origin
14
14
  from reflex.vars import VarData
15
- from reflex.vars.base import CallableVar, LiteralVar, Var
15
+ from reflex.vars.base import LiteralVar, Var
16
16
  from reflex.vars.function import FunctionVar
17
17
  from reflex.vars.object import ObjectVar
18
18
 
@@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var:
48
48
  ).guess_type()
49
49
 
50
50
 
51
- @CallableVar
52
51
  def set_color_mode(
53
52
  new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
54
53
  ) -> Var[EventChain]:
@@ -191,11 +190,12 @@ def convert(
191
190
  for key, value in style_dict.items():
192
191
  keys = (
193
192
  format_style_key(key)
194
- if not isinstance(value, (dict, ObjectVar))
193
+ if not isinstance(value, (dict, ObjectVar, list))
195
194
  or (
196
195
  isinstance(value, Breakpoints)
197
196
  and all(not isinstance(v, dict) for v in value.values())
198
197
  )
198
+ or (isinstance(value, list) and all(not isinstance(v, dict) for v in value))
199
199
  or (
200
200
  isinstance(value, ObjectVar)
201
201
  and not issubclass(get_origin(value._var_type) or value._var_type, dict)
@@ -237,7 +237,9 @@ def format_style_key(key: str) -> Tuple[str, ...]:
237
237
  Returns:
238
238
  Tuple of css style names corresponding to the key provided.
239
239
  """
240
- key = format.to_camel_case(key, allow_hyphens=True)
240
+ if key.startswith("--"):
241
+ return (key,)
242
+ key = format.to_camel_case(key)
241
243
  return STYLE_PROP_SHORTHAND_MAPPING.get(key, (key,))
242
244
 
243
245
 
reflex/testing.py CHANGED
@@ -43,6 +43,7 @@ import reflex.utils.exec
43
43
  import reflex.utils.format
44
44
  import reflex.utils.prerequisites
45
45
  import reflex.utils.processes
46
+ from reflex.components.component import CustomComponent
46
47
  from reflex.config import environment
47
48
  from reflex.state import (
48
49
  BaseState,
@@ -254,6 +255,7 @@ class AppHarness:
254
255
  # disable telemetry reporting for tests
255
256
 
256
257
  os.environ["TELEMETRY_ENABLED"] = "false"
258
+ CustomComponent.create().get_component.cache_clear()
257
259
  self.app_path.mkdir(parents=True, exist_ok=True)
258
260
  if self.app_source is not None:
259
261
  app_globals = self._get_globals_from_signature(self.app_source)
reflex/utils/build.py CHANGED
@@ -60,6 +60,7 @@ def _zip(
60
60
  dirs_to_exclude: set[str] | None = None,
61
61
  files_to_exclude: set[str] | None = None,
62
62
  top_level_dirs_to_exclude: set[str] | None = None,
63
+ globs_to_include: list[str] | None = None,
63
64
  ) -> None:
64
65
  """Zip utility function.
65
66
 
@@ -72,6 +73,7 @@ def _zip(
72
73
  dirs_to_exclude: The directories to exclude.
73
74
  files_to_exclude: The files to exclude.
74
75
  top_level_dirs_to_exclude: The top level directory names immediately under root_dir to exclude. Do not exclude folders by these names further in the sub-directories.
76
+ globs_to_include: Apply these globs from the root_dir and always include them in the zip.
75
77
 
76
78
  """
77
79
  target = Path(target)
@@ -103,6 +105,13 @@ def _zip(
103
105
  files_to_zip += [
104
106
  str(root / file) for file in files if file not in files_to_exclude
105
107
  ]
108
+ if globs_to_include:
109
+ for glob in globs_to_include:
110
+ files_to_zip += [
111
+ str(file)
112
+ for file in root_dir.glob(glob)
113
+ if file.name not in files_to_exclude
114
+ ]
106
115
 
107
116
  # Create a progress bar for zipping the component.
108
117
  progress = Progress(
@@ -160,6 +169,9 @@ def zip_app(
160
169
  top_level_dirs_to_exclude={"assets"},
161
170
  exclude_venv_dirs=True,
162
171
  upload_db_file=upload_db_file,
172
+ globs_to_include=[
173
+ str(Path(constants.Dirs.WEB) / constants.Dirs.BACKEND / "*")
174
+ ],
163
175
  )
164
176
 
165
177
 
reflex/utils/console.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  import inspect
7
+ import os
7
8
  import shutil
8
9
  import time
9
10
  from pathlib import Path
@@ -60,6 +61,9 @@ def set_log_level(log_level: LogLevel):
60
61
  f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
61
62
  )
62
63
  global _LOG_LEVEL
64
+ if log_level != _LOG_LEVEL:
65
+ # Set the loglevel persistenly for subprocesses.
66
+ os.environ["LOGLEVEL"] = log_level.value
63
67
  _LOG_LEVEL = log_level
64
68
 
65
69
 
@@ -0,0 +1,25 @@
1
+ """Decorator utilities."""
2
+
3
+ from typing import Callable, TypeVar
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ def once(f: Callable[[], T]) -> Callable[[], T]:
9
+ """A decorator that calls the function once and caches the result.
10
+
11
+ Args:
12
+ f: The function to call.
13
+
14
+ Returns:
15
+ A function that calls the function once and caches the result.
16
+ """
17
+ unset = object()
18
+ value: object | T = unset
19
+
20
+ def wrapper() -> T:
21
+ nonlocal value
22
+ value = f() if value is unset else value
23
+ return value # pyright: ignore[reportReturnType]
24
+
25
+ return wrapper
reflex/utils/exec.py CHANGED
@@ -10,6 +10,7 @@ import re
10
10
  import subprocess
11
11
  import sys
12
12
  from pathlib import Path
13
+ from typing import Sequence
13
14
  from urllib.parse import urljoin
14
15
 
15
16
  import psutil
@@ -242,29 +243,63 @@ def run_backend(
242
243
  run_uvicorn_backend(host, port, loglevel)
243
244
 
244
245
 
245
- def get_reload_dirs() -> list[Path]:
246
- """Get the reload directories for the backend.
246
+ def get_reload_paths() -> Sequence[Path]:
247
+ """Get the reload paths for the backend.
247
248
 
248
249
  Returns:
249
- The reload directories for the backend.
250
+ The reload paths for the backend.
250
251
  """
251
252
  config = get_config()
252
- reload_dirs = [Path(config.app_name)]
253
+ reload_paths = [Path(config.app_name).parent]
253
254
  if config.app_module is not None and config.app_module.__file__:
254
255
  module_path = Path(config.app_module.__file__).resolve().parent
255
256
 
256
- while module_path.parent.name:
257
- if any(
258
- sibling_file.name == "__init__.py"
259
- for sibling_file in module_path.parent.iterdir()
260
- ):
261
- # go up a level to find dir without `__init__.py`
262
- module_path = module_path.parent
263
- else:
264
- break
257
+ while module_path.parent.name and any(
258
+ sibling_file.name == "__init__.py"
259
+ for sibling_file in module_path.parent.iterdir()
260
+ ):
261
+ # go up a level to find dir without `__init__.py`
262
+ module_path = module_path.parent
265
263
 
266
- reload_dirs = [module_path]
267
- return reload_dirs
264
+ reload_paths = [module_path]
265
+
266
+ include_dirs = tuple(
267
+ map(Path.absolute, environment.REFLEX_HOT_RELOAD_INCLUDE_PATHS.get())
268
+ )
269
+ exclude_dirs = tuple(
270
+ map(Path.absolute, environment.REFLEX_HOT_RELOAD_EXCLUDE_PATHS.get())
271
+ )
272
+
273
+ def is_excluded_by_default(path: Path) -> bool:
274
+ if path.is_dir():
275
+ if path.name.startswith("."):
276
+ # exclude hidden directories
277
+ return True
278
+ if path.name.startswith("__"):
279
+ # ignore things like __pycache__
280
+ return True
281
+ return path.name in (".gitignore", "uploaded_files")
282
+
283
+ reload_paths = (
284
+ tuple(
285
+ path.absolute()
286
+ for dir in reload_paths
287
+ for path in dir.iterdir()
288
+ if not is_excluded_by_default(path)
289
+ )
290
+ + include_dirs
291
+ )
292
+
293
+ if exclude_dirs:
294
+ reload_paths = tuple(
295
+ path
296
+ for path in reload_paths
297
+ if all(not path.samefile(exclude) for exclude in exclude_dirs)
298
+ )
299
+
300
+ console.debug(f"Reload paths: {list(map(str, reload_paths))}")
301
+
302
+ return reload_paths
268
303
 
269
304
 
270
305
  def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
@@ -283,7 +318,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
283
318
  port=port,
284
319
  log_level=loglevel.value,
285
320
  reload=True,
286
- reload_dirs=list(map(str, get_reload_dirs())),
321
+ reload_dirs=list(map(str, get_reload_paths())),
287
322
  )
288
323
 
289
324
 
@@ -310,8 +345,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
310
345
  interface=Interfaces.ASGI,
311
346
  log_level=LogLevels(loglevel.value),
312
347
  reload=True,
313
- reload_paths=get_reload_dirs(),
314
- reload_ignore_dirs=[".web", ".states"],
348
+ reload_paths=get_reload_paths(),
315
349
  ).serve()
316
350
  except ImportError:
317
351
  console.error(
@@ -368,34 +402,49 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
368
402
 
369
403
  app_module = get_app_module()
370
404
 
371
- run_backend_prod = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
372
- run_backend_prod_windows = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
373
405
  command = (
374
406
  [
375
- *run_backend_prod_windows,
376
- "--host",
377
- host,
378
- "--port",
379
- str(port),
407
+ "uvicorn",
408
+ *(
409
+ [
410
+ "--limit-max-requests",
411
+ str(config.gunicorn_max_requests),
412
+ ]
413
+ if config.gunicorn_max_requests > 0
414
+ else []
415
+ ),
416
+ *("--timeout-keep-alive", str(config.timeout)),
417
+ *("--host", host),
418
+ *("--port", str(port)),
419
+ *("--workers", str(_get_backend_workers())),
380
420
  app_module,
381
421
  ]
382
422
  if constants.IS_WINDOWS
383
423
  else [
384
- *run_backend_prod,
385
- "--bind",
386
- f"{host}:{port}",
387
- "--threads",
388
- str(_get_backend_workers()),
424
+ "gunicorn",
425
+ *("--worker-class", config.gunicorn_worker_class),
426
+ *(
427
+ [
428
+ "--max-requests",
429
+ str(config.gunicorn_max_requests),
430
+ "--max-requests-jitter",
431
+ str(config.gunicorn_max_requests_jitter),
432
+ ]
433
+ if config.gunicorn_max_requests > 0
434
+ else []
435
+ ),
436
+ "--preload",
437
+ *("--timeout", str(config.timeout)),
438
+ *("--bind", f"{host}:{port}"),
439
+ *("--threads", str(_get_backend_workers())),
389
440
  f"{app_module}()",
390
441
  ]
391
442
  )
392
443
 
393
444
  command += [
394
- "--log-level",
395
- loglevel.value,
396
- "--workers",
397
- str(_get_backend_workers()),
445
+ *("--log-level", loglevel.value),
398
446
  ]
447
+
399
448
  processes.new_process(
400
449
  command,
401
450
  run=True,
@@ -535,3 +584,12 @@ def is_prod_mode() -> bool:
535
584
  """
536
585
  current_mode = environment.REFLEX_ENV_MODE.get()
537
586
  return current_mode == constants.Env.PROD
587
+
588
+
589
+ def get_compile_context() -> constants.CompileContext:
590
+ """Check if the app is compiled for deploy.
591
+
592
+ Returns:
593
+ Whether the app is being compiled for deploy.
594
+ """
595
+ return environment.REFLEX_COMPILE_CONTEXT.get()