reflex 0.7.4a0__py3-none-any.whl → 0.7.4a2__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.

@@ -1,5 +1,5 @@
1
1
  import { useTheme } from "next-themes";
2
- import { useEffect, useState } from "react";
2
+ import { useRef, useEffect, useState } from "react";
3
3
  import {
4
4
  ColorModeContext,
5
5
  defaultColorMode,
@@ -13,6 +13,14 @@ export default function RadixThemesColorModeProvider({ children }) {
13
13
  const [resolvedColorMode, setResolvedColorMode] = useState(
14
14
  defaultColorMode === "dark" ? "dark" : "light",
15
15
  );
16
+ const firstUpdate = useRef(true);
17
+ useEffect(() => {
18
+ if (firstUpdate.current) {
19
+ firstUpdate.current = false;
20
+ setRawColorMode(theme);
21
+ setResolvedColorMode(resolvedTheme);
22
+ }
23
+ });
16
24
 
17
25
  useEffect(() => {
18
26
  if (isDevMode) {
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):
@@ -952,7 +968,7 @@ class App(MiddlewareMixin, LifespanMixin):
952
968
  # Check the nocompile file.
953
969
  if nocompile.exists():
954
970
  # Delete the nocompile file
955
- nocompile.unlink()
971
+ nocompile.unlink(missing_ok=True)
956
972
  return False
957
973
 
958
974
  # By default, compile the app.
@@ -1242,7 +1258,9 @@ class App(MiddlewareMixin, LifespanMixin):
1242
1258
  compiler.compile_document_root(
1243
1259
  self.head_components,
1244
1260
  html_lang=self.html_lang,
1245
- html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType]
1261
+ html_custom_attrs=(
1262
+ {**self.html_custom_attrs} if self.html_custom_attrs else {}
1263
+ ),
1246
1264
  )
1247
1265
  )
1248
1266
 
reflex/base.py CHANGED
@@ -38,7 +38,7 @@ def validate_field_name(bases: list[Type["BaseModel"]], field_name: str) -> None
38
38
 
39
39
  # monkeypatch pydantic validate_field_name method to skip validating
40
40
  # shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
41
- pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPossiblyUnboundVariable, reportPrivateImportUsage]
41
+ pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPrivateImportUsage]
42
42
 
43
43
  if TYPE_CHECKING:
44
44
  from reflex.vars import Var
@@ -107,7 +107,7 @@ class Base(BaseModel):
107
107
  default_value: The default value of the field
108
108
  """
109
109
  var_name = var._var_field_name
110
- new_field = ModelField.infer( # pyright: ignore [reportPossiblyUnboundVariable]
110
+ new_field = ModelField.infer(
111
111
  name=var_name,
112
112
  value=default_value,
113
113
  annotation=var._var_type,
@@ -128,5 +128,5 @@ class Base(BaseModel):
128
128
  if isinstance(key, str):
129
129
  # Seems like this function signature was wrong all along?
130
130
  # If the user wants a field that we know of, get it and pass it off to _get_value
131
- return getattr(self, key, key)
131
+ return getattr(self, key)
132
132
  return key
@@ -20,6 +20,7 @@ from reflex.config import environment, get_config
20
20
  from reflex.state import BaseState
21
21
  from reflex.style import SYSTEM_COLOR_MODE
22
22
  from reflex.utils import console, path_ops
23
+ from reflex.utils.exceptions import ReflexError
23
24
  from reflex.utils.exec import is_prod_mode
24
25
  from reflex.utils.imports import ImportVar
25
26
  from reflex.utils.prerequisites import get_web_dir
@@ -368,13 +369,11 @@ def _compile_stateful_components(
368
369
 
369
370
  # Include dynamic imports in the shared component.
370
371
  if dynamic_imports := component._get_all_dynamic_imports():
371
- rendered_components.update(
372
- {dynamic_import: None for dynamic_import in dynamic_imports}
373
- )
372
+ rendered_components.update(dict.fromkeys(dynamic_imports))
374
373
 
375
374
  # Include custom code in the shared component.
376
375
  rendered_components.update(
377
- {code: None for code in component._get_all_custom_code()},
376
+ dict.fromkeys(component._get_all_custom_code()),
378
377
  )
379
378
 
380
379
  # Include all imports in the shared component.
@@ -653,6 +652,8 @@ def into_component(component: Component | ComponentCallable) -> Component:
653
652
  ):
654
653
  return converted
655
654
  except KeyError as e:
655
+ if isinstance(e, ReflexError):
656
+ raise
656
657
  key = e.args[0] if e.args else None
657
658
  if key is not None and isinstance(key, Var):
658
659
  raise TypeError(
@@ -660,6 +661,8 @@ def into_component(component: Component | ComponentCallable) -> Component:
660
661
  ).with_traceback(e.__traceback__) from None
661
662
  raise
662
663
  except TypeError as e:
664
+ if isinstance(e, ReflexError):
665
+ raise
663
666
  message = e.args[0] if e.args else None
664
667
  if message and isinstance(message, str):
665
668
  if message.endswith("has no len()") and (
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import contextlib
5
6
  import copy
6
7
  import dataclasses
7
8
  import functools
@@ -1974,13 +1975,15 @@ class NoSSRComponent(Component):
1974
1975
  # Do NOT import the main library/tag statically.
1975
1976
  import_name = self._get_import_name()
1976
1977
  if import_name is not None:
1977
- _imports[import_name] = [
1978
+ with contextlib.suppress(ValueError):
1979
+ _imports[import_name].remove(self.import_var)
1980
+ _imports[import_name].append(
1978
1981
  imports.ImportVar(
1979
1982
  tag=None,
1980
1983
  render=False,
1981
1984
  transpile=self._should_transpile(self.library),
1982
- ),
1983
- ]
1985
+ )
1986
+ )
1984
1987
 
1985
1988
  return imports.merge_imports(
1986
1989
  dynamic_import,
@@ -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.
@@ -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)
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
@@ -45,7 +44,7 @@ from reflex.utils.types import (
45
44
  )
46
45
 
47
46
  try:
48
- from dotenv import load_dotenv # pyright: ignore [reportMissingImports]
47
+ from dotenv import load_dotenv
49
48
  except ImportError:
50
49
  load_dotenv = None
51
50
 
@@ -602,7 +601,7 @@ class EnvironmentVariables:
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.
@@ -807,8 +806,8 @@ class Config(Base):
807
806
  # Tailwind config.
808
807
  tailwind: dict[str, Any] | None = {"plugins": ["@tailwindcss/typography"]}
809
808
 
810
- # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
811
- timeout: int = 120
809
+ # DEPRECATED. Timeout when launching the gunicorn server.
810
+ timeout: int | None = None
812
811
 
813
812
  # Whether to enable or disable nextJS gzip compression.
814
813
  next_compression: bool = True
@@ -819,22 +818,17 @@ class Config(Base):
819
818
  # Additional frontend packages to install.
820
819
  frontend_packages: list[str] = []
821
820
 
822
- # The hosting service backend URL.
823
- cp_backend_url: str = Hosting.HOSTING_SERVICE
824
- # The hosting service frontend URL.
825
- cp_web_url: str = Hosting.HOSTING_SERVICE_UI
826
-
827
- # The worker class used in production mode
821
+ # DEPRECATED. The worker class used in production mode
828
822
  gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
829
823
 
830
- # Number of gunicorn workers from user
824
+ # DEPRECATED. Number of gunicorn workers from user
831
825
  gunicorn_workers: int | None = None
832
826
 
833
- # Number of requests before a worker is restarted; set to 0 to disable
834
- 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
835
829
 
836
- # Variance limit for max requests; gunicorn only
837
- gunicorn_max_requests_jitter: int = 25
830
+ # DEPRECATED. Variance limit for max requests; gunicorn only
831
+ gunicorn_max_requests_jitter: int | None = None
838
832
 
839
833
  # Indicate which type of state manager to use
840
834
  state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
@@ -938,8 +932,14 @@ class Config(Base):
938
932
  """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
939
933
  )
940
934
  else:
941
- # load env file if exists
942
- 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)
943
943
 
944
944
  updated_values = {}
945
945
  # Iterate over the fields.
@@ -826,12 +826,19 @@ def _collect_details_for_gallery():
826
826
  Raises:
827
827
  Exit: If pyproject.toml file is ill-formed or the request to the backend services fails.
828
828
  """
829
+ import reflex_cli.constants
829
830
  from reflex_cli.utils import hosting
830
831
 
831
832
  console.rule("[bold]Authentication with Reflex Services")
832
833
  console.print("First let's log in to Reflex backend services.")
833
834
  access_token, _ = hosting.authenticated_token()
834
835
 
836
+ if not access_token:
837
+ console.error(
838
+ "Unable to authenticate with Reflex backend services. Make sure you are logged in."
839
+ )
840
+ raise typer.Exit(code=1)
841
+
835
842
  console.rule("[bold]Custom Component Information")
836
843
  params = {}
837
844
  package_name = None
@@ -845,10 +852,8 @@ def _collect_details_for_gallery():
845
852
  console.print(f"[ Custom component package name ] : {package_name}")
846
853
  params["package_name"] = package_name
847
854
 
848
- config = get_config()
849
-
850
855
  post_custom_components_gallery_endpoint = (
851
- f"{config.cp_backend_url}/custom-components/gallery"
856
+ f"{reflex_cli.constants.Hosting.HOSTING_SERVICE}/custom-components/gallery"
852
857
  )
853
858
 
854
859
  # Check the backend services if the user is allowed to update information of this package is already shared.
reflex/reflex.py CHANGED
@@ -7,13 +7,14 @@ from pathlib import Path
7
7
 
8
8
  import typer
9
9
  import typer.core
10
- from reflex_cli.v2.deployments import check_version, hosting_cli
10
+ from reflex_cli.v2.deployments import hosting_cli
11
11
 
12
12
  from reflex import constants
13
13
  from reflex.config import environment, get_config
14
14
  from reflex.custom_components.custom_components import custom_components_cli
15
15
  from reflex.state import reset_disk_state_manager
16
16
  from reflex.utils import console, redir, telemetry
17
+ from reflex.utils.exec import should_use_granian
17
18
 
18
19
  # Disable typer+rich integration for help panels
19
20
  typer.core.rich = None # pyright: ignore [reportPrivateImportUsage]
@@ -203,9 +204,23 @@ def _run(
203
204
 
204
205
  prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
205
206
 
206
- if frontend:
207
- # Get the app module.
208
- prerequisites.get_compiled_app()
207
+ # Get the app module.
208
+ app_task = prerequisites.compile_or_validate_app
209
+ args = (frontend,)
210
+
211
+ # Granian fails if the app is already imported.
212
+ if should_use_granian():
213
+ import concurrent.futures
214
+
215
+ compile_future = concurrent.futures.ProcessPoolExecutor(max_workers=1).submit(
216
+ app_task,
217
+ *args,
218
+ )
219
+ validation_result = compile_future.result()
220
+ else:
221
+ validation_result = app_task(*args)
222
+ if not validation_result:
223
+ raise typer.Exit(1)
209
224
 
210
225
  # Warn if schema is not up to date.
211
226
  prerequisites.check_schema_up_to_date()
@@ -386,6 +401,7 @@ def export(
386
401
  def login(loglevel: constants.LogLevel | None = typer.Option(None)):
387
402
  """Authenticate with experimental Reflex hosting service."""
388
403
  from reflex_cli.v2 import cli as hosting_cli
404
+ from reflex_cli.v2.deployments import check_version
389
405
 
390
406
  loglevel = loglevel or get_config().loglevel
391
407
 
@@ -407,6 +423,7 @@ def logout(
407
423
  ):
408
424
  """Log out of access to Reflex hosting service."""
409
425
  from reflex_cli.v2.cli import logout
426
+ from reflex_cli.v2.deployments import check_version
410
427
 
411
428
  check_version()
412
429
 
@@ -567,6 +584,7 @@ def deploy(
567
584
  from reflex_cli.constants.base import LogLevel as HostingLogLevel
568
585
  from reflex_cli.utils import dependency
569
586
  from reflex_cli.v2 import cli as hosting_cli
587
+ from reflex_cli.v2.deployments import check_version
570
588
 
571
589
  from reflex.utils import export as export_utils
572
590
  from reflex.utils import prerequisites
reflex/state.py CHANGED
@@ -1671,6 +1671,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1671
1671
 
1672
1672
  raise TypeError(
1673
1673
  f"Your handler {handler.fn.__qualname__} must only return/yield: None, Events or other EventHandlers referenced by their class (i.e. using `type(self)` or other class references)."
1674
+ f" Returned events of types {', '.join(map(str, map(type, events)))!s}."
1674
1675
  )
1675
1676
 
1676
1677
  async def _as_state_update(