reflex 0.4.6a4__py3-none-any.whl → 0.4.7__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 (69) hide show
  1. reflex/.templates/apps/blank/code/blank.py +1 -0
  2. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +1 -1
  3. reflex/.templates/jinja/custom_components/src.py.jinja2 +8 -8
  4. reflex/.templates/jinja/web/pages/index.js.jinja2 +0 -4
  5. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +2 -6
  6. reflex/.templates/web/utils/state.js +6 -1
  7. reflex/__init__.py +2 -0
  8. reflex/__init__.pyi +2 -0
  9. reflex/app.py +12 -16
  10. reflex/app.pyi +2 -0
  11. reflex/compiler/compiler.py +10 -11
  12. reflex/compiler/utils.py +3 -3
  13. reflex/components/chakra/forms/pininput.py +2 -1
  14. reflex/components/component.py +71 -104
  15. reflex/components/core/banner.py +1 -1
  16. reflex/components/core/upload.py +2 -1
  17. reflex/components/datadisplay/__init__.py +1 -0
  18. reflex/components/datadisplay/logo.py +49 -0
  19. reflex/components/el/elements/forms.py +7 -4
  20. reflex/components/el/elements/forms.pyi +0 -1
  21. reflex/components/lucide/icon.py +3 -2
  22. reflex/components/lucide/icon.pyi +2 -2
  23. reflex/components/markdown/markdown.py +10 -6
  24. reflex/components/markdown/markdown.pyi +0 -3
  25. reflex/components/radix/themes/components/select.py +10 -3
  26. reflex/config.py +1 -1
  27. reflex/config.pyi +1 -1
  28. reflex/constants/base.py +4 -5
  29. reflex/constants/base.pyi +94 -0
  30. reflex/constants/compiler.py +8 -0
  31. reflex/custom_components/custom_components.py +33 -38
  32. reflex/experimental/__init__.py +14 -0
  33. reflex/experimental/hooks.py +75 -0
  34. reflex/page.py +1 -1
  35. reflex/reflex.py +18 -32
  36. reflex/style.py +4 -4
  37. reflex/testing.py +1 -1
  38. reflex/utils/console.py +6 -4
  39. reflex/utils/exec.py +17 -1
  40. reflex/utils/export.py +0 -3
  41. reflex/utils/prerequisites.py +243 -43
  42. reflex/utils/processes.py +6 -1
  43. reflex/utils/telemetry.py +14 -2
  44. reflex/utils/types.py +3 -2
  45. reflex/vars.py +6 -6
  46. reflex/vars.pyi +2 -1
  47. {reflex-0.4.6a4.dist-info → reflex-0.4.7.dist-info}/METADATA +15 -10
  48. {reflex-0.4.6a4.dist-info → reflex-0.4.7.dist-info}/RECORD +51 -65
  49. {reflex-0.4.6a4.dist-info → reflex-0.4.7.dist-info}/WHEEL +1 -1
  50. reflex/.templates/apps/sidebar/README.md +0 -69
  51. reflex/.templates/apps/sidebar/assets/favicon.ico +0 -0
  52. reflex/.templates/apps/sidebar/assets/github.svg +0 -10
  53. reflex/.templates/apps/sidebar/assets/logo.svg +0 -68
  54. reflex/.templates/apps/sidebar/assets/paneleft.svg +0 -13
  55. reflex/.templates/apps/sidebar/assets/reflex_black.svg +0 -37
  56. reflex/.templates/apps/sidebar/assets/reflex_white.svg +0 -8
  57. reflex/.templates/apps/sidebar/code/__init__.py +0 -1
  58. reflex/.templates/apps/sidebar/code/components/__init__.py +0 -0
  59. reflex/.templates/apps/sidebar/code/components/sidebar.py +0 -152
  60. reflex/.templates/apps/sidebar/code/pages/__init__.py +0 -3
  61. reflex/.templates/apps/sidebar/code/pages/dashboard.py +0 -22
  62. reflex/.templates/apps/sidebar/code/pages/index.py +0 -18
  63. reflex/.templates/apps/sidebar/code/pages/settings.py +0 -61
  64. reflex/.templates/apps/sidebar/code/sidebar.py +0 -16
  65. reflex/.templates/apps/sidebar/code/styles.py +0 -60
  66. reflex/.templates/apps/sidebar/code/templates/__init__.py +0 -1
  67. reflex/.templates/apps/sidebar/code/templates/template.py +0 -145
  68. {reflex-0.4.6a4.dist-info → reflex-0.4.7.dist-info}/LICENSE +0 -0
  69. {reflex-0.4.6a4.dist-info → reflex-0.4.7.dist-info}/entry_points.txt +0 -0
reflex/utils/exec.py CHANGED
@@ -7,6 +7,7 @@ import json
7
7
  import os
8
8
  import platform
9
9
  import re
10
+ import subprocess
10
11
  import sys
11
12
  from pathlib import Path
12
13
  from urllib.parse import urljoin
@@ -18,6 +19,9 @@ from reflex.config import get_config
18
19
  from reflex.utils import console, path_ops
19
20
  from reflex.utils.watch import AssetFolderWatch
20
21
 
22
+ # For uvicorn windows bug fix (#2335)
23
+ frontend_process = None
24
+
21
25
 
22
26
  def start_watching_assets_folder(root):
23
27
  """Start watching assets folder.
@@ -66,6 +70,9 @@ def kill(proc_pid: int):
66
70
  process.kill()
67
71
 
68
72
 
73
+ # run_process_and_launch_url is assumed to be used
74
+ # only to launch the frontend
75
+ # If this is not the case, might have to change the logic
69
76
  def run_process_and_launch_url(run_command: list[str]):
70
77
  """Run the process and launch the URL.
71
78
 
@@ -81,9 +88,17 @@ def run_process_and_launch_url(run_command: list[str]):
81
88
 
82
89
  while True:
83
90
  if process is None:
91
+ kwargs = {}
92
+ if constants.IS_WINDOWS:
93
+ kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
84
94
  process = processes.new_process(
85
- run_command, cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS
95
+ run_command,
96
+ cwd=constants.Dirs.WEB,
97
+ shell=constants.IS_WINDOWS,
98
+ **kwargs,
86
99
  )
100
+ global frontend_process
101
+ frontend_process = process
87
102
  if process.stdout:
88
103
  for line in processes.stream_logs("Starting frontend", process):
89
104
  match = re.search(constants.Next.FRONTEND_LISTENING_REGEX, line)
@@ -175,6 +190,7 @@ def run_backend(
175
190
  log_level=loglevel.value,
176
191
  reload=True,
177
192
  reload_dirs=[config.app_name],
193
+ reload_excludes=[constants.Dirs.WEB],
178
194
  )
179
195
 
180
196
 
reflex/utils/export.py CHANGED
@@ -46,9 +46,6 @@ def export(
46
46
  # Show system info
47
47
  exec.output_system_info()
48
48
 
49
- # Check that the app is initialized.
50
- prerequisites.check_initialized(frontend=frontend)
51
-
52
49
  # Compile the app in production mode and export it.
53
50
  console.rule("[bold]Compiling production app and preparing for export.")
54
51
 
@@ -10,6 +10,7 @@ import os
10
10
  import platform
11
11
  import random
12
12
  import re
13
+ import shutil
13
14
  import stat
14
15
  import sys
15
16
  import tempfile
@@ -30,6 +31,7 @@ from redis.asyncio import Redis
30
31
 
31
32
  import reflex
32
33
  from reflex import constants, model
34
+ from reflex.base import Base
33
35
  from reflex.compiler import templates
34
36
  from reflex.config import Config, get_config
35
37
  from reflex.utils import console, path_ops, processes
@@ -37,6 +39,15 @@ from reflex.utils import console, path_ops, processes
37
39
  CURRENTLY_INSTALLING_NODE = False
38
40
 
39
41
 
42
+ class Template(Base):
43
+ """A template for a Reflex app."""
44
+
45
+ name: str
46
+ description: str
47
+ code_url: str
48
+ demo_url: str
49
+
50
+
40
51
  def check_latest_package_version(package_name: str):
41
52
  """Check if the latest version of the package is installed.
42
53
 
@@ -407,17 +418,42 @@ def initialize_requirements_txt():
407
418
  console.info(f"Unable to check {fp} for reflex dependency.")
408
419
 
409
420
 
410
- def initialize_app_directory(app_name: str, template: constants.Templates.Kind):
421
+ def initialize_app_directory(
422
+ app_name: str,
423
+ template_name: str = constants.Templates.DEFAULT,
424
+ template_code_dir_name: str | None = None,
425
+ template_dir: Path | None = None,
426
+ ):
411
427
  """Initialize the app directory on reflex init.
412
428
 
413
429
  Args:
414
430
  app_name: The name of the app.
415
- template: The template to use.
431
+ template_name: The name of the template to use.
432
+ template_code_dir_name: The name of the code directory in the template.
433
+ template_dir: The directory of the template source files.
434
+
435
+ Raises:
436
+ Exit: If template_name, template_code_dir_name, template_dir combination is not supported.
416
437
  """
417
438
  console.log("Initializing the app directory.")
418
439
 
419
- # Copy the template to the current directory.
420
- template_dir = Path(constants.Templates.Dirs.BASE, "apps", template.value)
440
+ # By default, use the blank template from local assets.
441
+ if template_name == constants.Templates.DEFAULT:
442
+ if template_code_dir_name is not None or template_dir is not None:
443
+ console.error(
444
+ f"Only {template_name=} should be provided, got {template_code_dir_name=}, {template_dir=}."
445
+ )
446
+ raise typer.Exit(1)
447
+ template_code_dir_name = constants.Templates.Dirs.CODE
448
+ template_dir = Path(constants.Templates.Dirs.BASE, "apps", template_name)
449
+ else:
450
+ if template_code_dir_name is None or template_dir is None:
451
+ console.error(
452
+ f"For `{template_name}` template, `template_code_dir_name` and `template_dir` should both be provided."
453
+ )
454
+ raise typer.Exit(1)
455
+
456
+ console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
421
457
 
422
458
  # Remove all pyc and __pycache__ dirs in template directory.
423
459
  for pyc_file in template_dir.glob("**/*.pyc"):
@@ -430,16 +466,16 @@ def initialize_app_directory(app_name: str, template: constants.Templates.Kind):
430
466
  path_ops.cp(str(file), file.name)
431
467
 
432
468
  # Rename the template app to the app name.
433
- path_ops.mv(constants.Templates.Dirs.CODE, app_name)
469
+ path_ops.mv(template_code_dir_name, app_name)
434
470
  path_ops.mv(
435
- os.path.join(app_name, template_dir.name + constants.Ext.PY),
471
+ os.path.join(app_name, template_name + constants.Ext.PY),
436
472
  os.path.join(app_name, app_name + constants.Ext.PY),
437
473
  )
438
474
 
439
475
  # Fix up the imports.
440
476
  path_ops.find_replace(
441
477
  app_name,
442
- f"from {constants.Templates.Dirs.CODE}",
478
+ f"from {template_name}",
443
479
  f"from {app_name}",
444
480
  )
445
481
 
@@ -802,43 +838,42 @@ def install_frontend_packages(packages: set[str], config: Config):
802
838
  )
803
839
 
804
840
 
805
- def check_initialized(frontend: bool = True):
806
- """Check that the app is initialized.
841
+ def needs_reinit(frontend: bool = True) -> bool:
842
+ """Check if an app needs to be reinitialized.
807
843
 
808
844
  Args:
809
845
  frontend: Whether to check if the frontend is initialized.
810
846
 
847
+ Returns:
848
+ Whether the app needs to be reinitialized.
849
+
811
850
  Raises:
812
851
  Exit: If the app is not initialized.
813
852
  """
814
- has_config = os.path.exists(constants.Config.FILE)
815
- has_reflex_dir = not frontend or os.path.exists(constants.Reflex.DIR)
816
- has_web_dir = not frontend or os.path.exists(constants.Dirs.WEB)
817
-
818
- # Check if the app is initialized.
819
- if not (has_config and has_reflex_dir and has_web_dir):
853
+ if not os.path.exists(constants.Config.FILE):
820
854
  console.error(
821
- f"The app is not initialized. Run [bold]{constants.Reflex.MODULE_NAME} init[/bold] first."
855
+ 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."
822
856
  )
823
857
  raise typer.Exit(1)
824
858
 
825
- # Check that the template is up to date.
826
- if frontend and not is_latest_template():
827
- console.error(
828
- "The base app template has updated. Run [bold]reflex init[/bold] again."
829
- )
830
- raise typer.Exit(1)
859
+ # Don't need to reinit if not running in frontend mode.
860
+ if not frontend:
861
+ return False
862
+
863
+ # Make sure the .reflex directory exists.
864
+ if not os.path.exists(constants.Reflex.DIR):
865
+ return True
866
+
867
+ # Make sure the .web directory exists in frontend mode.
868
+ if not os.path.exists(constants.Dirs.WEB):
869
+ return True
831
870
 
832
- # Print a warning for Windows users.
833
871
  if constants.IS_WINDOWS:
834
872
  console.warn(
835
873
  """Windows Subsystem for Linux (WSL) is recommended for improving initial install times."""
836
874
  )
837
- if sys.version_info >= (3, 12):
838
- console.warn(
839
- "Python 3.12 on Windows has known issues with hot reload (reflex-dev/reflex#2335). "
840
- "Python 3.11 is recommended with this release of Reflex."
841
- )
875
+ # No need to reinitialize if the app is already initialized.
876
+ return False
842
877
 
843
878
 
844
879
  def is_latest_template() -> bool:
@@ -1004,33 +1039,35 @@ def check_schema_up_to_date():
1004
1039
  )
1005
1040
 
1006
1041
 
1007
- def prompt_for_template() -> constants.Templates.Kind:
1042
+ def prompt_for_template(templates: list[Template]) -> str:
1008
1043
  """Prompt the user to specify a template.
1009
1044
 
1045
+ Args:
1046
+ templates: The templates to choose from.
1047
+
1010
1048
  Returns:
1011
- The template the user selected.
1049
+ The template name the user selects.
1012
1050
  """
1013
- # Show the user the URLs of each temlate to preview.
1051
+ # Show the user the URLs of each template to preview.
1014
1052
  console.print("\nGet started with a template:")
1015
- console.print("blank (https://blank-template.reflex.run) - A minimal template.")
1016
- console.print(
1017
- "sidebar (https://sidebar-template.reflex.run) - A template with a sidebar to navigate pages."
1018
- )
1019
- console.print("")
1020
1053
 
1021
1054
  # Prompt the user to select a template.
1055
+ id_to_name = {
1056
+ str(idx): f"{template.name} ({template.demo_url}) - {template.description}"
1057
+ for idx, template in enumerate(templates)
1058
+ }
1059
+ for id in range(len(id_to_name)):
1060
+ console.print(f"({id}) {id_to_name[str(id)]}")
1061
+
1022
1062
  template = console.ask(
1023
1063
  "Which template would you like to use?",
1024
- choices=[
1025
- template.value
1026
- for template in constants.Templates.Kind
1027
- if template.value != "demo"
1028
- ],
1029
- default=constants.Templates.Kind.BLANK.value,
1064
+ choices=[str(i) for i in range(len(id_to_name))],
1065
+ show_choices=False,
1066
+ default="0",
1030
1067
  )
1031
1068
 
1032
1069
  # Return the template.
1033
- return constants.Templates.Kind(template)
1070
+ return templates[int(template)].name
1034
1071
 
1035
1072
 
1036
1073
  def should_show_rx_chakra_migration_instructions() -> bool:
@@ -1183,3 +1220,166 @@ def migrate_to_reflex():
1183
1220
  for old, new in updates.items():
1184
1221
  line = line.replace(old, new)
1185
1222
  print(line, end="")
1223
+
1224
+
1225
+ def fetch_app_templates() -> dict[str, Template]:
1226
+ """Fetch the list of app templates from the Reflex backend server.
1227
+
1228
+ Returns:
1229
+ The name and download URL as a dictionary.
1230
+ """
1231
+ config = get_config()
1232
+ if not config.cp_backend_url:
1233
+ console.info(
1234
+ "Skip fetching App templates. No backend URL is specified in the config."
1235
+ )
1236
+ return {}
1237
+ try:
1238
+ response = httpx.get(
1239
+ f"{config.cp_backend_url}{constants.Templates.APP_TEMPLATES_ROUTE}"
1240
+ )
1241
+ response.raise_for_status()
1242
+ return {
1243
+ template["name"]: Template.parse_obj(template)
1244
+ for template in response.json()
1245
+ }
1246
+ except httpx.HTTPError as ex:
1247
+ console.info(f"Failed to fetch app templates: {ex}")
1248
+ return {}
1249
+ except (TypeError, KeyError, json.JSONDecodeError) as tkje:
1250
+ console.info(f"Unable to process server response for app templates: {tkje}")
1251
+ return {}
1252
+
1253
+
1254
+ def create_config_init_app_from_remote_template(
1255
+ app_name: str,
1256
+ template_url: str,
1257
+ ):
1258
+ """Create new rxconfig and initialize app using a remote template.
1259
+
1260
+ Args:
1261
+ app_name: The name of the app.
1262
+ template_url: The path to the template source code as a zip file.
1263
+
1264
+ Raises:
1265
+ Exit: If any download, file operations fail or unexpected zip file format.
1266
+
1267
+ """
1268
+ # Create a temp directory for the zip download.
1269
+ try:
1270
+ temp_dir = tempfile.mkdtemp()
1271
+ except OSError as ose:
1272
+ console.error(f"Failed to create temp directory for download: {ose}")
1273
+ raise typer.Exit(1) from ose
1274
+
1275
+ # Use httpx GET with redirects to download the zip file.
1276
+ zip_file_path = Path(temp_dir) / "template.zip"
1277
+ try:
1278
+ # Note: following redirects can be risky. We only allow this for reflex built templates at the moment.
1279
+ response = httpx.get(template_url, follow_redirects=True)
1280
+ console.debug(f"Server responded download request: {response}")
1281
+ response.raise_for_status()
1282
+ except httpx.HTTPError as he:
1283
+ console.error(f"Failed to download the template: {he}")
1284
+ raise typer.Exit(1) from he
1285
+ try:
1286
+ with open(zip_file_path, "wb") as f:
1287
+ f.write(response.content)
1288
+ console.debug(f"Downloaded the zip to {zip_file_path}")
1289
+ except OSError as ose:
1290
+ console.error(f"Unable to write the downloaded zip to disk {ose}")
1291
+ raise typer.Exit(1) from ose
1292
+
1293
+ # Create a temp directory for the zip extraction.
1294
+ try:
1295
+ unzip_dir = Path(tempfile.mkdtemp())
1296
+ except OSError as ose:
1297
+ console.error(f"Failed to create temp directory for extracting zip: {ose}")
1298
+ raise typer.Exit(1) from ose
1299
+ try:
1300
+ zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
1301
+ # The zip file downloaded from github looks like:
1302
+ # repo-name-branch/**/*, so we need to remove the top level directory.
1303
+ if len(subdirs := os.listdir(unzip_dir)) != 1:
1304
+ console.error(f"Expected one directory in the zip, found {subdirs}")
1305
+ raise typer.Exit(1)
1306
+ template_dir = unzip_dir / subdirs[0]
1307
+ console.debug(f"Template folder is located at {template_dir}")
1308
+ except Exception as uze:
1309
+ console.error(f"Failed to unzip the template: {uze}")
1310
+ raise typer.Exit(1) from uze
1311
+
1312
+ # Move the rxconfig file here first.
1313
+ path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE)
1314
+ new_config = get_config(reload=True)
1315
+
1316
+ # Get the template app's name from rxconfig in case it is different than
1317
+ # the source code repo name on github.
1318
+ template_name = new_config.app_name
1319
+
1320
+ create_config(app_name)
1321
+ initialize_app_directory(
1322
+ app_name,
1323
+ template_name=template_name,
1324
+ template_code_dir_name=template_name,
1325
+ template_dir=template_dir,
1326
+ )
1327
+
1328
+ # Clean up the temp directories.
1329
+ shutil.rmtree(temp_dir)
1330
+ shutil.rmtree(unzip_dir)
1331
+
1332
+
1333
+ def initialize_app(app_name: str, template: str | None = None):
1334
+ """Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
1335
+
1336
+ Args:
1337
+ app_name: The name of the app.
1338
+ template: The name of the template to use.
1339
+
1340
+ Raises:
1341
+ Exit: If template is directly provided in the command flag and is invalid.
1342
+ """
1343
+ # Local imports to avoid circular imports.
1344
+ from reflex.utils import telemetry
1345
+
1346
+ # Check if the app is already initialized.
1347
+ if os.path.exists(constants.Config.FILE):
1348
+ telemetry.send("reinit")
1349
+ return
1350
+
1351
+ # Get the available templates
1352
+ templates: dict[str, Template] = fetch_app_templates()
1353
+
1354
+ # Prompt for a template if not provided.
1355
+ if template is None and len(templates) > 0:
1356
+ template = prompt_for_template(list(templates.values()))
1357
+ elif template is None:
1358
+ template = constants.Templates.DEFAULT
1359
+ assert template is not None
1360
+
1361
+ # If the blank template is selected, create a blank app.
1362
+ if template == constants.Templates.DEFAULT:
1363
+ # Default app creation behavior: a blank app.
1364
+ create_config(app_name)
1365
+ initialize_app_directory(app_name)
1366
+ else:
1367
+ # Fetch App templates from the backend server.
1368
+ console.debug(f"Available templates: {templates}")
1369
+
1370
+ # If user selects a template, it needs to exist.
1371
+ if template in templates:
1372
+ template_url = templates[template].code_url
1373
+ else:
1374
+ # Check if the template is a github repo.
1375
+ if template.startswith("https://github.com"):
1376
+ template_url = f"{template.strip('/')}/archive/main.zip"
1377
+ else:
1378
+ console.error(f"Template `{template}` not found.")
1379
+ raise typer.Exit(1)
1380
+ create_config_init_app_from_remote_template(
1381
+ app_name=app_name,
1382
+ template_url=template_url,
1383
+ )
1384
+
1385
+ telemetry.send("init")
reflex/utils/processes.py CHANGED
@@ -14,6 +14,7 @@ import psutil
14
14
  import typer
15
15
  from redis.exceptions import RedisError
16
16
 
17
+ from reflex import constants
17
18
  from reflex.utils import console, path_ops, prerequisites
18
19
 
19
20
 
@@ -227,7 +228,11 @@ def stream_logs(message: str, process: subprocess.Popen, progress=None):
227
228
  yield line
228
229
 
229
230
  # Check if the process failed (not printing the logs for SIGINT).
230
- if process.returncode not in [0, -2]:
231
+
232
+ # Windows uvicorn bug
233
+ # https://github.com/reflex-dev/reflex/issues/2335
234
+ accepted_return_codes = [0, -2, 15] if constants.IS_WINDOWS else [0, -2]
235
+ if process.returncode not in accepted_return_codes:
231
236
  console.error(f"{message} failed with exit code {process.returncode}")
232
237
  for line in logs:
233
238
  console.error(line, end="")
reflex/utils/telemetry.py CHANGED
@@ -4,7 +4,13 @@ from __future__ import annotations
4
4
 
5
5
  import multiprocessing
6
6
  import platform
7
- from datetime import datetime
7
+
8
+ try:
9
+ from datetime import UTC, datetime
10
+ except ImportError:
11
+ from datetime import datetime
12
+
13
+ UTC = None
8
14
 
9
15
  import httpx
10
16
  import psutil
@@ -99,6 +105,12 @@ def _prepare_event(event: str) -> dict:
99
105
  )
100
106
  return {}
101
107
 
108
+ if UTC is None:
109
+ # for python 3.8, 3.9 & 3.10
110
+ stamp = datetime.utcnow().isoformat()
111
+ else:
112
+ # for python 3.11 & 3.12
113
+ stamp = datetime.now(UTC).isoformat()
102
114
  return {
103
115
  "api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
104
116
  "event": event,
@@ -111,7 +123,7 @@ def _prepare_event(event: str) -> dict:
111
123
  "cpu_count": get_cpu_count(),
112
124
  "memory": get_memory(),
113
125
  },
114
- "timestamp": datetime.utcnow().isoformat(),
126
+ "timestamp": stamp,
115
127
  }
116
128
 
117
129
 
reflex/utils/types.py CHANGED
@@ -416,11 +416,12 @@ def validate_literal(key: str, value: Any, expected_type: Type, comp_name: str):
416
416
  ):
417
417
  allowed_values = expected_type.__args__
418
418
  if value not in allowed_values:
419
- value_str = ",".join(
419
+ allowed_value_str = ",".join(
420
420
  [str(v) if not isinstance(v, str) else f"'{v}'" for v in allowed_values]
421
421
  )
422
+ value_str = f"'{value}'" if isinstance(value, str) else value
422
423
  raise ValueError(
423
- f"prop value for {str(key)} of the `{comp_name}` component should be one of the following: {value_str}. Got '{value}' instead"
424
+ f"prop value for {str(key)} of the `{comp_name}` component should be one of the following: {allowed_value_str}. Got {value_str} instead"
424
425
  )
425
426
 
426
427
 
reflex/vars.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Define a state var."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import contextlib
@@ -21,7 +22,6 @@ from typing import (
21
22
  List,
22
23
  Literal,
23
24
  Optional,
24
- Set,
25
25
  Tuple,
26
26
  Type,
27
27
  Union,
@@ -119,7 +119,7 @@ class VarData(Base):
119
119
  imports: ImportDict = {}
120
120
 
121
121
  # Hooks that need to be present in the component to render this var
122
- hooks: Set[str] = set()
122
+ hooks: Dict[str, None] = {}
123
123
 
124
124
  # Positions of interpolated strings. This is used by the decoder to figure
125
125
  # out where the interpolations are and only escape the non-interpolated
@@ -138,7 +138,7 @@ class VarData(Base):
138
138
  """
139
139
  state = ""
140
140
  _imports = {}
141
- hooks = set()
141
+ hooks = {}
142
142
  interpolations = []
143
143
  for var_data in others:
144
144
  if var_data is None:
@@ -182,7 +182,7 @@ class VarData(Base):
182
182
  # not part of the vardata itself.
183
183
  return (
184
184
  self.state == other.state
185
- and self.hooks == other.hooks
185
+ and self.hooks.keys() == other.hooks.keys()
186
186
  and imports.collapse_imports(self.imports)
187
187
  == imports.collapse_imports(other.imports)
188
188
  )
@@ -200,7 +200,7 @@ class VarData(Base):
200
200
  lib: [import_var.dict() for import_var in import_vars]
201
201
  for lib, import_vars in self.imports.items()
202
202
  },
203
- "hooks": list(self.hooks),
203
+ "hooks": self.hooks,
204
204
  }
205
205
 
206
206
 
@@ -1659,7 +1659,7 @@ class Var:
1659
1659
  hooks={
1660
1660
  "const {0} = useContext(StateContexts.{0})".format(
1661
1661
  format.format_state_name(state_name)
1662
- )
1662
+ ): None
1663
1663
  },
1664
1664
  imports={
1665
1665
  f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
reflex/vars.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  """ Generated with stubgen from mypy, then manually edited, do not regen."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from dataclasses import dataclass
@@ -35,7 +36,7 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]: ...
35
36
  class VarData(Base):
36
37
  state: str
37
38
  imports: dict[str, set[ImportVar]]
38
- hooks: set[str]
39
+ hooks: Dict[str, None]
39
40
  interpolations: List[Tuple[int, int]]
40
41
  @classmethod
41
42
  def merge(cls, *others: VarData | None) -> VarData | None: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: reflex
3
- Version: 0.4.6a4
3
+ Version: 0.4.7
4
4
  Summary: Web apps in pure Python.
5
5
  Home-page: https://reflex.dev
6
6
  License: Apache-2.0
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Requires-Dist: alembic (>=1.11.1,<2.0)
19
20
  Requires-Dist: build (>=1.0.3,<2.0)
20
21
  Requires-Dist: charset-normalizer (>=3.3.2,<4.0)
@@ -74,6 +75,18 @@ Description-Content-Type: text/markdown
74
75
  [English](https://github.com/reflex-dev/reflex/blob/main/README.md) | [简体中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_cn/README.md) | [繁體中文](https://github.com/reflex-dev/reflex/blob/main/docs/zh/zh_tw/README.md) | [Türkçe](https://github.com/reflex-dev/reflex/blob/main/docs/tr/README.md) | [हिंदी](https://github.com/reflex-dev/reflex/blob/main/docs/in/README.md) | [Português (Brasil)](https://github.com/reflex-dev/reflex/blob/main/docs/pt/pt_br/README.md) | [Italiano](https://github.com/reflex-dev/reflex/blob/main/docs/it/README.md) | [Español](https://github.com/reflex-dev/reflex/blob/main/docs/es/README.md) | [한국어](https://github.com/reflex-dev/reflex/blob/main/docs/kr/README.md)
75
76
 
76
77
  ---
78
+
79
+ # Reflex
80
+
81
+ Reflex is a library to build full-stack web apps in pure Python.
82
+
83
+ Key features:
84
+ * **Pure Python** - Write your app's frontend and backend all in Python, no need to learn Javascript.
85
+ * **Full Flexibility** - Reflex is easy to get started with, but can also scale to complex apps.
86
+ * **Deploy Instantly** - After building, deploy your app with a [single command](https://reflex.dev/docs/hosting/deploy-quick-start/) or host it on your own server.
87
+
88
+ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architecture/#the-reflex-architecture) to learn how Reflex works under the hood.
89
+
77
90
  ## ⚙️ Installation
78
91
 
79
92
  Open a terminal and run (Requires Python 3.8+):
@@ -263,19 +276,11 @@ You can create a multi-page app by adding more pages.
263
276
  </div>
264
277
 
265
278
 
266
-
267
-
268
-
269
279
  ## ✅ Status
270
280
 
271
281
  Reflex launched in December 2022 with the name Pynecone.
272
282
 
273
- As of July 2023, we are in the **Public Beta** stage.
274
-
275
- - :white_check_mark: **Public Alpha**: Anyone can install and use Reflex. There may be issues, but we are working to resolve them actively.
276
- - :large_orange_diamond: **Public Beta**: Stable enough for non-enterprise use-cases.
277
- - **Public Hosting Beta**: _Optionally_, deploy and host your apps on Reflex!
278
- - **Public**: Reflex is production ready.
283
+ As of February 2024, our hosting service is in alpha! During this time anyone can deploy their apps for free. See our [roadmap](https://github.com/reflex-dev/reflex/issues/2727) to see what's planned.
279
284
 
280
285
  Reflex has new releases and features coming every week! Make sure to :star: star and :eyes: watch this repository to stay up to date.
281
286