reflex 0.7.3a1__py3-none-any.whl → 0.7.4__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 (50) hide show
  1. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +9 -1
  2. reflex/.templates/web/utils/state.js +1 -1
  3. reflex/app.py +21 -5
  4. reflex/app_mixins/middleware.py +2 -3
  5. reflex/base.py +3 -3
  6. reflex/compiler/compiler.py +68 -8
  7. reflex/components/component.py +6 -3
  8. reflex/components/core/client_side_routing.py +3 -3
  9. reflex/components/core/cond.py +20 -12
  10. reflex/components/core/upload.py +1 -1
  11. reflex/components/dynamic.py +2 -4
  12. reflex/components/lucide/icon.py +20 -27
  13. reflex/components/plotly/plotly.py +9 -9
  14. reflex/components/recharts/recharts.py +2 -2
  15. reflex/components/sonner/toast.py +1 -1
  16. reflex/config.py +23 -23
  17. reflex/constants/__init__.py +1 -2
  18. reflex/constants/base.py +3 -0
  19. reflex/constants/installer.py +8 -105
  20. reflex/custom_components/custom_components.py +8 -3
  21. reflex/reflex.py +22 -4
  22. reflex/state.py +9 -1
  23. reflex/testing.py +7 -1
  24. reflex/utils/build.py +3 -4
  25. reflex/utils/exec.py +156 -75
  26. reflex/utils/net.py +107 -18
  27. reflex/utils/path_ops.py +15 -25
  28. reflex/utils/prerequisites.py +225 -189
  29. reflex/utils/processes.py +70 -35
  30. reflex/utils/redir.py +3 -1
  31. reflex/utils/registry.py +16 -8
  32. reflex/vars/base.py +2 -38
  33. reflex/vars/datetime.py +10 -34
  34. reflex/vars/number.py +16 -112
  35. reflex/vars/sequence.py +99 -108
  36. {reflex-0.7.3a1.dist-info → reflex-0.7.4.dist-info}/METADATA +32 -23
  37. {reflex-0.7.3a1.dist-info → reflex-0.7.4.dist-info}/RECORD +58 -68
  38. {reflex-0.7.3a1.dist-info → reflex-0.7.4.dist-info}/WHEEL +1 -1
  39. {reflex-0.7.3a1.dist-info → reflex-0.7.4.dist-info}/entry_points.txt +0 -3
  40. benchmarks/__init__.py +0 -3
  41. benchmarks/benchmark_compile_times.py +0 -147
  42. benchmarks/benchmark_imports.py +0 -128
  43. benchmarks/benchmark_lighthouse.py +0 -75
  44. benchmarks/benchmark_package_size.py +0 -135
  45. benchmarks/benchmark_web_size.py +0 -106
  46. benchmarks/conftest.py +0 -20
  47. benchmarks/lighthouse.sh +0 -77
  48. benchmarks/utils.py +0 -74
  49. reflex/app_module_for_backend.py +0 -33
  50. {reflex-0.7.3a1.dist-info → reflex-0.7.4.dist-info}/licenses/LICENSE +0 -0
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
 
@@ -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
 
@@ -724,6 +720,9 @@ class EnvironmentVariables:
724
720
  # Used by flexgen to enumerate the pages.
725
721
  REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False)
726
722
 
723
+ # The address to bind the HTTP client to. You can set this to "::" to enable IPv6.
724
+ REFLEX_HTTP_CLIENT_BIND_ADDRESS: EnvVar[str | None] = env_var(None)
725
+
727
726
 
728
727
  environment = EnvironmentVariables()
729
728
 
@@ -810,8 +809,8 @@ class Config(Base):
810
809
  # Tailwind config.
811
810
  tailwind: dict[str, Any] | None = {"plugins": ["@tailwindcss/typography"]}
812
811
 
813
- # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
814
- timeout: int = 120
812
+ # DEPRECATED. Timeout when launching the gunicorn server.
813
+ timeout: int | None = None
815
814
 
816
815
  # Whether to enable or disable nextJS gzip compression.
817
816
  next_compression: bool = True
@@ -822,22 +821,17 @@ class Config(Base):
822
821
  # Additional frontend packages to install.
823
822
  frontend_packages: list[str] = []
824
823
 
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
824
+ # DEPRECATED. The worker class used in production mode
831
825
  gunicorn_worker_class: str = "uvicorn.workers.UvicornH11Worker"
832
826
 
833
- # Number of gunicorn workers from user
827
+ # DEPRECATED. Number of gunicorn workers from user
834
828
  gunicorn_workers: int | None = None
835
829
 
836
- # Number of requests before a worker is restarted; set to 0 to disable
837
- gunicorn_max_requests: int = 100
830
+ # DEPRECATED. Number of requests before a worker is restarted; set to 0 to disable
831
+ gunicorn_max_requests: int | None = None
838
832
 
839
- # Variance limit for max requests; gunicorn only
840
- gunicorn_max_requests_jitter: int = 25
833
+ # DEPRECATED. Variance limit for max requests; gunicorn only
834
+ gunicorn_max_requests_jitter: int | None = None
841
835
 
842
836
  # Indicate which type of state manager to use
843
837
  state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
@@ -941,8 +935,14 @@ class Config(Base):
941
935
  """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
942
936
  )
943
937
  else:
944
- # load env file if exists
945
- load_dotenv(env_file, override=True)
938
+ # load env files in reverse order if they exist
939
+ for env_file_path in [
940
+ Path(p)
941
+ for s in reversed(env_file.split(os.pathsep))
942
+ if (p := s.strip())
943
+ ]:
944
+ if env_file_path.exists():
945
+ load_dotenv(env_file_path, override=True)
946
946
 
947
947
  updated_values = {}
948
948
  # Iterate over the fields.
@@ -964,7 +964,7 @@ class Config(Base):
964
964
  env_var = "***"
965
965
 
966
966
  if value != getattr(self, key):
967
- console.info(
967
+ console.debug(
968
968
  f"Overriding config value {key} with env var {key.upper()}={env_var}",
969
969
  dedupe=True,
970
970
  )
@@ -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."""
@@ -1,46 +1,22 @@
1
- """File for constants related to the installation process. (Bun/FNM/Node)."""
1
+ """File for constants related to the installation process. (Bun/Node)."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import platform
6
- from pathlib import Path
7
5
  from types import SimpleNamespace
8
6
 
9
7
  from .base import IS_WINDOWS
10
8
  from .utils import classproperty
11
9
 
12
10
 
13
- def get_fnm_name() -> str | None:
14
- """Get the appropriate fnm executable name based on the current platform.
15
-
16
- Returns:
17
- The fnm executable name for the current platform.
18
- """
19
- platform_os = platform.system()
20
-
21
- if platform_os == "Windows":
22
- return "fnm-windows"
23
- elif platform_os == "Darwin":
24
- return "fnm-macos"
25
- elif platform_os == "Linux":
26
- machine = platform.machine()
27
- if machine == "arm" or machine.startswith("armv7"):
28
- return "fnm-arm32"
29
- elif machine.startswith("aarch") or machine.startswith("armv8"):
30
- return "fnm-arm64"
31
- return "fnm-linux"
32
- return None
33
-
34
-
35
11
  # Bun config.
36
12
  class Bun(SimpleNamespace):
37
13
  """Bun constants."""
38
14
 
39
15
  # The Bun version.
40
- VERSION = "1.2.0"
16
+ VERSION = "1.2.4"
41
17
 
42
18
  # Min Bun Version
43
- MIN_VERSION = "1.1.0"
19
+ MIN_VERSION = "1.2.4"
44
20
 
45
21
  # URL to bun install script.
46
22
  INSTALL_URL = "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/bun_install.sh"
@@ -81,43 +57,6 @@ registry = "{registry}"
81
57
  """
82
58
 
83
59
 
84
- # FNM config.
85
- class Fnm(SimpleNamespace):
86
- """FNM constants."""
87
-
88
- # The FNM version.
89
- VERSION = "1.35.1"
90
-
91
- FILENAME = get_fnm_name()
92
-
93
- # The URL to the fnm release binary
94
- INSTALL_URL = (
95
- f"https://github.com/Schniz/fnm/releases/download/v{VERSION}/{FILENAME}.zip"
96
- )
97
-
98
- @classproperty
99
- @classmethod
100
- def DIR(cls) -> Path:
101
- """The directory to store fnm.
102
-
103
- Returns:
104
- The directory to store fnm.
105
- """
106
- from reflex.config import environment
107
-
108
- return environment.REFLEX_DIR.get() / "fnm"
109
-
110
- @classproperty
111
- @classmethod
112
- def EXE(cls):
113
- """The fnm executable binary.
114
-
115
- Returns:
116
- The fnm executable binary.
117
- """
118
- return cls.DIR / ("fnm.exe" if IS_WINDOWS else "fnm")
119
-
120
-
121
60
  # Node / NPM config
122
61
  class Node(SimpleNamespace):
123
62
  """Node/ NPM constants."""
@@ -127,42 +66,6 @@ class Node(SimpleNamespace):
127
66
  # The minimum required node version.
128
67
  MIN_VERSION = "18.18.0"
129
68
 
130
- @classproperty
131
- @classmethod
132
- def BIN_PATH(cls):
133
- """The node bin path.
134
-
135
- Returns:
136
- The node bin path.
137
- """
138
- return (
139
- Fnm.DIR
140
- / "node-versions"
141
- / f"v{cls.VERSION}"
142
- / "installation"
143
- / ("bin" if not IS_WINDOWS else "")
144
- )
145
-
146
- @classproperty
147
- @classmethod
148
- def PATH(cls):
149
- """The default path where node is installed.
150
-
151
- Returns:
152
- The default path where node is installed.
153
- """
154
- return cls.BIN_PATH / ("node.exe" if IS_WINDOWS else "node")
155
-
156
- @classproperty
157
- @classmethod
158
- def NPM_PATH(cls):
159
- """The default path where npm is installed.
160
-
161
- Returns:
162
- The default path where npm is installed.
163
- """
164
- return cls.BIN_PATH / "npm"
165
-
166
69
 
167
70
  class PackageJson(SimpleNamespace):
168
71
  """Constants used to build the package.json file."""
@@ -179,11 +82,11 @@ class PackageJson(SimpleNamespace):
179
82
 
180
83
  DEPENDENCIES = {
181
84
  "@emotion/react": "11.14.0",
182
- "axios": "1.7.9",
85
+ "axios": "1.8.3",
183
86
  "json5": "2.2.3",
184
- "next": "15.1.7",
87
+ "next": "15.0.4",
185
88
  "next-sitemap": "4.2.3",
186
- "next-themes": "0.4.4",
89
+ "next-themes": "0.4.6",
187
90
  "react": "19.0.0",
188
91
  "react-dom": "19.0.0",
189
92
  "react-focus-lock": "2.13.6",
@@ -191,8 +94,8 @@ class PackageJson(SimpleNamespace):
191
94
  "universal-cookie": "7.2.2",
192
95
  }
193
96
  DEV_DEPENDENCIES = {
194
- "autoprefixer": "10.4.20",
195
- "postcss": "8.5.1",
97
+ "autoprefixer": "10.4.21",
98
+ "postcss": "8.5.3",
196
99
  "postcss-import": "16.1.0",
197
100
  }
198
101
  OVERRIDES = {
@@ -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
@@ -905,7 +905,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
905
905
  ]
906
906
  if len(parent_states) >= 2:
907
907
  raise ValueError(f"Only one parent state is allowed {parent_states}.")
908
- return parent_states[0] if len(parent_states) == 1 else None
908
+ # The first non-mixin state in the mro is our parent.
909
+ for base in cls.mro()[1:]:
910
+ if not issubclass(base, BaseState) or base._mixin:
911
+ continue
912
+ if base is BaseState:
913
+ break
914
+ return base
915
+ return None # No known parent
909
916
 
910
917
  @classmethod
911
918
  @functools.lru_cache()
@@ -1664,6 +1671,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1664
1671
 
1665
1672
  raise TypeError(
1666
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}."
1667
1675
  )
1668
1676
 
1669
1677
  async def _as_state_update(
reflex/testing.py CHANGED
@@ -371,7 +371,13 @@ class AppHarness:
371
371
 
372
372
  # Start the frontend.
373
373
  self.frontend_process = reflex.utils.processes.new_process(
374
- [reflex.utils.prerequisites.get_package_manager(), "run", "dev"],
374
+ [
375
+ *reflex.utils.prerequisites.get_js_package_executor(raise_on_none=True)[
376
+ 0
377
+ ],
378
+ "run",
379
+ "dev",
380
+ ],
375
381
  cwd=self.app_path / reflex.utils.prerequisites.get_web_dir(),
376
382
  env={"PORT": "0"},
377
383
  **FRONTEND_POPEN_ARGS,
reflex/utils/build.py CHANGED
@@ -212,7 +212,7 @@ def build(
212
212
 
213
213
  # Start the subprocess with the progress bar.
214
214
  process = processes.new_process(
215
- [prerequisites.get_package_manager(), "run", command],
215
+ [*prerequisites.get_js_package_executor(raise_on_none=True)[0], "run", command],
216
216
  cwd=wdir,
217
217
  shell=constants.IS_WINDOWS,
218
218
  )
@@ -231,11 +231,10 @@ def setup_frontend(
231
231
  """
232
232
  # Create the assets dir if it doesn't exist.
233
233
  path_ops.mkdir(constants.Dirs.APP_ASSETS)
234
-
235
- # Copy asset files to public folder.
236
234
  path_ops.cp(
237
235
  src=str(root / constants.Dirs.APP_ASSETS),
238
236
  dest=str(root / prerequisites.get_web_dir() / constants.Dirs.PUBLIC),
237
+ ignore=tuple(f"*.{ext}" for ext in constants.Reflex.STYLESHEETS_SUPPORTED),
239
238
  )
240
239
 
241
240
  # Set the environment variables in client (env.json).
@@ -248,7 +247,7 @@ def setup_frontend(
248
247
  if disable_telemetry:
249
248
  processes.new_process(
250
249
  [
251
- prerequisites.get_package_manager(),
250
+ *prerequisites.get_js_package_executor(raise_on_none=True)[0],
252
251
  "run",
253
252
  "next",
254
253
  "telemetry",