reflex 0.4.9a2__py3-none-any.whl → 0.5.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (131) hide show
  1. reflex/.templates/apps/blank/code/blank.py +19 -16
  2. reflex/.templates/apps/demo/code/pages/datatable.py +4 -4
  3. reflex/.templates/apps/demo/code/pages/forms.py +2 -2
  4. reflex/.templates/web/utils/helpers/debounce.js +17 -0
  5. reflex/.templates/web/utils/helpers/throttle.js +22 -0
  6. reflex/.templates/web/utils/state.js +21 -3
  7. reflex/__init__.py +6 -1
  8. reflex/__init__.pyi +4 -1
  9. reflex/app.py +157 -140
  10. reflex/app_module_for_backend.py +1 -1
  11. reflex/base.py +13 -15
  12. reflex/compiler/compiler.py +10 -1
  13. reflex/compiler/utils.py +3 -30
  14. reflex/components/__init__.py +1 -0
  15. reflex/components/chakra/datadisplay/list.py +1 -3
  16. reflex/components/chakra/datadisplay/list.pyi +3 -3
  17. reflex/components/chakra/disclosure/accordion.py +1 -1
  18. reflex/components/chakra/forms/pininput.pyi +1 -1
  19. reflex/components/chakra/media/icon.py +2 -2
  20. reflex/components/component.py +279 -32
  21. reflex/components/core/__init__.py +2 -2
  22. reflex/components/core/cond.py +1 -10
  23. reflex/components/core/debounce.py +5 -2
  24. reflex/components/core/debounce.pyi +4 -2
  25. reflex/components/core/foreach.py +1 -16
  26. reflex/components/core/html.py +6 -0
  27. reflex/components/core/match.py +2 -17
  28. reflex/components/core/upload.py +42 -1
  29. reflex/components/core/upload.pyi +199 -1
  30. reflex/components/datadisplay/code.py +7 -3
  31. reflex/components/datadisplay/code.pyi +3 -1
  32. reflex/components/el/elements/forms.py +1 -1
  33. reflex/components/el/elements/forms.pyi +1 -1
  34. reflex/components/lucide/icon.py +5 -13
  35. reflex/components/lucide/icon.pyi +0 -1
  36. reflex/components/markdown/markdown.py +5 -23
  37. reflex/components/markdown/markdown.pyi +1 -4
  38. reflex/components/radix/primitives/accordion.py +227 -406
  39. reflex/components/radix/primitives/accordion.pyi +369 -28
  40. reflex/components/radix/primitives/form.py +33 -29
  41. reflex/components/radix/primitives/form.pyi +7 -2
  42. reflex/components/radix/primitives/progress.py +17 -9
  43. reflex/components/radix/primitives/progress.pyi +2 -0
  44. reflex/components/radix/primitives/slider.py +30 -18
  45. reflex/components/radix/primitives/slider.pyi +4 -0
  46. reflex/components/radix/themes/base.py +8 -1
  47. reflex/components/radix/themes/base.pyi +79 -1
  48. reflex/components/radix/themes/color_mode.py +74 -30
  49. reflex/components/radix/themes/color_mode.pyi +26 -185
  50. reflex/components/radix/themes/components/__init__.py +17 -0
  51. reflex/components/radix/themes/components/badge.py +2 -1
  52. reflex/components/radix/themes/components/badge.pyi +3 -1
  53. reflex/components/radix/themes/components/button.py +3 -1
  54. reflex/components/radix/themes/components/button.pyi +4 -1
  55. reflex/components/radix/themes/components/checkbox_cards.py +48 -0
  56. reflex/components/radix/themes/components/checkbox_cards.pyi +264 -0
  57. reflex/components/radix/themes/components/checkbox_group.py +42 -0
  58. reflex/components/radix/themes/components/checkbox_group.pyi +253 -0
  59. reflex/components/radix/themes/components/data_list.py +63 -0
  60. reflex/components/radix/themes/components/data_list.pyi +426 -0
  61. reflex/components/radix/themes/components/icon_button.py +20 -17
  62. reflex/components/radix/themes/components/icon_button.pyi +5 -1
  63. reflex/components/radix/themes/components/progress.py +55 -0
  64. reflex/components/radix/themes/components/progress.pyi +180 -0
  65. reflex/components/radix/themes/components/radio.py +31 -0
  66. reflex/components/radix/themes/components/radio.pyi +169 -0
  67. reflex/components/radix/themes/components/radio_cards.py +48 -0
  68. reflex/components/radix/themes/components/radio_cards.pyi +264 -0
  69. reflex/components/radix/themes/components/radio_group.py +2 -4
  70. reflex/components/radix/themes/components/segmented_control.py +48 -0
  71. reflex/components/radix/themes/components/segmented_control.pyi +262 -0
  72. reflex/components/radix/themes/components/skeleton.py +32 -0
  73. reflex/components/radix/themes/components/skeleton.pyi +106 -0
  74. reflex/components/radix/themes/components/spinner.py +26 -0
  75. reflex/components/radix/themes/components/spinner.pyi +101 -0
  76. reflex/components/radix/themes/components/tabs.py +26 -1
  77. reflex/components/radix/themes/components/tabs.pyi +69 -9
  78. reflex/components/radix/themes/components/text_field.py +101 -71
  79. reflex/components/radix/themes/components/text_field.pyi +81 -499
  80. reflex/components/radix/themes/layout/base.py +2 -2
  81. reflex/components/radix/themes/layout/base.pyi +4 -4
  82. reflex/components/radix/themes/layout/center.py +8 -3
  83. reflex/components/radix/themes/layout/center.pyi +2 -1
  84. reflex/components/radix/themes/layout/container.py +30 -2
  85. reflex/components/radix/themes/layout/container.pyi +9 -30
  86. reflex/components/radix/themes/layout/list.py +10 -5
  87. reflex/components/radix/themes/layout/list.pyi +5 -21
  88. reflex/components/radix/themes/layout/spacer.py +8 -3
  89. reflex/components/radix/themes/layout/spacer.pyi +2 -1
  90. reflex/components/radix/themes/layout/stack.py +7 -1
  91. reflex/components/radix/themes/layout/stack.pyi +3 -3
  92. reflex/components/radix/themes/typography/link.py +10 -2
  93. reflex/components/radix/themes/typography/link.pyi +5 -4
  94. reflex/components/sonner/__init__.py +3 -0
  95. reflex/components/sonner/toast.py +267 -0
  96. reflex/components/sonner/toast.pyi +205 -0
  97. reflex/components/tags/iter_tag.py +9 -6
  98. reflex/config.py +30 -54
  99. reflex/constants/__init__.py +0 -2
  100. reflex/constants/base.py +0 -5
  101. reflex/constants/colors.py +2 -0
  102. reflex/constants/installer.py +5 -1
  103. reflex/constants/route.py +4 -0
  104. reflex/custom_components/custom_components.py +22 -1
  105. reflex/event.py +75 -30
  106. reflex/experimental/__init__.py +5 -0
  107. reflex/experimental/layout.py +24 -6
  108. reflex/model.py +2 -1
  109. reflex/page.py +7 -4
  110. reflex/reflex.py +8 -3
  111. reflex/route.py +39 -0
  112. reflex/state.py +128 -131
  113. reflex/style.py +20 -1
  114. reflex/testing.py +10 -6
  115. reflex/utils/console.py +3 -1
  116. reflex/utils/exec.py +20 -7
  117. reflex/utils/format.py +1 -1
  118. reflex/utils/imports.py +3 -1
  119. reflex/utils/prerequisites.py +141 -20
  120. reflex/utils/processes.py +21 -1
  121. reflex/utils/pyi_generator.py +95 -5
  122. reflex/utils/serializers.py +1 -1
  123. reflex/utils/telemetry.py +26 -4
  124. reflex/utils/types.py +62 -18
  125. reflex/vars.py +11 -5
  126. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/METADATA +16 -4
  127. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/RECORD +130 -108
  128. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/WHEEL +1 -1
  129. reflex/app.pyi +0 -149
  130. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/LICENSE +0 -0
  131. {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/entry_points.txt +0 -0
reflex/state.py CHANGED
@@ -7,9 +7,7 @@ import contextlib
7
7
  import copy
8
8
  import functools
9
9
  import inspect
10
- import json
11
10
  import traceback
12
- import urllib.parse
13
11
  import uuid
14
12
  from abc import ABC, abstractmethod
15
13
  from collections import defaultdict
@@ -31,13 +29,7 @@ from typing import (
31
29
  import dill
32
30
 
33
31
  try:
34
- # TODO The type checking guard can be removed once
35
- # reflex-hosting-cli tools are compatible with pydantic v2
36
-
37
- if not TYPE_CHECKING:
38
- import pydantic.v1 as pydantic
39
- else:
40
- raise ModuleNotFoundError
32
+ import pydantic.v1 as pydantic
41
33
  except ModuleNotFoundError:
42
34
  import pydantic
43
35
 
@@ -47,6 +39,7 @@ from redis.asyncio import Redis
47
39
  from reflex import constants
48
40
  from reflex.base import Base
49
41
  from reflex.event import (
42
+ BACKGROUND_TASK_MARKER,
50
43
  Event,
51
44
  EventHandler,
52
45
  EventSpec,
@@ -247,6 +240,60 @@ def _split_substate_key(substate_key: str) -> tuple[str, str]:
247
240
  return token, state_name
248
241
 
249
242
 
243
+ class EventHandlerSetVar(EventHandler):
244
+ """A special event handler to wrap setvar functionality."""
245
+
246
+ state_cls: Type[BaseState]
247
+
248
+ def __init__(self, state_cls: Type[BaseState]):
249
+ """Initialize the EventHandlerSetVar.
250
+
251
+ Args:
252
+ state_cls: The state class that vars will be set on.
253
+ """
254
+ super().__init__(
255
+ fn=type(self).setvar,
256
+ state_full_name=state_cls.get_full_name(),
257
+ state_cls=state_cls, # type: ignore
258
+ )
259
+
260
+ def setvar(self, var_name: str, value: Any):
261
+ """Set the state variable to the value of the event.
262
+
263
+ Note: `self` here will be an instance of the state, not EventHandlerSetVar.
264
+
265
+ Args:
266
+ var_name: The name of the variable to set.
267
+ value: The value to set the variable to.
268
+ """
269
+ getattr(self, constants.SETTER_PREFIX + var_name)(value)
270
+
271
+ def __call__(self, *args: Any) -> EventSpec:
272
+ """Performs pre-checks and munging on the provided args that will become an EventSpec.
273
+
274
+ Args:
275
+ *args: The event args.
276
+
277
+ Returns:
278
+ The (partial) EventSpec that will be used to create the event to setvar.
279
+
280
+ Raises:
281
+ AttributeError: If the given Var name does not exist on the state.
282
+ ValueError: If the given Var name is not a str
283
+ """
284
+ if args:
285
+ if not isinstance(args[0], str):
286
+ raise ValueError(
287
+ f"Var name must be passed as a string, got {args[0]!r}"
288
+ )
289
+ # Check that the requested Var setter exists on the State at compile time.
290
+ if getattr(self.state_cls, constants.SETTER_PREFIX + args[0], None) is None:
291
+ raise AttributeError(
292
+ f"Variable `{args[0]}` cannot be set on `{self.state_cls.get_full_name()}`"
293
+ )
294
+ return super().__call__(*args)
295
+
296
+
250
297
  class BaseState(Base, ABC, extra=pydantic.Extra.allow):
251
298
  """The state of the app."""
252
299
 
@@ -310,6 +357,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
310
357
  # Whether the state has ever been touched since instantiation.
311
358
  _was_touched: bool = False
312
359
 
360
+ # A special event handler for setting base vars.
361
+ setvar: ClassVar[EventHandler]
362
+
313
363
  def __init__(
314
364
  self,
315
365
  *args,
@@ -393,6 +443,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
393
443
  super().__init_subclass__(**kwargs)
394
444
  # Event handlers should not shadow builtin state methods.
395
445
  cls._check_overridden_methods()
446
+ # Computed vars should not shadow builtin state props.
447
+ cls._check_overriden_basevars()
396
448
 
397
449
  # Reset subclass tracking for this class.
398
450
  cls.class_subclasses = set()
@@ -500,6 +552,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
500
552
  value.__qualname__ = f"{cls.__name__}.{name}"
501
553
  events[name] = value
502
554
 
555
+ # Create the setvar event handler for this state
556
+ cls._create_setvar()
557
+
503
558
  for name, fn in events.items():
504
559
  handler = cls._create_event_handler(fn)
505
560
  cls.event_handlers[name] = handler
@@ -525,6 +580,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
525
580
  closure=fn.__closure__,
526
581
  )
527
582
  newfn.__annotations__ = fn.__annotations__
583
+ if mark := getattr(fn, BACKGROUND_TASK_MARKER, None):
584
+ setattr(newfn, BACKGROUND_TASK_MARKER, mark)
528
585
  return newfn
529
586
 
530
587
  @staticmethod
@@ -636,6 +693,19 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
636
693
  f"The event handler name `{method_name}` shadows a builtin State method; use a different name instead"
637
694
  )
638
695
 
696
+ @classmethod
697
+ def _check_overriden_basevars(cls):
698
+ """Check for shadow base vars and raise error if any.
699
+
700
+ Raises:
701
+ NameError: When a computed var shadows a base var.
702
+ """
703
+ for computed_var_ in cls._get_computed_vars():
704
+ if computed_var_._var_name in cls.__annotations__:
705
+ raise NameError(
706
+ f"The computed var name `{computed_var_._var_name}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
707
+ )
708
+
639
709
  @classmethod
640
710
  def get_skip_vars(cls) -> set[str]:
641
711
  """Get the vars to skip when serializing.
@@ -806,7 +876,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
806
876
  cls.vars.update({name: var})
807
877
 
808
878
  # let substates know about the new variable
809
- for substate_class in cls.__subclasses__():
879
+ for substate_class in cls.class_subclasses:
810
880
  substate_class.vars.setdefault(name, var)
811
881
 
812
882
  # Reinitialize dependency tracking dicts.
@@ -833,6 +903,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
833
903
  """
834
904
  return EventHandler(fn=fn, state_full_name=cls.get_full_name())
835
905
 
906
+ @classmethod
907
+ def _create_setvar(cls):
908
+ """Create the setvar method for the state."""
909
+ cls.setvar = cls.event_handlers["setvar"] = EventHandlerSetVar(state_cls=cls)
910
+
836
911
  @classmethod
837
912
  def _create_setter(cls, prop: BaseVar):
838
913
  """Create a setter for the var.
@@ -881,124 +956,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
881
956
  if not func[0].startswith("__")
882
957
  }
883
958
 
884
- def get_token(self) -> str:
885
- """Return the token of the client associated with this state.
886
-
887
- Returns:
888
- The token of the client.
889
- """
890
- console.deprecate(
891
- feature_name="get_token",
892
- reason="replaced by `State.router.session.client_token`",
893
- deprecation_version="0.3.0",
894
- removal_version="0.5.0",
895
- )
896
- return self.router_data.get(constants.RouteVar.CLIENT_TOKEN, "")
897
-
898
- def get_sid(self) -> str:
899
- """Return the session ID of the client associated with this state.
900
-
901
- Returns:
902
- The session ID of the client.
903
- """
904
- console.deprecate(
905
- feature_name="get_sid",
906
- reason="replaced by `State.router.session.session_id`",
907
- deprecation_version="0.3.0",
908
- removal_version="0.5.0",
909
- )
910
- return self.router_data.get(constants.RouteVar.SESSION_ID, "")
911
-
912
- def get_headers(self) -> Dict:
913
- """Return the headers of the client associated with this state.
914
-
915
- Returns:
916
- The headers of the client.
917
- """
918
- console.deprecate(
919
- feature_name="get_headers",
920
- reason="replaced by `State.router.headers`",
921
- deprecation_version="0.3.0",
922
- removal_version="0.5.0",
923
- )
924
- return self.router_data.get(constants.RouteVar.HEADERS, {})
925
-
926
- def get_client_ip(self) -> str:
927
- """Return the IP of the client associated with this state.
928
-
929
- Returns:
930
- The IP of the client.
931
- """
932
- console.deprecate(
933
- feature_name="get_client_ip",
934
- reason="replaced by `State.router.session.client_ip`",
935
- deprecation_version="0.3.0",
936
- removal_version="0.5.0",
937
- )
938
- return self.router_data.get(constants.RouteVar.CLIENT_IP, "")
939
-
940
- def get_current_page(self, origin=False) -> str:
941
- """Obtain the path of current page from the router data.
942
-
943
- Args:
944
- origin: whether to return the base route as shown in browser
945
-
946
- Returns:
947
- The current page.
948
- """
949
- console.deprecate(
950
- feature_name="get_current_page",
951
- reason="replaced by State.router.page / self.router.page",
952
- deprecation_version="0.3.0",
953
- removal_version="0.5.0",
954
- )
955
-
956
- return self.router.page.raw_path if origin else self.router.page.path
957
-
958
- def get_query_params(self) -> dict[str, str]:
959
- """Obtain the query parameters for the queried page.
960
-
961
- The query object contains both the URI parameters and the GET parameters.
962
-
963
- Returns:
964
- The dict of query parameters.
965
- """
966
- console.deprecate(
967
- feature_name="get_query_params",
968
- reason="replaced by `State.router.page.params`",
969
- deprecation_version="0.3.0",
970
- removal_version="0.5.0",
971
- )
972
- return self.router_data.get(constants.RouteVar.QUERY, {})
973
-
974
- def get_cookies(self) -> dict[str, str]:
975
- """Obtain the cookies of the client stored in the browser.
976
-
977
- Returns:
978
- The dict of cookies.
979
- """
980
- console.deprecate(
981
- feature_name=f"rx.get_cookies",
982
- reason="and has been replaced by rx.Cookie, which can be used as a state var",
983
- deprecation_version="0.3.0",
984
- removal_version="0.5.0",
985
- )
986
- cookie_dict = {}
987
- cookies = self.get_headers().get(constants.RouteVar.COOKIE, "").split(";")
988
-
989
- cookie_pairs = [cookie.split("=") for cookie in cookies if cookie]
990
-
991
- for pair in cookie_pairs:
992
- key, value = pair[0].strip(), urllib.parse.unquote(pair[1].strip())
993
- try:
994
- # cast non-string values to the actual types.
995
- value = json.loads(value)
996
- except json.JSONDecodeError:
997
- pass
998
- finally:
999
- cookie_dict[key] = value
1000
- return cookie_dict
1001
-
1002
959
  @classmethod
1003
960
  def setup_dynamic_args(cls, args: dict[str, str]):
1004
961
  """Set up args for easy access in renderer.
@@ -1800,6 +1757,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1800
1757
  return state
1801
1758
 
1802
1759
 
1760
+ EventHandlerSetVar.update_forward_refs()
1761
+
1762
+
1803
1763
  class State(BaseState):
1804
1764
  """The app Base State."""
1805
1765
 
@@ -1841,7 +1801,7 @@ class OnLoadInternalState(State):
1841
1801
  Returns:
1842
1802
  The list of events to queue for on load handling.
1843
1803
  """
1844
- # Do not app.compile_()! It should be already compiled by now.
1804
+ # Do not app._compile()! It should be already compiled by now.
1845
1805
  app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
1846
1806
  load_events = app.get_load_events(self.router.page.path)
1847
1807
  if not load_events:
@@ -1859,8 +1819,45 @@ class OnLoadInternalState(State):
1859
1819
 
1860
1820
 
1861
1821
  class ComponentState(Base):
1862
- """The base class for a State that is copied for each Component associated with it."""
1822
+ """Base class to allow for the creation of a state instance per component.
1823
+
1824
+ This allows for the bundling of UI and state logic into a single class,
1825
+ where each instance has a separate instance of the state.
1826
+
1827
+ Subclass this class and define vars and event handlers in the traditional way.
1828
+ Then define a `get_component` method that returns the UI for the component instance.
1829
+
1830
+ See the full [docs](https://reflex.dev/docs/substates/component-state/) for more.
1831
+
1832
+ Basic example:
1833
+ ```python
1834
+ # Subclass ComponentState and define vars and event handlers.
1835
+ class Counter(rx.ComponentState):
1836
+ # Define vars that change.
1837
+ count: int = 0
1838
+
1839
+ # Define event handlers.
1840
+ def increment(self):
1841
+ self.count += 1
1842
+
1843
+ def decrement(self):
1844
+ self.count -= 1
1845
+
1846
+ @classmethod
1847
+ def get_component(cls, **props):
1848
+ # Access the state vars and event handlers using `cls`.
1849
+ return rx.hstack(
1850
+ rx.button("Decrement", on_click=cls.decrement),
1851
+ rx.text(cls.count),
1852
+ rx.button("Increment", on_click=cls.increment),
1853
+ **props,
1854
+ )
1855
+
1856
+ counter = Counter.create()
1857
+ ```
1858
+ """
1863
1859
 
1860
+ # The number of components created from this class.
1864
1861
  _per_component_state_instance_count: ClassVar[int] = 0
1865
1862
 
1866
1863
  @classmethod
@@ -2651,7 +2648,7 @@ class StateManagerRedis(StateManager):
2651
2648
 
2652
2649
  Note: Connections will be automatically reopened when needed.
2653
2650
  """
2654
- await self.redis.close(close_connection_pool=True)
2651
+ await self.redis.aclose(close_connection_pool=True)
2655
2652
 
2656
2653
 
2657
2654
  def get_state_manager() -> StateManager:
reflex/style.py CHANGED
@@ -159,12 +159,17 @@ def format_style_key(key: str) -> Tuple[str, ...]:
159
159
  class Style(dict):
160
160
  """A style dictionary."""
161
161
 
162
- def __init__(self, style_dict: dict | None = None):
162
+ def __init__(self, style_dict: dict | None = None, **kwargs):
163
163
  """Initialize the style.
164
164
 
165
165
  Args:
166
166
  style_dict: The style dictionary.
167
+ kwargs: Other key value pairs to apply to the dict update.
167
168
  """
169
+ if style_dict:
170
+ style_dict.update(kwargs)
171
+ else:
172
+ style_dict = kwargs
168
173
  style_dict, self._var_data = convert(style_dict or {})
169
174
  super().__init__(style_dict)
170
175
 
@@ -274,3 +279,17 @@ def convert_dict_to_style_and_format_emotion(
274
279
 
275
280
  """
276
281
  return format_as_emotion(Style(raw_dict))
282
+
283
+
284
+ STACK_CHILDREN_FULL_WIDTH = {
285
+ "& :where(.rx-Stack)": {
286
+ "width": "100%",
287
+ },
288
+ "& :where(.rx-Stack) > :where( "
289
+ "div:not(.rt-Box, .rx-Upload, .rx-Html),"
290
+ "input, select, textarea, table"
291
+ ")": {
292
+ "width": "100%",
293
+ "flex_shrink": "1",
294
+ },
295
+ }
reflex/testing.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """reflex.testing - tools for testing reflex apps."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -42,7 +43,6 @@ import reflex.utils.prerequisites
42
43
  import reflex.utils.processes
43
44
  from reflex.state import (
44
45
  BaseState,
45
- State,
46
46
  StateManagerMemory,
47
47
  StateManagerRedis,
48
48
  reload_state_module,
@@ -167,6 +167,8 @@ class AppHarness:
167
167
  app_name = app_source.__name__
168
168
 
169
169
  app_name = app_name.lower()
170
+ while "__" in app_name:
171
+ app_name = app_name.replace("__", "_")
170
172
  return cls(
171
173
  app_name=app_name,
172
174
  app_source=app_source,
@@ -240,13 +242,15 @@ class AppHarness:
240
242
  # ensure config and app are reloaded when testing different app
241
243
  reflex.config.get_config(reload=True)
242
244
  # Save decorated pages before importing the test app module
243
- before_decorated_pages = reflex.app.DECORATED_PAGES.copy()
245
+ before_decorated_pages = reflex.app.DECORATED_PAGES[self.app_name].copy()
244
246
  # Ensure the AppHarness test does not skip State assignment due to running via pytest
245
247
  os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
246
248
  self.app_module = reflex.utils.prerequisites.get_compiled_app(reload=True)
247
249
  # Save the pages that were added during testing
248
250
  self._decorated_pages = [
249
- p for p in reflex.app.DECORATED_PAGES if p not in before_decorated_pages
251
+ p
252
+ for p in reflex.app.DECORATED_PAGES[self.app_name]
253
+ if p not in before_decorated_pages
250
254
  ]
251
255
  self.app_instance = self.app_module.app
252
256
  if isinstance(self.app_instance._state_manager, StateManagerRedis):
@@ -411,7 +415,7 @@ class AppHarness:
411
415
 
412
416
  # Cleanup decorated pages added during testing
413
417
  for page in self._decorated_pages:
414
- reflex.app.DECORATED_PAGES.remove(page)
418
+ reflex.app.DECORATED_PAGES[self.app_name].remove(page)
415
419
 
416
420
  def __exit__(self, *excinfo) -> None:
417
421
  """Contextmanager protocol for `stop()`.
@@ -536,7 +540,7 @@ class AppHarness:
536
540
  if driver_clz is None:
537
541
  requested_driver = os.environ.get("APP_HARNESS_DRIVER", "Chrome")
538
542
  driver_clz = getattr(webdriver, requested_driver)
539
- options = webdriver.ChromeOptions()
543
+ options = getattr(webdriver, f"{requested_driver}Options")()
540
544
  if driver_clz is webdriver.Chrome and want_headless:
541
545
  options = webdriver.ChromeOptions()
542
546
  options.add_argument("--headless=new")
@@ -596,7 +600,7 @@ class AppHarness:
596
600
  await self.state_manager.close()
597
601
 
598
602
  @contextlib.asynccontextmanager
599
- async def modify_state(self, token: str) -> AsyncIterator[State]:
603
+ async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
600
604
  """Modify the state associated with the given token and send update to frontend.
601
605
 
602
606
  Args:
reflex/utils/console.py CHANGED
@@ -174,7 +174,9 @@ def ask(
174
174
  Returns:
175
175
  A string with the user input.
176
176
  """
177
- return Prompt.ask(question, choices=choices, default=default, show_choices=show_choices) # type: ignore
177
+ return Prompt.ask(
178
+ question, choices=choices, default=default, show_choices=show_choices
179
+ ) # type: ignore
178
180
 
179
181
 
180
182
  def progress():
reflex/utils/exec.py CHANGED
@@ -73,11 +73,12 @@ def kill(proc_pid: int):
73
73
  # run_process_and_launch_url is assumed to be used
74
74
  # only to launch the frontend
75
75
  # If this is not the case, might have to change the logic
76
- def run_process_and_launch_url(run_command: list[str]):
76
+ def run_process_and_launch_url(run_command: list[str], backend_present=True):
77
77
  """Run the process and launch the URL.
78
78
 
79
79
  Args:
80
80
  run_command: The command to run.
81
+ backend_present: Whether the backend is present.
81
82
  """
82
83
  from reflex.utils import processes
83
84
 
@@ -89,7 +90,7 @@ def run_process_and_launch_url(run_command: list[str]):
89
90
  while True:
90
91
  if process is None:
91
92
  kwargs = {}
92
- if constants.IS_WINDOWS:
93
+ if constants.IS_WINDOWS and backend_present:
93
94
  kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
94
95
  process = processes.new_process(
95
96
  run_command,
@@ -122,12 +123,13 @@ def run_process_and_launch_url(run_command: list[str]):
122
123
  break # while True
123
124
 
124
125
 
125
- def run_frontend(root: Path, port: str):
126
+ def run_frontend(root: Path, port: str, backend_present=True):
126
127
  """Run the frontend.
127
128
 
128
129
  Args:
129
130
  root: The root path of the project.
130
131
  port: The port to run the frontend on.
132
+ backend_present: Whether the backend is present.
131
133
  """
132
134
  from reflex.utils import prerequisites
133
135
 
@@ -139,15 +141,19 @@ def run_frontend(root: Path, port: str):
139
141
  # Run the frontend in development mode.
140
142
  console.rule("[bold green]App Running")
141
143
  os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
142
- run_process_and_launch_url([prerequisites.get_package_manager(), "run", "dev"]) # type: ignore
144
+ run_process_and_launch_url(
145
+ [prerequisites.get_package_manager(), "run", "dev"], # type: ignore
146
+ backend_present,
147
+ )
143
148
 
144
149
 
145
- def run_frontend_prod(root: Path, port: str):
150
+ def run_frontend_prod(root: Path, port: str, backend_present=True):
146
151
  """Run the frontend.
147
152
 
148
153
  Args:
149
154
  root: The root path of the project (to keep same API as run_frontend).
150
155
  port: The port to run the frontend on.
156
+ backend_present: Whether the backend is present.
151
157
  """
152
158
  from reflex.utils import prerequisites
153
159
 
@@ -157,7 +163,10 @@ def run_frontend_prod(root: Path, port: str):
157
163
  prerequisites.validate_frontend_dependencies(init=False)
158
164
  # Run the frontend in production mode.
159
165
  console.rule("[bold green]App Running")
160
- run_process_and_launch_url([prerequisites.get_package_manager(), "run", "prod"]) # type: ignore
166
+ run_process_and_launch_url(
167
+ [prerequisites.get_package_manager(), "run", "prod"], # type: ignore
168
+ backend_present,
169
+ )
161
170
 
162
171
 
163
172
  def run_backend(
@@ -271,7 +280,11 @@ def output_system_info():
271
280
 
272
281
  system = platform.system()
273
282
 
274
- if system != "Windows":
283
+ if (
284
+ system != "Windows"
285
+ or system == "Windows"
286
+ and prerequisites.is_windows_bun_supported()
287
+ ):
275
288
  dependencies.extend(
276
289
  [
277
290
  f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]",
reflex/utils/format.py CHANGED
@@ -330,7 +330,7 @@ def format_cond(
330
330
  cond = f"isTrue({cond})"
331
331
 
332
332
  def create_var(cond_part):
333
- return Var.create_safe(cond_part, _var_is_string=type(cond_part) is str)
333
+ return Var.create_safe(cond_part, _var_is_string=isinstance(cond_part, str))
334
334
 
335
335
  # Format prop conds.
336
336
  if is_prop:
reflex/utils/imports.py CHANGED
@@ -66,7 +66,9 @@ class ImportVar(Base):
66
66
  The name(tag name with alias) of tag.
67
67
  """
68
68
  if self.alias:
69
- return self.alias if self.is_default else " as ".join([self.tag, self.alias]) # type: ignore
69
+ return (
70
+ self.alias if self.is_default else " as ".join([self.tag, self.alias]) # type: ignore
71
+ )
70
72
  else:
71
73
  return self.tag or ""
72
74