reflex 0.8.13__py3-none-any.whl → 0.8.14a1__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/reflex.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import atexit
6
5
  from importlib.util import find_spec
7
6
  from pathlib import Path
8
7
  from typing import TYPE_CHECKING
@@ -132,8 +131,11 @@ def _run(
132
131
  frontend_port: int | None = None,
133
132
  backend_port: int | None = None,
134
133
  backend_host: str | None = None,
134
+ single_port: bool = False,
135
135
  ):
136
136
  """Run the app in the given directory."""
137
+ import atexit
138
+
137
139
  from reflex.utils import build, exec, prerequisites, processes
138
140
 
139
141
  config = get_config()
@@ -173,7 +175,9 @@ def _run(
173
175
  auto_increment=auto_increment_frontend,
174
176
  )
175
177
 
176
- if backend:
178
+ if single_port:
179
+ backend_port = frontend_port
180
+ elif backend:
177
181
  auto_increment_backend = not bool(backend_port or config.backend_port)
178
182
 
179
183
  backend_port = processes.handle_port(
@@ -223,23 +227,23 @@ def _run(
223
227
  if not return_result:
224
228
  raise SystemExit(1)
225
229
 
230
+ if env != constants.Env.PROD and env != constants.Env.DEV:
231
+ msg = f"Invalid env: {env}. Must be DEV or PROD."
232
+ raise ValueError(msg)
233
+
226
234
  # Get the frontend and backend commands, based on the environment.
227
- setup_frontend = frontend_cmd = backend_cmd = None
228
235
  if env == constants.Env.DEV:
229
236
  setup_frontend, frontend_cmd, backend_cmd = (
230
237
  build.setup_frontend,
231
238
  exec.run_frontend,
232
239
  exec.run_backend,
233
240
  )
234
- if env == constants.Env.PROD:
241
+ elif env == constants.Env.PROD:
235
242
  setup_frontend, frontend_cmd, backend_cmd = (
236
243
  build.setup_frontend_prod,
237
244
  exec.run_frontend_prod,
238
245
  exec.run_backend_prod,
239
246
  )
240
- if not setup_frontend or not frontend_cmd or not backend_cmd:
241
- msg = f"Invalid env: {env}. Must be DEV or PROD."
242
- raise ValueError(msg)
243
247
 
244
248
  # Post a telemetry event.
245
249
  telemetry.send(f"run-{env.value}")
@@ -251,7 +255,7 @@ def _run(
251
255
  commands = []
252
256
 
253
257
  # Run the frontend on a separate thread.
254
- if frontend:
258
+ if frontend and not single_port:
255
259
  setup_frontend(Path.cwd())
256
260
  commands.append((frontend_cmd, Path.cwd(), frontend_port, backend))
257
261
 
@@ -267,21 +271,30 @@ def _run(
267
271
  )
268
272
  )
269
273
 
270
- # Start the frontend and backend.
271
- with processes.run_concurrently_context(*commands):
272
- # In dev mode, run the backend on the main thread.
273
- if backend and backend_port and env == constants.Env.DEV:
274
- backend_cmd(
275
- backend_host,
276
- int(backend_port),
277
- config.loglevel.subprocess_level(),
278
- frontend,
279
- )
280
- # The windows uvicorn bug workaround
281
- # https://github.com/reflex-dev/reflex/issues/2335
282
- if constants.IS_WINDOWS and exec.frontend_process:
283
- # Sends SIGTERM in windows
284
- exec.kill(exec.frontend_process.pid)
274
+ if single_port:
275
+ setup_frontend(Path.cwd())
276
+ backend_function, *args = commands[0]
277
+ exec.notify_app_running()
278
+ exec.notify_frontend(
279
+ f"http://0.0.0.0:{get_config().frontend_port}", backend_present=True
280
+ )
281
+ backend_function(*args, mount_frontend_compiled_app=True)
282
+ else:
283
+ # Start the frontend and backend.
284
+ with processes.run_concurrently_context(*commands):
285
+ # In dev mode, run the backend on the main thread.
286
+ if backend and backend_port and env == constants.Env.DEV:
287
+ backend_cmd(
288
+ backend_host,
289
+ int(backend_port),
290
+ config.loglevel.subprocess_level(),
291
+ frontend,
292
+ )
293
+ # The windows uvicorn bug workaround
294
+ # https://github.com/reflex-dev/reflex/issues/2335
295
+ if constants.IS_WINDOWS and exec.frontend_process:
296
+ # Sends SIGTERM in windows
297
+ exec.kill(exec.frontend_process.pid)
285
298
 
286
299
 
287
300
  @cli.command()
@@ -322,6 +335,12 @@ def _run(
322
335
  "--backend-host",
323
336
  help="Specify the backend host.",
324
337
  )
338
+ @click.option(
339
+ "--single-port",
340
+ is_flag=True,
341
+ help="Run both frontend and backend on the same port.",
342
+ default=False,
343
+ )
325
344
  def run(
326
345
  env: LITERAL_ENV,
327
346
  frontend_only: bool,
@@ -329,11 +348,29 @@ def run(
329
348
  frontend_port: int | None,
330
349
  backend_port: int | None,
331
350
  backend_host: str | None,
351
+ single_port: bool,
332
352
  ):
333
353
  """Run the app in the current directory."""
334
354
  if frontend_only and backend_only:
335
355
  console.error("Cannot use both --frontend-only and --backend-only options.")
336
- raise click.exceptions.Exit(1)
356
+ raise SystemExit(1)
357
+
358
+ if single_port:
359
+ if env != constants.Env.PROD.value:
360
+ console.error("--single-port can only be used with --env=PROD.")
361
+ raise click.exceptions.Exit(1)
362
+ if frontend_only or backend_only:
363
+ console.error(
364
+ "Cannot use --single-port with --frontend-only or --backend-only options."
365
+ )
366
+ raise click.exceptions.Exit(1)
367
+ if backend_port and frontend_port and backend_port != frontend_port:
368
+ console.error(
369
+ "When using --single-port, --backend-port and --frontend-port must be the same."
370
+ )
371
+ raise click.exceptions.Exit(1)
372
+ elif frontend_port and backend_port and frontend_port == backend_port:
373
+ single_port = True
337
374
 
338
375
  config = get_config()
339
376
 
@@ -352,6 +389,7 @@ def run(
352
389
  frontend_port,
353
390
  backend_port,
354
391
  backend_host,
392
+ single_port,
355
393
  )
356
394
 
357
395
 
reflex/state.py CHANGED
@@ -533,7 +533,7 @@ class BaseState(EvenMoreBasicBaseState):
533
533
  cls._check_overridden_computed_vars()
534
534
 
535
535
  new_backend_vars = {
536
- name: value
536
+ name: value if not isinstance(value, Field) else value.default_value()
537
537
  for name, value in list(cls.__dict__.items())
538
538
  if types.is_backend_base_variable(name, cls)
539
539
  }
@@ -1207,7 +1207,8 @@ class BaseState(EvenMoreBasicBaseState):
1207
1207
  The default value of the var or None.
1208
1208
  """
1209
1209
  try:
1210
- return getattr(cls, name)
1210
+ value = getattr(cls, name)
1211
+ return value if not isinstance(value, Field) else value.default_value()
1211
1212
  except AttributeError:
1212
1213
  try:
1213
1214
  return types.get_default_value_for_type(annotation_value)
reflex/utils/exec.py CHANGED
@@ -145,6 +145,18 @@ def kill(proc_pid: int):
145
145
  process.kill()
146
146
 
147
147
 
148
+ def notify_frontend(url: str, backend_present: bool):
149
+ """Output a string notifying where the frontend is running.
150
+
151
+ Args:
152
+ url: The URL where the frontend is running.
153
+ backend_present: Whether the backend is present.
154
+ """
155
+ console.print(
156
+ f"App running at: [bold green]{url.rstrip('/')}/[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
157
+ )
158
+
159
+
148
160
  def notify_backend():
149
161
  """Output a string notifying where the backend is running."""
150
162
  console.print(
@@ -210,9 +222,7 @@ def run_process_and_launch_url(
210
222
  if get_config().frontend_path != "":
211
223
  url = urljoin(url, get_config().frontend_path)
212
224
 
213
- console.print(
214
- f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
215
- )
225
+ notify_frontend(url, backend_present)
216
226
  if backend_present:
217
227
  notify_backend()
218
228
  first_run = False
@@ -249,6 +259,11 @@ def run_frontend(root: Path, port: str, backend_present: bool = True):
249
259
  )
250
260
 
251
261
 
262
+ def notify_app_running():
263
+ """Notify that the app is running."""
264
+ console.rule("[bold green]App Running")
265
+
266
+
252
267
  def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
253
268
  """Run the frontend.
254
269
 
@@ -264,7 +279,7 @@ def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
264
279
  # validate dependencies before run
265
280
  js_runtimes.validate_frontend_dependencies(init=False)
266
281
  # Run the frontend in production mode.
267
- console.rule("[bold green]App Running")
282
+ notify_app_running()
268
283
  run_process_and_launch_url(
269
284
  [*js_runtimes.get_js_package_executor(raise_on_none=True)[0], "run", "prod"],
270
285
  backend_present,
@@ -552,6 +567,7 @@ def run_backend_prod(
552
567
  port: int,
553
568
  loglevel: constants.LogLevel = constants.LogLevel.ERROR,
554
569
  frontend_present: bool = False,
570
+ mount_frontend_compiled_app: bool = False,
555
571
  ):
556
572
  """Run the backend.
557
573
 
@@ -560,10 +576,13 @@ def run_backend_prod(
560
576
  port: The app port
561
577
  loglevel: The log level.
562
578
  frontend_present: Whether the frontend is present.
579
+ mount_frontend_compiled_app: Whether to mount the compiled frontend app with the backend.
563
580
  """
564
581
  if not frontend_present:
565
582
  notify_backend()
566
583
 
584
+ environment.REFLEX_MOUNT_FRONTEND_COMPILED_APP.set(mount_frontend_compiled_app)
585
+
567
586
  if should_use_granian():
568
587
  run_granian_backend_prod(host, port, loglevel)
569
588
  else:
@@ -5,8 +5,6 @@ import random
5
5
  import re
6
6
  from pathlib import Path
7
7
 
8
- import click
9
-
10
8
  from reflex import constants
11
9
  from reflex.compiler import templates
12
10
  from reflex.config import Config, get_config
@@ -54,7 +52,7 @@ def initialize_requirements_txt() -> bool:
54
52
  True if the user has to update the requirements.txt file.
55
53
 
56
54
  Raises:
57
- Exit: If the requirements.txt file cannot be read or written to.
55
+ SystemExit: If the requirements.txt file cannot be read or written to.
58
56
  """
59
57
  requirements_file_path = Path(constants.RequirementsTxt.FILE)
60
58
  if (
@@ -72,8 +70,8 @@ def initialize_requirements_txt() -> bool:
72
70
  except UnicodeDecodeError:
73
71
  continue
74
72
  except Exception as e:
75
- console.error(f"Failed to read {requirements_file_path}.")
76
- raise click.exceptions.Exit(1) from e
73
+ console.error(f"Failed to read {requirements_file_path} due to {e}.")
74
+ raise SystemExit(1) from None
77
75
  else:
78
76
  return True
79
77
 
@@ -6,7 +6,6 @@ import tempfile
6
6
  from collections.abc import Sequence
7
7
  from pathlib import Path
8
8
 
9
- import click
10
9
  from packaging import version
11
10
 
12
11
  from reflex import constants
@@ -193,7 +192,7 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
193
192
  env: The environment variables to use.
194
193
 
195
194
  Raises:
196
- Exit: If the script fails to download.
195
+ SystemExit: If the script fails to download.
197
196
  """
198
197
  import httpx
199
198
 
@@ -206,7 +205,7 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
206
205
  console.error(
207
206
  f"Failed to download bun install script. You can install or update bun manually from https://bun.com \n{e}"
208
207
  )
209
- raise click.exceptions.Exit(1) from None
208
+ raise SystemExit(1) from None
210
209
 
211
210
  # Save the script to a temporary file.
212
211
  with tempfile.NamedTemporaryFile() as tempfile_file:
@@ -226,7 +225,7 @@ def install_bun():
226
225
 
227
226
  Raises:
228
227
  SystemPackageMissingError: If "unzip" is missing.
229
- Exit: If REFLEX_USE_NPM is set but Node.js is not installed.
228
+ SystemExit: If REFLEX_USE_NPM is set but Node.js is not installed.
230
229
  """
231
230
  if npm_escape_hatch():
232
231
  if get_node_version() is not None:
@@ -237,7 +236,7 @@ def install_bun():
237
236
  console.error(
238
237
  "REFLEX_USE_NPM is set, but Node.js is not installed. Please install Node.js to use npm."
239
238
  )
240
- raise click.exceptions.Exit(1)
239
+ raise SystemExit(1)
241
240
 
242
241
  bun_path = path_ops.get_bun_path()
243
242
 
@@ -290,7 +289,7 @@ def validate_bun(bun_path: Path | None = None):
290
289
  bun_path: The path to the bun executable. If None, the default bun path is used.
291
290
 
292
291
  Raises:
293
- Exit: If custom specified bun does not exist or does not meet requirements.
292
+ SystemExit: If custom specified bun does not exist or does not meet requirements.
294
293
  """
295
294
  bun_path = bun_path or path_ops.get_bun_path()
296
295
 
@@ -304,7 +303,7 @@ def validate_bun(bun_path: Path | None = None):
304
303
  console.error(
305
304
  "Failed to obtain bun version. Make sure the specified bun path in your config is correct."
306
305
  )
307
- raise click.exceptions.Exit(1)
306
+ raise SystemExit(1)
308
307
  if bun_version < version.parse(constants.Bun.MIN_VERSION):
309
308
  console.warn(
310
309
  f"Reflex requires bun version {constants.Bun.MIN_VERSION} or higher to run, but the detected version is "
@@ -320,20 +319,21 @@ def validate_frontend_dependencies(init: bool = True):
320
319
  init: whether running `reflex init`
321
320
 
322
321
  Raises:
323
- Exit: If the package manager is invalid.
322
+ SystemExit: If the package manager is invalid.
324
323
  """
325
324
  if not init:
326
325
  try:
327
326
  get_js_package_executor(raise_on_none=True)
328
327
  except FileNotFoundError as e:
329
- raise click.exceptions.Exit(1) from e
328
+ console.error(f"Failed to find a valid package manager due to {e}.")
329
+ raise SystemExit(1) from None
330
330
 
331
331
  if prefer_npm_over_bun() and not check_node_version():
332
332
  node_version = get_node_version()
333
333
  console.error(
334
334
  f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
335
335
  )
336
- raise click.exceptions.Exit(1)
336
+ raise SystemExit(1)
337
337
 
338
338
 
339
339
  def remove_existing_bun_installation():
@@ -15,7 +15,6 @@ from pathlib import Path
15
15
  from types import ModuleType
16
16
  from typing import NamedTuple
17
17
 
18
- import click
19
18
  from alembic.util.exc import CommandError
20
19
  from packaging import version
21
20
  from redis import Redis as RedisSync
@@ -444,7 +443,7 @@ def validate_app_name(app_name: str | None = None) -> str:
444
443
  The app name after validation.
445
444
 
446
445
  Raises:
447
- Exit: if the app directory name is reflex or if the name is not standard for a python package name.
446
+ SystemExit: if the app directory name is reflex or if the name is not standard for a python package name.
448
447
  """
449
448
  app_name = app_name if app_name else Path.cwd().name.replace("-", "_")
450
449
  # Make sure the app is not named "reflex".
@@ -452,14 +451,14 @@ def validate_app_name(app_name: str | None = None) -> str:
452
451
  console.error(
453
452
  f"The app directory cannot be named [bold]{constants.Reflex.MODULE_NAME}[/bold]."
454
453
  )
455
- raise click.exceptions.Exit(1)
454
+ raise SystemExit(1)
456
455
 
457
456
  # Make sure the app name is standard for a python package name.
458
457
  if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", app_name):
459
458
  console.error(
460
459
  "The app directory name must start with a letter and can contain letters, numbers, and underscores."
461
460
  )
462
- raise click.exceptions.Exit(1)
461
+ raise SystemExit(1)
463
462
 
464
463
  return app_name
465
464
 
@@ -499,13 +498,13 @@ def assert_in_reflex_dir():
499
498
  """Assert that the current working directory is the reflex directory.
500
499
 
501
500
  Raises:
502
- Exit: If the current working directory is not the reflex directory.
501
+ SystemExit: If the current working directory is not the reflex directory.
503
502
  """
504
503
  if not constants.Config.FILE.exists():
505
504
  console.error(
506
505
  f"[cyan]{constants.Config.FILE}[/cyan] not found. Move to the root folder of your project, or run [bold]{constants.Reflex.MODULE_NAME} init[/bold] to start a new project."
507
506
  )
508
- raise click.exceptions.Exit(1)
507
+ raise SystemExit(1)
509
508
 
510
509
 
511
510
  def needs_reinit() -> bool:
reflex/utils/processes.py CHANGED
@@ -15,7 +15,6 @@ from contextlib import closing
15
15
  from pathlib import Path
16
16
  from typing import Any, Literal, overload
17
17
 
18
- import click
19
18
  import rich.markup
20
19
  from redis.exceptions import RedisError
21
20
  from rich.progress import Progress
@@ -39,7 +38,7 @@ def get_num_workers() -> int:
39
38
  """Get the number of backend worker processes.
40
39
 
41
40
  Raises:
42
- Exit: If unable to connect to Redis.
41
+ SystemExit: If unable to connect to Redis.
43
42
 
44
43
  Returns:
45
44
  The number of backend worker processes.
@@ -50,7 +49,7 @@ def get_num_workers() -> int:
50
49
  redis_client.ping()
51
50
  except RedisError as re:
52
51
  console.error(f"Unable to connect to Redis: {re}")
53
- raise click.exceptions.Exit(1) from re
52
+ raise SystemExit(1) from None
54
53
  return (os.cpu_count() or 1) * 2 + 1
55
54
 
56
55
 
@@ -131,7 +130,7 @@ def handle_port(service_name: str, port: int, auto_increment: bool) -> int:
131
130
  The port to run the service on.
132
131
 
133
132
  Raises:
134
- Exit:when the port is in use.
133
+ SystemExit:when the port is in use.
135
134
  """
136
135
  console.debug(f"Checking if {service_name.capitalize()} port: {port} is in use.")
137
136
 
@@ -146,7 +145,7 @@ def handle_port(service_name: str, port: int, auto_increment: bool) -> int:
146
145
  f"Unable to bind to any port for {service_name}. "
147
146
  "Please check your network configuration."
148
147
  )
149
- raise click.exceptions.Exit(1)
148
+ raise SystemExit(1)
150
149
 
151
150
  console.debug(
152
151
  f"Checking if {service_name.capitalize()} port: {port} is in use for families: {families}."
@@ -172,7 +171,7 @@ def handle_port(service_name: str, port: int, auto_increment: bool) -> int:
172
171
  else:
173
172
  console.error(f"{service_name.capitalize()} port: {port} is already in use.")
174
173
 
175
- raise click.exceptions.Exit(1)
174
+ raise SystemExit(1)
176
175
 
177
176
 
178
177
  @overload
@@ -211,13 +210,13 @@ def new_process(
211
210
  Execute a child program in a new process.
212
211
 
213
212
  Raises:
214
- Exit: When attempting to run a command with a None value.
213
+ SystemExit: When attempting to run a command with a None value.
215
214
  """
216
215
  # Check for invalid command first.
217
216
  non_empty_args = list(filter(None, args)) if isinstance(args, list) else [args]
218
217
  if isinstance(args, list) and len(non_empty_args) != len(args):
219
218
  console.error(f"Invalid command: {args}")
220
- raise click.exceptions.Exit(1)
219
+ raise SystemExit(1)
221
220
 
222
221
  path_env: str = os.environ.get("PATH", "")
223
222
 
@@ -325,7 +324,7 @@ def stream_logs(
325
324
  The lines of the process output.
326
325
 
327
326
  Raises:
328
- Exit: If the process failed.
327
+ SystemExit: If the process failed.
329
328
  ValueError: If the process stdout pipe is closed, but the process remains running.
330
329
  """
331
330
  from reflex.utils import telemetry
@@ -376,7 +375,7 @@ def stream_logs(
376
375
  "NPM_CONFIG_REGISTRY environment variable. If TLS is the issue, and you know what "
377
376
  "you are doing, you can disable it by setting the SSL_NO_VERIFY environment variable."
378
377
  )
379
- raise click.exceptions.Exit(1)
378
+ raise SystemExit(1)
380
379
  for set_of_logs in (*prior_logs, tuple(logs)):
381
380
  for line in set_of_logs:
382
381
  console.error(line, end="")
@@ -384,7 +383,7 @@ def stream_logs(
384
383
  if analytics_enabled:
385
384
  telemetry.send("error", context=message)
386
385
  console.error("Run with [bold]--loglevel debug [/bold] for the full log.")
387
- raise click.exceptions.Exit(1)
386
+ raise SystemExit(1)
388
387
 
389
388
 
390
389
  def show_logs(message: str, process: subprocess.Popen):
reflex/utils/rename.py CHANGED
@@ -4,8 +4,6 @@ import re
4
4
  import sys
5
5
  from pathlib import Path
6
6
 
7
- import click
8
-
9
7
  from reflex import constants
10
8
  from reflex.config import get_config
11
9
  from reflex.utils import console
@@ -57,7 +55,7 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
57
55
  loglevel: The log level to use.
58
56
 
59
57
  Raises:
60
- Exit: If the command is not ran in the root dir or the app module cannot be imported.
58
+ SystemExit: If the command is not ran in the root dir or the app module cannot be imported.
61
59
  """
62
60
  # Set the log level.
63
61
  console.set_log_level(loglevel)
@@ -66,7 +64,7 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
66
64
  console.error(
67
65
  "No rxconfig.py found. Make sure you are in the root directory of your app."
68
66
  )
69
- raise click.exceptions.Exit(1)
67
+ raise SystemExit(1)
70
68
 
71
69
  sys.path.insert(0, str(Path.cwd()))
72
70
 
@@ -74,7 +72,7 @@ def rename_app(new_app_name: str, loglevel: constants.LogLevel):
74
72
  module_path = get_module_path(config.module)
75
73
  if module_path is None:
76
74
  console.error(f"Could not find module {config.module}.")
77
- raise click.exceptions.Exit(1)
75
+ raise SystemExit(1)
78
76
 
79
77
  console.info(f"Renaming app directory to {new_app_name}.")
80
78
  process_directory(
@@ -413,8 +413,8 @@ def serialize_decimal(value: decimal.Decimal) -> float:
413
413
  return float(value)
414
414
 
415
415
 
416
- @serializer(to=dict)
417
- def serialize_color(color: Color) -> dict:
416
+ @serializer(to=str)
417
+ def serialize_color(color: Color) -> str:
418
418
  """Serialize a color.
419
419
 
420
420
  Args:
@@ -423,11 +423,7 @@ def serialize_color(color: Color) -> dict:
423
423
  Returns:
424
424
  The serialized color.
425
425
  """
426
- return {
427
- "color": color.color,
428
- "shade": color.shade,
429
- "alpha": color.alpha,
430
- }
426
+ return color.__format__("")
431
427
 
432
428
 
433
429
  with contextlib.suppress(ImportError):