reflex 0.7.3a2__py3-none-any.whl → 0.7.4a1__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.

reflex/app.py CHANGED
@@ -576,6 +576,22 @@ class App(MiddlewareMixin, LifespanMixin):
576
576
  """
577
577
  if not self.api:
578
578
  raise ValueError("The app has not been initialized.")
579
+
580
+ # For py3.9 compatibility when redis is used, we MUST add any decorator pages
581
+ # before compiling the app in a thread to avoid event loop error (REF-2172).
582
+ self._apply_decorated_pages()
583
+
584
+ compile_future = concurrent.futures.ThreadPoolExecutor(max_workers=1).submit(
585
+ self._compile
586
+ )
587
+ compile_future.add_done_callback(
588
+ # Force background compile errors to print eagerly
589
+ lambda f: f.result()
590
+ )
591
+ # Wait for the compile to finish in prod mode to ensure all optional endpoints are mounted.
592
+ if is_prod_mode():
593
+ compile_future.result()
594
+
579
595
  return self.api
580
596
 
581
597
  def _add_default_endpoints(self):
@@ -1063,7 +1079,7 @@ class App(MiddlewareMixin, LifespanMixin):
1063
1079
  with stateful_pages_marker.open("r") as f:
1064
1080
  stateful_pages = json.load(f)
1065
1081
  for route in stateful_pages:
1066
- console.info(f"BE Evaluating stateful page: {route}")
1082
+ console.debug(f"BE Evaluating stateful page: {route}")
1067
1083
  self._compile_page(route, save_page=False)
1068
1084
  self._enable_state()
1069
1085
  self._add_optional_endpoints()
@@ -19,6 +19,7 @@ from reflex.components.component import (
19
19
  from reflex.config import environment, get_config
20
20
  from reflex.state import BaseState
21
21
  from reflex.style import SYSTEM_COLOR_MODE
22
+ from reflex.utils import console, path_ops
22
23
  from reflex.utils.exec import is_prod_mode
23
24
  from reflex.utils.imports import ImportVar
24
25
  from reflex.utils.prerequisites import get_web_dir
@@ -190,18 +191,74 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
190
191
  if get_config().tailwind is not None
191
192
  else []
192
193
  )
194
+
195
+ failed_to_import_sass = False
193
196
  for stylesheet in stylesheets:
194
197
  if not utils.is_valid_url(stylesheet):
195
198
  # check if stylesheet provided exists.
196
- stylesheet_full_path = (
197
- Path.cwd() / constants.Dirs.APP_ASSETS / stylesheet.strip("/")
198
- )
199
+ assets_app_path = Path.cwd() / constants.Dirs.APP_ASSETS
200
+ stylesheet_full_path = assets_app_path / stylesheet.strip("/")
201
+
199
202
  if not stylesheet_full_path.exists():
200
203
  raise FileNotFoundError(
201
204
  f"The stylesheet file {stylesheet_full_path} does not exist."
202
205
  )
203
- stylesheet = f"../{constants.Dirs.PUBLIC}/{stylesheet.strip('/')}"
206
+
207
+ if stylesheet_full_path.is_dir():
208
+ # NOTE: this can create an infinite loop, for example:
209
+ # assets/
210
+ # | dir_a/
211
+ # | | dir_c/ (symlink to "assets/dir_a")
212
+ # | dir_b/
213
+ # so to avoid the infinite loop, we don't include symbolic links
214
+ stylesheets += [
215
+ str(p.relative_to(assets_app_path))
216
+ for p in stylesheet_full_path.iterdir()
217
+ if not (p.is_symlink() and p.is_dir())
218
+ ]
219
+ continue
220
+
221
+ if (
222
+ stylesheet_full_path.suffix[1:].lower()
223
+ in constants.Reflex.STYLESHEETS_SUPPORTED
224
+ ):
225
+ target = (
226
+ Path.cwd()
227
+ / constants.Dirs.WEB
228
+ / constants.Dirs.STYLES
229
+ / (stylesheet.rsplit(".", 1)[0].strip("/") + ".css")
230
+ )
231
+ target.parent.mkdir(parents=True, exist_ok=True)
232
+
233
+ if stylesheet_full_path.suffix == ".css":
234
+ path_ops.cp(src=stylesheet_full_path, dest=target, overwrite=True)
235
+ else:
236
+ try:
237
+ from sass import compile as sass_compile
238
+
239
+ target.write_text(
240
+ data=sass_compile(
241
+ filename=str(stylesheet_full_path),
242
+ output_style="compressed",
243
+ ),
244
+ encoding="utf8",
245
+ )
246
+ except ImportError:
247
+ failed_to_import_sass = True
248
+ else:
249
+ raise FileNotFoundError(
250
+ f'The stylesheet file "{stylesheet_full_path}" is not a valid file.'
251
+ )
252
+
253
+ stylesheet = f"./{stylesheet.rsplit('.', 1)[0].strip('/')}.css"
254
+
204
255
  sheets.append(stylesheet) if stylesheet not in sheets else None
256
+
257
+ if failed_to_import_sass:
258
+ console.error(
259
+ 'The `libsass` package is required to compile sass/scss stylesheet files. Run `pip install "libsass>=0.23.0"`.'
260
+ )
261
+
205
262
  return templates.STYLE.render(stylesheets=sheets)
206
263
 
207
264
 
@@ -311,13 +368,11 @@ def _compile_stateful_components(
311
368
 
312
369
  # Include dynamic imports in the shared component.
313
370
  if dynamic_imports := component._get_all_dynamic_imports():
314
- rendered_components.update(
315
- {dynamic_import: None for dynamic_import in dynamic_imports}
316
- )
371
+ rendered_components.update(dict.fromkeys(dynamic_imports))
317
372
 
318
373
  # Include custom code in the shared component.
319
374
  rendered_components.update(
320
- {code: None for code in component._get_all_custom_code()},
375
+ dict.fromkeys(component._get_all_custom_code()),
321
376
  )
322
377
 
323
378
  # Include all imports in the shared component.
@@ -53,9 +53,9 @@ def wait_for_client_redirect(component: Component) -> Component:
53
53
  The conditionally rendered component.
54
54
  """
55
55
  return cond(
56
- condition=route_not_found,
57
- c1=component,
58
- c2=ClientSideRouting.create(),
56
+ route_not_found,
57
+ component,
58
+ ClientSideRouting.create(),
59
59
  )
60
60
 
61
61
 
@@ -31,7 +31,7 @@ class Cond(MemoizationLeaf):
31
31
  cls,
32
32
  cond: Var,
33
33
  comp1: BaseComponent,
34
- comp2: BaseComponent | None = None,
34
+ comp2: BaseComponent | types.Unset = types.Unset(),
35
35
  ) -> Component:
36
36
  """Create a conditional component.
37
37
 
@@ -44,10 +44,14 @@ class Cond(MemoizationLeaf):
44
44
  The conditional component.
45
45
  """
46
46
  # Wrap everything in fragments.
47
- if type(comp1).__name__ != "Fragment":
47
+ if type(comp1) is not Fragment:
48
48
  comp1 = Fragment.create(comp1)
49
- if comp2 is None or type(comp2).__name__ != "Fragment":
50
- comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
49
+ if isinstance(comp2, types.Unset) or type(comp2) is not Fragment:
50
+ comp2 = (
51
+ Fragment.create(comp2)
52
+ if not isinstance(comp2, types.Unset)
53
+ else Fragment.create()
54
+ )
51
55
  return Fragment.create(
52
56
  cls._create(
53
57
  children=[comp1, comp2],
@@ -96,18 +100,22 @@ class Cond(MemoizationLeaf):
96
100
 
97
101
 
98
102
  @overload
99
- def cond(condition: Any, c1: Component, c2: Any) -> Component: ... # pyright: ignore [reportOverlappingOverload]
103
+ def cond(condition: Any, c1: Component, c2: Any, /) -> Component: ... # pyright: ignore [reportOverlappingOverload]
104
+
105
+
106
+ @overload
107
+ def cond(condition: Any, c1: Component, /) -> Component: ...
100
108
 
101
109
 
102
110
  @overload
103
- def cond(condition: Any, c1: Component) -> Component: ...
111
+ def cond(condition: Any, c1: Any, c2: Component, /) -> Component: ... # pyright: ignore [reportOverlappingOverload]
104
112
 
105
113
 
106
114
  @overload
107
- def cond(condition: Any, c1: Any, c2: Any) -> Var: ...
115
+ def cond(condition: Any, c1: Any, c2: Any, /) -> Var: ...
108
116
 
109
117
 
110
- def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
118
+ def cond(condition: Any, c1: Any, c2: Any = types.Unset(), /) -> Component | Var:
111
119
  """Create a conditional component or Prop.
112
120
 
113
121
  Args:
@@ -128,15 +136,15 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
128
136
 
129
137
  # If the first component is a component, create a Cond component.
130
138
  if isinstance(c1, BaseComponent):
131
- if c2 is not None and not isinstance(c2, BaseComponent):
132
- raise ValueError("Both arguments must be components.")
139
+ if not isinstance(c2, types.Unset) and not isinstance(c2, BaseComponent):
140
+ return Cond.create(cond_var.bool(), c1, Fragment.create(c2))
133
141
  return Cond.create(cond_var.bool(), c1, c2)
134
142
 
135
143
  # Otherwise, create a conditional Var.
136
144
  # Check that the second argument is valid.
137
145
  if isinstance(c2, BaseComponent):
138
- raise ValueError("Both arguments must be props.")
139
- if c2 is None:
146
+ return Cond.create(cond_var.bool(), Fragment.create(c1), c2)
147
+ if isinstance(c2, types.Unset):
140
148
  raise ValueError("For conditional vars, the second argument must be set.")
141
149
 
142
150
  # convert the truth and false cond parts into vars so the _var_data can be obtained.
@@ -190,7 +190,7 @@ class GhostUpload(Fragment):
190
190
  class Upload(MemoizationLeaf):
191
191
  """A file upload component."""
192
192
 
193
- library = "react-dropzone@14.3.5"
193
+ library = "react-dropzone@14.3.8"
194
194
 
195
195
  tag = ""
196
196
 
@@ -72,13 +72,11 @@ def load_dynamic_serializer():
72
72
  rendered_components = {}
73
73
  # Include dynamic imports in the shared component.
74
74
  if dynamic_imports := component._get_all_dynamic_imports():
75
- rendered_components.update(
76
- {dynamic_import: None for dynamic_import in dynamic_imports}
77
- )
75
+ rendered_components.update(dict.fromkeys(dynamic_imports))
78
76
 
79
77
  # Include custom code in the shared component.
80
78
  rendered_components.update(
81
- {code: None for code in component._get_all_custom_code()},
79
+ dict.fromkeys(component._get_all_custom_code()),
82
80
  )
83
81
 
84
82
  rendered_components[
@@ -53,42 +53,35 @@ class Icon(LucideIconComponent):
53
53
  if "tag" not in props:
54
54
  raise AttributeError("Missing 'tag' keyword-argument for Icon")
55
55
 
56
- tag: str | Var | LiteralVar = Var.create(props.pop("tag"))
57
- if isinstance(tag, LiteralVar):
58
- if isinstance(tag, LiteralStringVar):
59
- tag = tag._var_value
56
+ tag_var: Var | LiteralVar = Var.create(props.pop("tag"))
57
+ if isinstance(tag_var, LiteralVar):
58
+ if isinstance(tag_var, LiteralStringVar):
59
+ tag = format.to_snake_case(tag_var._var_value.lower())
60
60
  else:
61
- raise TypeError(f"Icon name must be a string, got {type(tag)}")
62
- elif isinstance(tag, Var):
63
- tag_stringified = tag.guess_type()
61
+ raise TypeError(f"Icon name must be a string, got {type(tag_var)}")
62
+ elif isinstance(tag_var, Var):
63
+ tag_stringified = tag_var.guess_type()
64
64
  if not isinstance(tag_stringified, StringVar):
65
- raise TypeError(f"Icon name must be a string, got {tag._var_type}")
65
+ raise TypeError(f"Icon name must be a string, got {tag_var._var_type}")
66
66
  return DynamicIcon.create(name=tag_stringified.replace("_", "-"), **props)
67
67
 
68
- if (
69
- not isinstance(tag, str)
70
- or format.to_snake_case(tag) not in LUCIDE_ICON_LIST
71
- ):
72
- if isinstance(tag, str):
73
- icons_sorted = sorted(
74
- LUCIDE_ICON_LIST,
75
- key=lambda s, tag=tag: format.length_of_largest_common_substring(
76
- tag, s
77
- ),
78
- reverse=True,
79
- )
80
- else:
81
- icons_sorted = LUCIDE_ICON_LIST
68
+ if tag not in LUCIDE_ICON_LIST:
69
+ icons_sorted = sorted(
70
+ LUCIDE_ICON_LIST,
71
+ key=lambda s, tag=tag: format.length_of_largest_common_substring(
72
+ tag, s
73
+ ),
74
+ reverse=True,
75
+ )
82
76
  console.warn(
83
77
  f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(icons_sorted[0:10])}, ..."
84
78
  "\nSee full list at https://reflex.dev/docs/library/data-display/icon/#icons-list. Using 'circle-help' icon instead."
85
79
  )
86
- tag = "circle-help"
80
+ tag = "circle_help"
87
81
 
88
- if tag in LUCIDE_ICON_MAPPING_OVERRIDE:
89
- props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[tag]
90
- else:
91
- props["tag"] = format.to_title_case(format.to_snake_case(tag)) + "Icon"
82
+ props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE.get(
83
+ tag, format.to_title_case(tag) + "Icon"
84
+ )
92
85
  props["alias"] = f"Lucide{props['tag']}"
93
86
  props.setdefault("color", "var(--current-color)")
94
87
  return super().create(**props)
@@ -71,7 +71,7 @@ class Plotly(NoSSRComponent):
71
71
 
72
72
  library = "react-plotly.js@2.6.0"
73
73
 
74
- lib_dependencies: list[str] = ["plotly.js@2.35.3"]
74
+ lib_dependencies: list[str] = ["plotly.js@3.0.1"]
75
75
 
76
76
  tag = "Plot"
77
77
 
@@ -289,7 +289,7 @@ class PlotlyBasic(Plotly):
289
289
 
290
290
  library = "react-plotly.js@2.6.0"
291
291
 
292
- lib_dependencies: list[str] = ["plotly.js-basic-dist-min@3.0.0"]
292
+ lib_dependencies: list[str] = ["plotly.js-basic-dist-min@3.0.1"]
293
293
 
294
294
  def add_imports(self) -> ImportDict | list[ImportDict]:
295
295
  """Add imports for the plotly basic component.
@@ -315,7 +315,7 @@ class PlotlyCartesian(Plotly):
315
315
 
316
316
  library = "react-plotly.js@2.6.0"
317
317
 
318
- lib_dependencies: list[str] = ["plotly.js-cartesian-dist-min@3.0.0"]
318
+ lib_dependencies: list[str] = ["plotly.js-cartesian-dist-min@3.0.1"]
319
319
 
320
320
  def add_imports(self) -> ImportDict | list[ImportDict]:
321
321
  """Add imports for the plotly cartesian component.
@@ -341,7 +341,7 @@ class PlotlyGeo(Plotly):
341
341
 
342
342
  library = "react-plotly.js@2.6.0"
343
343
 
344
- lib_dependencies: list[str] = ["plotly.js-geo-dist-min@3.0.0"]
344
+ lib_dependencies: list[str] = ["plotly.js-geo-dist-min@3.0.1"]
345
345
 
346
346
  def add_imports(self) -> ImportDict | list[ImportDict]:
347
347
  """Add imports for the plotly geo component.
@@ -367,7 +367,7 @@ class PlotlyGl3d(Plotly):
367
367
 
368
368
  library = "react-plotly.js@2.6.0"
369
369
 
370
- lib_dependencies: list[str] = ["plotly.js-gl3d-dist-min@3.0.0"]
370
+ lib_dependencies: list[str] = ["plotly.js-gl3d-dist-min@3.0.1"]
371
371
 
372
372
  def add_imports(self) -> ImportDict | list[ImportDict]:
373
373
  """Add imports for the plotly 3d component.
@@ -393,7 +393,7 @@ class PlotlyGl2d(Plotly):
393
393
 
394
394
  library = "react-plotly.js@2.6.0"
395
395
 
396
- lib_dependencies: list[str] = ["plotly.js-gl2d-dist-min@3.0.0"]
396
+ lib_dependencies: list[str] = ["plotly.js-gl2d-dist-min@3.0.1"]
397
397
 
398
398
  def add_imports(self) -> ImportDict | list[ImportDict]:
399
399
  """Add imports for the plotly 2d component.
@@ -419,7 +419,7 @@ class PlotlyMapbox(Plotly):
419
419
 
420
420
  library = "react-plotly.js@2.6.0"
421
421
 
422
- lib_dependencies: list[str] = ["plotly.js-mapbox-dist-min@3.0.0"]
422
+ lib_dependencies: list[str] = ["plotly.js-mapbox-dist-min@3.0.1"]
423
423
 
424
424
  def add_imports(self) -> ImportDict | list[ImportDict]:
425
425
  """Add imports for the plotly mapbox component.
@@ -445,7 +445,7 @@ class PlotlyFinance(Plotly):
445
445
 
446
446
  library = "react-plotly.js@2.6.0"
447
447
 
448
- lib_dependencies: list[str] = ["plotly.js-finance-dist-min@3.0.0"]
448
+ lib_dependencies: list[str] = ["plotly.js-finance-dist-min@3.0.1"]
449
449
 
450
450
  def add_imports(self) -> ImportDict | list[ImportDict]:
451
451
  """Add imports for the plotly finance component.
@@ -471,7 +471,7 @@ class PlotlyStrict(Plotly):
471
471
 
472
472
  library = "react-plotly.js@2.6.0"
473
473
 
474
- lib_dependencies: list[str] = ["plotly.js-strict-dist-min@3.0.0"]
474
+ lib_dependencies: list[str] = ["plotly.js-strict-dist-min@3.0.1"]
475
475
 
476
476
  def add_imports(self) -> ImportDict | list[ImportDict]:
477
477
  """Add imports for the plotly strict component.
@@ -8,7 +8,7 @@ from reflex.components.component import Component, MemoizationLeaf, NoSSRCompone
8
8
  class Recharts(Component):
9
9
  """A component that wraps a recharts lib."""
10
10
 
11
- library = "recharts@2.15.0"
11
+ library = "recharts@2.15.1"
12
12
 
13
13
  def _get_style(self) -> dict:
14
14
  return {"wrapperStyle": self.style}
@@ -17,7 +17,7 @@ class Recharts(Component):
17
17
  class RechartsCharts(NoSSRComponent, MemoizationLeaf):
18
18
  """A component that wraps a recharts lib."""
19
19
 
20
- library = "recharts@2.15.0"
20
+ library = "recharts@2.15.1"
21
21
 
22
22
 
23
23
  LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]
@@ -172,7 +172,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps):
172
172
  class Toaster(Component):
173
173
  """A Toaster Component for displaying toast notifications."""
174
174
 
175
- library: str | None = "sonner@1.7.2"
175
+ library: str | None = "sonner@2.0.1"
176
176
 
177
177
  tag = "Toaster"
178
178
 
reflex/config.py CHANGED
@@ -30,7 +30,6 @@ from typing import (
30
30
  )
31
31
 
32
32
  import pydantic.v1 as pydantic
33
- from reflex_cli.constants.hosting import Hosting
34
33
 
35
34
  from reflex import constants
36
35
  from reflex.base import Base
@@ -596,13 +595,13 @@ class EnvironmentVariables:
596
595
  constants.CompileContext.UNDEFINED, internal=True
597
596
  )
598
597
 
599
- # Whether to use npm over bun to install frontend packages.
598
+ # Whether to use npm over bun to install and run the frontend.
600
599
  REFLEX_USE_NPM: EnvVar[bool] = env_var(False)
601
600
 
602
601
  # The npm registry to use.
603
602
  NPM_CONFIG_REGISTRY: EnvVar[str | None] = env_var(None)
604
603
 
605
- # Whether to use Granian for the backend. Otherwise, use Uvicorn.
604
+ # Whether to use Granian for the backend. By default, the backend uses Uvicorn if available.
606
605
  REFLEX_USE_GRANIAN: EnvVar[bool] = env_var(False)
607
606
 
608
607
  # The username to use for authentication on python package repository. Username and password must both be provided.
@@ -614,9 +613,6 @@ class EnvironmentVariables:
614
613
  # Whether to use the system installed bun. If set to false, bun will be bundled with the app.
615
614
  REFLEX_USE_SYSTEM_BUN: EnvVar[bool] = env_var(False)
616
615
 
617
- # Whether to use the system installed node and npm. If set to false, node and npm will be bundled with the app.
618
- REFLEX_USE_SYSTEM_NODE: EnvVar[bool] = env_var(False)
619
-
620
616
  # The working directory for the next.js commands.
621
617
  REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB))
622
618
 
@@ -810,8 +806,8 @@ class Config(Base):
810
806
  # Tailwind config.
811
807
  tailwind: dict[str, Any] | None = {"plugins": ["@tailwindcss/typography"]}
812
808
 
813
- # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
814
- timeout: int = 120
809
+ # DEPRECATED. Timeout when launching the gunicorn server.
810
+ timeout: int | None = None
815
811
 
816
812
  # Whether to enable or disable nextJS gzip compression.
817
813
  next_compression: bool = True
@@ -822,22 +818,17 @@ class Config(Base):
822
818
  # Additional frontend packages to install.
823
819
  frontend_packages: list[str] = []
824
820
 
825
- # The hosting service backend URL.
826
- cp_backend_url: str = Hosting.HOSTING_SERVICE
827
- # The hosting service frontend URL.
828
- cp_web_url: str = Hosting.HOSTING_SERVICE_UI
829
-
830
- # The worker class used in production mode
821
+ # DEPRECATED. The worker class used in production mode
831
822
  gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
832
823
 
833
- # Number of gunicorn workers from user
824
+ # DEPRECATED. Number of gunicorn workers from user
834
825
  gunicorn_workers: int | None = None
835
826
 
836
- # Number of requests before a worker is restarted; set to 0 to disable
837
- gunicorn_max_requests: int = 100
827
+ # DEPRECATED. Number of requests before a worker is restarted; set to 0 to disable
828
+ gunicorn_max_requests: int | None = None
838
829
 
839
- # Variance limit for max requests; gunicorn only
840
- gunicorn_max_requests_jitter: int = 25
830
+ # DEPRECATED. Variance limit for max requests; gunicorn only
831
+ gunicorn_max_requests_jitter: int | None = None
841
832
 
842
833
  # Indicate which type of state manager to use
843
834
  state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
@@ -941,8 +932,14 @@ class Config(Base):
941
932
  """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
942
933
  )
943
934
  else:
944
- # load env file if exists
945
- load_dotenv(env_file, override=True)
935
+ # load env files in reverse order if they exist
936
+ for env_file_path in [
937
+ Path(p)
938
+ for s in reversed(env_file.split(os.pathsep))
939
+ if (p := s.strip())
940
+ ]:
941
+ if env_file_path.exists():
942
+ load_dotenv(env_file_path, override=True)
946
943
 
947
944
  updated_values = {}
948
945
  # Iterate over the fields.
@@ -964,7 +961,7 @@ class Config(Base):
964
961
  env_var = "***"
965
962
 
966
963
  if value != getattr(self, key):
967
- console.info(
964
+ console.debug(
968
965
  f"Overriding config value {key} with env var {key.upper()}={env_var}",
969
966
  dedupe=True,
970
967
  )
@@ -45,7 +45,7 @@ from .config import (
45
45
  )
46
46
  from .custom_components import CustomComponents
47
47
  from .event import Endpoint, EventTriggers, SocketEvent
48
- from .installer import Bun, Fnm, Node, PackageJson
48
+ from .installer import Bun, Node, PackageJson
49
49
  from .route import (
50
50
  ROUTE_NOT_FOUND,
51
51
  ROUTER,
@@ -94,7 +94,6 @@ __all__ = [
94
94
  "EventTriggers",
95
95
  "Expiration",
96
96
  "Ext",
97
- "Fnm",
98
97
  "GitIgnore",
99
98
  "Hooks",
100
99
  "Imports",
reflex/constants/base.py CHANGED
@@ -88,6 +88,9 @@ class Reflex(SimpleNamespace):
88
88
 
89
89
  RELEASES_URL = "https://api.github.com/repos/reflex-dev/templates/releases"
90
90
 
91
+ # The reflex stylesheet language supported
92
+ STYLESHEETS_SUPPORTED = ["css", "sass", "scss"]
93
+
91
94
 
92
95
  class ReflexHostingCLI(SimpleNamespace):
93
96
  """Base constants concerning Reflex Hosting CLI."""