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
@@ -35,7 +35,7 @@ class Bun(SimpleNamespace):
35
35
  """Bun constants."""
36
36
 
37
37
  # The Bun version.
38
- VERSION = "1.1.3"
38
+ VERSION = "1.1.6"
39
39
  # Min Bun Version
40
40
  MIN_VERSION = "0.7.0"
41
41
  # The directory to store the bun.
@@ -46,6 +46,10 @@ class Bun(SimpleNamespace):
46
46
  )
47
47
  # URL to bun install script.
48
48
  INSTALL_URL = "https://bun.sh/install"
49
+ # URL to windows install script.
50
+ WINDOWS_INSTALL_URL = (
51
+ "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/install.ps1"
52
+ )
49
53
 
50
54
 
51
55
  # FNM config.
reflex/constants/route.py CHANGED
@@ -44,6 +44,10 @@ class RouteRegex(SimpleNamespace):
44
44
  STRICT_CATCHALL = re.compile(r"\[\.{3}([a-zA-Z_][\w]*)\]")
45
45
  # group return the arg name (i.e. "slug") (optional arg can be empty)
46
46
  OPT_CATCHALL = re.compile(r"\[\[\.{3}([a-zA-Z_][\w]*)\]\]")
47
+ SINGLE_SEGMENT = "__SINGLE_SEGMENT__"
48
+ DOUBLE_SEGMENT = "__DOUBLE_SEGMENT__"
49
+ SINGLE_CATCHALL_SEGMENT = "__SINGLE_CATCHALL_SEGMENT__"
50
+ DOUBLE_CATCHALL_SEGMENT = "__DOUBLE_CATCHALL_SEGMENT__"
47
51
 
48
52
 
49
53
  class DefaultPage(SimpleNamespace):
@@ -555,7 +555,6 @@ def _ensure_dist_dir(version_to_publish: str, build: bool):
555
555
  if build:
556
556
  # Need to check if the files here are for the version to be published.
557
557
  if dist_dir.exists():
558
-
559
558
  # Check if the distribution files are for the version to be published.
560
559
  needs_rebuild = False
561
560
  for suffix in CustomComponents.DISTRIBUTION_FILE_SUFFIXES:
@@ -936,3 +935,25 @@ def share_more_detail(
936
935
  console.set_log_level(loglevel)
937
936
 
938
937
  _collect_details_for_gallery()
938
+
939
+
940
+ @custom_components_cli.command()
941
+ def install(
942
+ loglevel: constants.LogLevel = typer.Option(
943
+ config.loglevel, help="The log level to use."
944
+ ),
945
+ ):
946
+ """Install package from this local custom component in editable mode.
947
+
948
+ Args:
949
+ loglevel: The log level to use.
950
+
951
+ Raises:
952
+ Exit: If unable to install the current directory in editable mode.
953
+ """
954
+ console.set_log_level(loglevel)
955
+
956
+ if _pip_install_on_demand(package_name=".", install_args=["-e"]):
957
+ console.info(f"Package installed successfully!")
958
+ else:
959
+ raise typer.Exit(code=1)
reflex/event.py CHANGED
@@ -18,7 +18,7 @@ from typing import (
18
18
 
19
19
  from reflex import constants
20
20
  from reflex.base import Base
21
- from reflex.utils import console, format
21
+ from reflex.utils import format
22
22
  from reflex.utils.types import ArgsSpec
23
23
  from reflex.vars import BaseVar, Var
24
24
 
@@ -80,7 +80,7 @@ class EventActionsMixin(Base):
80
80
  """Mixin for DOM event actions."""
81
81
 
82
82
  # Whether to `preventDefault` or `stopPropagation` on the event.
83
- event_actions: Dict[str, bool] = {}
83
+ event_actions: Dict[str, Union[bool, int]] = {}
84
84
 
85
85
  @property
86
86
  def stop_propagation(self):
@@ -104,6 +104,32 @@ class EventActionsMixin(Base):
104
104
  update={"event_actions": {"preventDefault": True, **self.event_actions}},
105
105
  )
106
106
 
107
+ def throttle(self, limit_ms: int):
108
+ """Throttle the event handler.
109
+
110
+ Args:
111
+ limit_ms: The time in milliseconds to throttle the event handler.
112
+
113
+ Returns:
114
+ New EventHandler-like with throttle set to limit_ms.
115
+ """
116
+ return self.copy(
117
+ update={"event_actions": {"throttle": limit_ms, **self.event_actions}},
118
+ )
119
+
120
+ def debounce(self, delay_ms: int):
121
+ """Debounce the event handler.
122
+
123
+ Args:
124
+ delay_ms: The time in milliseconds to debounce the event handler.
125
+
126
+ Returns:
127
+ New EventHandler-like with debounce set to delay_ms.
128
+ """
129
+ return self.copy(
130
+ update={"event_actions": {"debounce": delay_ms, **self.event_actions}},
131
+ )
132
+
107
133
 
108
134
  class EventHandler(EventActionsMixin):
109
135
  """An event handler responds to an event to update the state."""
@@ -142,7 +168,7 @@ class EventHandler(EventActionsMixin):
142
168
  """
143
169
  return getattr(self.fn, BACKGROUND_TASK_MARKER, False)
144
170
 
145
- def __call__(self, *args: Var) -> EventSpec:
171
+ def __call__(self, *args: Any) -> EventSpec:
146
172
  """Pass arguments to the handler to get an event spec.
147
173
 
148
174
  This method configures event handlers that take in arguments.
@@ -169,7 +195,7 @@ class EventHandler(EventActionsMixin):
169
195
 
170
196
  # Otherwise, convert to JSON.
171
197
  try:
172
- values.append(Var.create(arg, _var_is_string=type(arg) is str))
198
+ values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
173
199
  except TypeError as e:
174
200
  raise TypeError(
175
201
  f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
@@ -220,6 +246,34 @@ class EventSpec(EventActionsMixin):
220
246
  event_actions=self.event_actions.copy(),
221
247
  )
222
248
 
249
+ def add_args(self, *args: Var) -> EventSpec:
250
+ """Add arguments to the event spec.
251
+
252
+ Args:
253
+ *args: The arguments to add positionally.
254
+
255
+ Returns:
256
+ The event spec with the new arguments.
257
+
258
+ Raises:
259
+ TypeError: If the arguments are invalid.
260
+ """
261
+ # Get the remaining unfilled function args.
262
+ fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
263
+ fn_args = (Var.create_safe(arg) for arg in fn_args)
264
+
265
+ # Construct the payload.
266
+ values = []
267
+ for arg in args:
268
+ try:
269
+ values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
270
+ except TypeError as e:
271
+ raise TypeError(
272
+ f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
273
+ ) from e
274
+ new_payload = tuple(zip(fn_args, values))
275
+ return self.with_args(self.args + new_payload)
276
+
223
277
 
224
278
  class CallableEventSpec(EventSpec):
225
279
  """Decorate an EventSpec-returning function to act as both a EventSpec and a function.
@@ -350,7 +404,9 @@ class FileUpload(Base):
350
404
  ]
351
405
  elif isinstance(on_upload_progress, Callable):
352
406
  # Call the lambda to get the event chain.
353
- events = call_event_fn(on_upload_progress, self.on_upload_progress_args_spec) # type: ignore
407
+ events = call_event_fn(
408
+ on_upload_progress, self.on_upload_progress_args_spec
409
+ ) # type: ignore
354
410
  else:
355
411
  raise ValueError(f"{on_upload_progress} is not a valid event handler.")
356
412
  on_upload_progress_chain = EventChain(
@@ -400,7 +456,7 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
400
456
  return EventSpec(
401
457
  handler=EventHandler(fn=fn),
402
458
  args=tuple(
403
- (Var.create_safe(k), Var.create_safe(v, _var_is_string=type(v) is str))
459
+ (Var.create_safe(k), Var.create_safe(v, _var_is_string=isinstance(v, str)))
404
460
  for k, v in kwargs.items()
405
461
  ),
406
462
  )
@@ -704,7 +760,8 @@ def get_hydrate_event(state) -> str:
704
760
 
705
761
 
706
762
  def call_event_handler(
707
- event_handler: EventHandler, arg_spec: Union[Var, ArgsSpec]
763
+ event_handler: EventHandler | EventSpec,
764
+ arg_spec: ArgsSpec,
708
765
  ) -> EventSpec:
709
766
  """Call an event handler to get the event spec.
710
767
 
@@ -722,33 +779,21 @@ def call_event_handler(
722
779
  Returns:
723
780
  The event spec from calling the event handler.
724
781
  """
725
- args = inspect.getfullargspec(event_handler.fn).args
782
+ parsed_args = parse_args_spec(arg_spec) # type: ignore
726
783
 
727
- # handle new API using lambda to define triggers
728
- if isinstance(arg_spec, ArgsSpec):
729
- parsed_args = parse_args_spec(arg_spec) # type: ignore
784
+ if isinstance(event_handler, EventSpec):
785
+ # Handle partial application of EventSpec args
786
+ return event_handler.add_args(*parsed_args)
730
787
 
731
- if len(args) == len(["self", *parsed_args]):
732
- return event_handler(*parsed_args) # type: ignore
733
- else:
734
- source = inspect.getsource(arg_spec) # type: ignore
735
- raise ValueError(
736
- f"number of arguments in {event_handler.fn.__qualname__} "
737
- f"doesn't match the definition of the event trigger '{source.strip().strip(',')}'"
738
- )
788
+ args = inspect.getfullargspec(event_handler.fn).args
789
+ if len(args) == len(["self", *parsed_args]):
790
+ return event_handler(*parsed_args) # type: ignore
739
791
  else:
740
- console.deprecate(
741
- feature_name="EVENT_ARG API for triggers",
742
- reason="Replaced by new API using lambda allow arbitrary number of args",
743
- deprecation_version="0.2.8",
744
- removal_version="0.5.0",
792
+ source = inspect.getsource(arg_spec) # type: ignore
793
+ raise ValueError(
794
+ f"number of arguments in {event_handler.fn.__qualname__} "
795
+ f"doesn't match the definition of the event trigger '{source.strip().strip(',')}'"
745
796
  )
746
- if len(args) == 1:
747
- return event_handler()
748
- assert (
749
- len(args) == 2
750
- ), f"Event handler {event_handler.fn} must have 1 or 2 arguments."
751
- return event_handler(arg_spec) # type: ignore
752
797
 
753
798
 
754
799
  def parse_args_spec(arg_spec: ArgsSpec):
@@ -2,6 +2,9 @@
2
2
 
3
3
  from types import SimpleNamespace
4
4
 
5
+ from reflex.components.radix.themes.components.progress import progress as progress
6
+ from reflex.components.sonner.toast import toast as toast
7
+
5
8
  from ..utils.console import warn
6
9
  from . import hooks as hooks
7
10
  from .layout import layout as layout
@@ -14,5 +17,7 @@ warn(
14
17
  _x = SimpleNamespace(
15
18
  hooks=hooks,
16
19
  layout=layout,
20
+ progress=progress,
17
21
  run_in_thread=run_in_thread,
22
+ toast=toast,
18
23
  )
@@ -1,5 +1,7 @@
1
1
  """To experiment with layout component, move them to reflex/components later."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from reflex import color, cond
4
6
  from reflex.components.base.fragment import Fragment
5
7
  from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
@@ -9,6 +11,7 @@ from reflex.components.radix.themes.layout import Box, Container, HStack
9
11
  from reflex.event import call_script
10
12
  from reflex.experimental import hooks
11
13
  from reflex.state import ComponentState
14
+ from reflex.style import Style
12
15
  from reflex.vars import Var
13
16
 
14
17
 
@@ -26,23 +29,38 @@ class Sidebar(Box, MemoizationLeaf):
26
29
  Returns:
27
30
  The sidebar component.
28
31
  """
29
- props.setdefault("border_right", f"1px solid {color('accent', 12)}")
30
- props.setdefault("background_color", color("accent", 1))
31
- props.setdefault("width", "20vw")
32
- props.setdefault("height", "100vh")
33
- props.setdefault("position", "fixed")
32
+ # props.setdefault("border_right", f"1px solid {color('accent', 12)}")
33
+ # props.setdefault("background_color", color("accent", 1))
34
+ # props.setdefault("width", "20vw")
35
+ # props.setdefault("height", "100vh")
36
+ # props.setdefault("position", "fixed")
34
37
 
35
38
  return super().create(
36
39
  Box.create(*children, **props), # sidebar for content
37
40
  Box.create(width=props.get("width")), # spacer for layout
38
41
  )
39
42
 
40
- def _apply_theme(self, theme: Component | None):
43
+ def add_style(self) -> Style | None:
44
+ """Add style to the component.
45
+
46
+ Returns:
47
+ The style of the component.
48
+ """
41
49
  sidebar: Component = self.children[-2] # type: ignore
42
50
  spacer: Component = self.children[-1] # type: ignore
43
51
  open = self.State.open if self.State else Var.create("open") # type: ignore
44
52
  sidebar.style["display"] = spacer.style["display"] = cond(open, "block", "none")
45
53
 
54
+ return Style(
55
+ {
56
+ "position": "fixed",
57
+ "border_right": f"1px solid {color('accent', 12)}",
58
+ "background_color": color("accent", 1),
59
+ "width": "20vw",
60
+ "height": "100vh",
61
+ }
62
+ )
63
+
46
64
  def _get_hooks(self) -> Var | None:
47
65
  return hooks.useState("open", "true") if not self.State else None
48
66
 
reflex/model.py CHANGED
@@ -133,7 +133,7 @@ class ModelRegistry:
133
133
  return metadata
134
134
 
135
135
 
136
- class Model(Base, sqlmodel.SQLModel):
136
+ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssues]
137
137
  """Base class to define a table in the database."""
138
138
 
139
139
  # The primary key for the table.
@@ -310,6 +310,7 @@ class Model(Base, sqlmodel.SQLModel):
310
310
  render_item=cls._alembic_render_item,
311
311
  process_revision_directives=writer, # type: ignore
312
312
  compare_type=False,
313
+ render_as_batch=True, # for sqlite compatibility
313
314
  )
314
315
  env.run_migrations()
315
316
  changes_detected = False
reflex/page.py CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any
5
+ from collections import defaultdict
6
+ from typing import Any, Dict, List
6
7
 
7
- DECORATED_PAGES = []
8
+ from reflex.config import get_config
9
+
10
+ DECORATED_PAGES: Dict[str, List] = defaultdict(list)
8
11
 
9
12
 
10
13
  def page(
@@ -55,7 +58,7 @@ def page(
55
58
  if on_load:
56
59
  kwargs["on_load"] = on_load
57
60
 
58
- DECORATED_PAGES.append((render_fn, kwargs))
61
+ DECORATED_PAGES[get_config().app_name].append((render_fn, kwargs))
59
62
 
60
63
  return render_fn
61
64
 
@@ -69,6 +72,6 @@ def get_decorated_pages() -> list[dict]:
69
72
  The decorated pages.
70
73
  """
71
74
  return sorted(
72
- [page_data for render_fn, page_data in DECORATED_PAGES],
75
+ [page_data for _, page_data in DECORATED_PAGES[get_config().app_name]],
73
76
  key=lambda x: x["route"],
74
77
  )
reflex/reflex.py CHANGED
@@ -157,7 +157,7 @@ def _run(
157
157
  if prerequisites.needs_reinit(frontend=frontend):
158
158
  _init(name=config.app_name, loglevel=loglevel)
159
159
 
160
- # If something is running on the ports, ask the user if they want to kill or change it.
160
+ # Find the next available open port.
161
161
  if frontend and processes.is_process_on_port(frontend_port):
162
162
  frontend_port = processes.change_port(frontend_port, "frontend")
163
163
 
@@ -212,7 +212,7 @@ def _run(
212
212
  # Run the frontend on a separate thread.
213
213
  if frontend:
214
214
  setup_frontend(Path.cwd())
215
- commands.append((frontend_cmd, Path.cwd(), frontend_port))
215
+ commands.append((frontend_cmd, Path.cwd(), frontend_port, backend))
216
216
 
217
217
  # In prod mode, run the backend on a separate thread.
218
218
  if backend and env == constants.Env.PROD:
@@ -528,7 +528,12 @@ def deploy(
528
528
 
529
529
  hosting_cli.deploy(
530
530
  app_name=app_name,
531
- export_fn=lambda zip_dest_dir, api_url, deploy_url, frontend, backend, zipping: export_utils.export(
531
+ export_fn=lambda zip_dest_dir,
532
+ api_url,
533
+ deploy_url,
534
+ frontend,
535
+ backend,
536
+ zipping: export_utils.export(
532
537
  zip_dest_dir=zip_dest_dir,
533
538
  api_url=api_url,
534
539
  deploy_url=deploy_url,
reflex/route.py CHANGED
@@ -101,3 +101,42 @@ def catchall_prefix(route: str) -> str:
101
101
  """
102
102
  pattern = catchall_in_route(route)
103
103
  return route.replace(pattern, "") if pattern else ""
104
+
105
+
106
+ def replace_brackets_with_keywords(input_string):
107
+ """Replace brackets and everything inside it in a string with a keyword.
108
+
109
+ Args:
110
+ input_string: String to replace.
111
+
112
+ Returns:
113
+ new string containing keywords.
114
+ """
115
+ # /posts -> /post
116
+ # /posts/[slug] -> /posts/__SINGLE_SEGMENT__
117
+ # /posts/[slug]/comments -> /posts/__SINGLE_SEGMENT__/comments
118
+ # /posts/[[slug]] -> /posts/__DOUBLE_SEGMENT__
119
+ # / posts/[[...slug2]]-> /posts/__DOUBLE_CATCHALL_SEGMENT__
120
+ # /posts/[...slug3]-> /posts/__SINGLE_CATCHALL_SEGMENT__
121
+
122
+ # Replace [[...<slug>]] with __DOUBLE_CATCHALL_SEGMENT__
123
+ output_string = re.sub(
124
+ r"\[\[\.\.\..+?\]\]",
125
+ constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
126
+ input_string,
127
+ )
128
+ # Replace [...<slug>] with __SINGLE_CATCHALL_SEGMENT__
129
+ output_string = re.sub(
130
+ r"\[\.\.\..+?\]",
131
+ constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
132
+ output_string,
133
+ )
134
+ # Replace [[<slug>]] with __DOUBLE_SEGMENT__
135
+ output_string = re.sub(
136
+ r"\[\[.+?\]\]", constants.RouteRegex.DOUBLE_SEGMENT, output_string
137
+ )
138
+ # Replace [<slug>] with __SINGLE_SEGMENT__
139
+ output_string = re.sub(
140
+ r"\[.+?\]", constants.RouteRegex.SINGLE_SEGMENT, output_string
141
+ )
142
+ return output_string