reflex 0.6.3a4__py3-none-any.whl → 0.6.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 (96) hide show
  1. reflex/.templates/jinja/web/pages/_app.js.jinja2 +2 -2
  2. reflex/.templates/jinja/web/utils/context.js.jinja2 +3 -1
  3. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -3
  4. reflex/.templates/web/components/shiki/code.js +29 -0
  5. reflex/.templates/web/jsconfig.json +2 -1
  6. reflex/.templates/web/utils/state.js +6 -4
  7. reflex/__init__.py +6 -3
  8. reflex/__init__.pyi +4 -3
  9. reflex/app.py +6 -5
  10. reflex/compiler/compiler.py +6 -7
  11. reflex/compiler/utils.py +8 -1
  12. reflex/components/base/error_boundary.py +37 -24
  13. reflex/components/base/error_boundary.pyi +8 -7
  14. reflex/components/component.py +9 -4
  15. reflex/components/core/banner.py +2 -2
  16. reflex/components/core/client_side_routing.py +1 -1
  17. reflex/components/core/clipboard.py +1 -1
  18. reflex/components/core/clipboard.pyi +1 -1
  19. reflex/components/core/cond.py +1 -1
  20. reflex/components/core/debounce.py +5 -1
  21. reflex/components/core/upload.py +7 -9
  22. reflex/components/core/upload.pyi +2 -2
  23. reflex/components/datadisplay/code.py +1 -1
  24. reflex/components/datadisplay/dataeditor.py +83 -18
  25. reflex/components/datadisplay/dataeditor.pyi +67 -15
  26. reflex/components/datadisplay/shiki_code_block.py +813 -0
  27. reflex/components/datadisplay/shiki_code_block.pyi +2211 -0
  28. reflex/components/dynamic.py +3 -3
  29. reflex/components/el/elements/forms.py +37 -23
  30. reflex/components/el/elements/forms.pyi +7 -4
  31. reflex/components/markdown/markdown.py +12 -1
  32. reflex/components/moment/moment.pyi +1 -1
  33. reflex/components/radix/primitives/drawer.pyi +2 -2
  34. reflex/components/radix/themes/base.py +2 -2
  35. reflex/components/radix/themes/color_mode.pyi +1 -1
  36. reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
  37. reflex/components/radix/themes/components/checkbox.pyi +3 -3
  38. reflex/components/radix/themes/components/context_menu.pyi +1 -1
  39. reflex/components/radix/themes/components/dialog.pyi +2 -2
  40. reflex/components/radix/themes/components/dropdown_menu.pyi +2 -2
  41. reflex/components/radix/themes/components/hover_card.pyi +2 -2
  42. reflex/components/radix/themes/components/popover.pyi +1 -1
  43. reflex/components/radix/themes/components/radio_cards.pyi +1 -1
  44. reflex/components/radix/themes/components/radio_group.pyi +1 -1
  45. reflex/components/radix/themes/components/select.pyi +6 -6
  46. reflex/components/radix/themes/components/switch.pyi +1 -1
  47. reflex/components/radix/themes/components/tabs.pyi +2 -2
  48. reflex/components/radix/themes/components/tooltip.pyi +4 -2
  49. reflex/components/react_player/__init__.py +1 -0
  50. reflex/components/react_player/audio.pyi +6 -3
  51. reflex/components/react_player/react_player.py +12 -1
  52. reflex/components/react_player/react_player.pyi +11 -3
  53. reflex/components/react_player/video.pyi +6 -3
  54. reflex/components/recharts/recharts.py +2 -2
  55. reflex/components/sonner/toast.py +1 -1
  56. reflex/components/suneditor/editor.py +40 -16
  57. reflex/components/suneditor/editor.pyi +15 -11
  58. reflex/config.py +284 -20
  59. reflex/constants/__init__.py +2 -0
  60. reflex/constants/base.py +53 -31
  61. reflex/constants/compiler.py +2 -12
  62. reflex/constants/config.py +1 -2
  63. reflex/constants/installer.py +88 -32
  64. reflex/constants/style.py +1 -1
  65. reflex/constants/utils.py +32 -0
  66. reflex/custom_components/custom_components.py +3 -3
  67. reflex/event.py +152 -84
  68. reflex/experimental/__init__.py +2 -0
  69. reflex/experimental/client_state.py +1 -1
  70. reflex/experimental/layout.pyi +1 -1
  71. reflex/istate/storage.py +144 -0
  72. reflex/model.py +8 -11
  73. reflex/reflex.py +18 -17
  74. reflex/state.py +89 -151
  75. reflex/style.py +1 -1
  76. reflex/testing.py +2 -1
  77. reflex/utils/build.py +0 -12
  78. reflex/utils/exceptions.py +8 -0
  79. reflex/utils/exec.py +22 -4
  80. reflex/utils/imports.py +6 -0
  81. reflex/utils/net.py +2 -4
  82. reflex/utils/path_ops.py +7 -21
  83. reflex/utils/prerequisites.py +11 -17
  84. reflex/utils/pyi_generator.py +91 -3
  85. reflex/utils/registry.py +2 -6
  86. reflex/utils/types.py +14 -0
  87. reflex/vars/base.py +453 -424
  88. reflex/vars/function.py +6 -16
  89. reflex/vars/number.py +46 -67
  90. reflex/vars/object.py +1 -31
  91. reflex/vars/sequence.py +177 -47
  92. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/METADATA +1 -1
  93. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/RECORD +96 -91
  94. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/LICENSE +0 -0
  95. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/WHEEL +0 -0
  96. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/entry_points.txt +0 -0
reflex/utils/exec.py CHANGED
@@ -15,7 +15,7 @@ from urllib.parse import urljoin
15
15
  import psutil
16
16
 
17
17
  from reflex import constants
18
- from reflex.config import get_config
18
+ from reflex.config import environment, get_config
19
19
  from reflex.constants.base import LogLevel
20
20
  from reflex.utils import console, path_ops
21
21
  from reflex.utils.prerequisites import get_web_dir
@@ -184,7 +184,7 @@ def should_use_granian():
184
184
  Returns:
185
185
  True if Granian should be used.
186
186
  """
187
- return os.getenv("REFLEX_USE_GRANIAN", "0") == "1"
187
+ return environment.REFLEX_USE_GRANIAN
188
188
 
189
189
 
190
190
  def get_app_module():
@@ -337,8 +337,8 @@ def run_uvicorn_backend_prod(host, port, loglevel):
337
337
 
338
338
  app_module = get_app_module()
339
339
 
340
- RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split()
341
- RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split()
340
+ RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
341
+ RUN_BACKEND_PROD_WINDOWS = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
342
342
  command = (
343
343
  [
344
344
  *RUN_BACKEND_PROD_WINDOWS,
@@ -496,6 +496,24 @@ def is_prod_mode() -> bool:
496
496
  return current_mode == constants.Env.PROD.value
497
497
 
498
498
 
499
+ def is_frontend_only() -> bool:
500
+ """Check if the app is running in frontend-only mode.
501
+
502
+ Returns:
503
+ True if the app is running in frontend-only mode.
504
+ """
505
+ return os.environ.get(constants.ENV_FRONTEND_ONLY_ENV_VAR, "").lower() == "true"
506
+
507
+
508
+ def is_backend_only() -> bool:
509
+ """Check if the app is running in backend-only mode.
510
+
511
+ Returns:
512
+ True if the app is running in backend-only mode.
513
+ """
514
+ return os.environ.get(constants.ENV_BACKEND_ONLY_ENV_VAR, "").lower() == "true"
515
+
516
+
499
517
  def should_skip_compile() -> bool:
500
518
  """Whether the app should skip compile.
501
519
 
reflex/utils/imports.py CHANGED
@@ -23,6 +23,12 @@ def merge_imports(
23
23
  for lib, fields in (
24
24
  import_dict if isinstance(import_dict, tuple) else import_dict.items()
25
25
  ):
26
+ # If the lib is an absolute path, we need to prefix it with a $
27
+ lib = (
28
+ "$" + lib
29
+ if lib.startswith(("/utils/", "/components/", "/styles/", "/public/"))
30
+ else lib
31
+ )
26
32
  if isinstance(fields, (list, tuple, set)):
27
33
  all_imports[lib].extend(
28
34
  (
reflex/utils/net.py CHANGED
@@ -1,9 +1,8 @@
1
1
  """Helpers for downloading files from the network."""
2
2
 
3
- import os
4
-
5
3
  import httpx
6
4
 
5
+ from ..config import environment
7
6
  from . import console
8
7
 
9
8
 
@@ -13,8 +12,7 @@ def _httpx_verify_kwarg() -> bool:
13
12
  Returns:
14
13
  True if SSL verification is enabled, False otherwise
15
14
  """
16
- ssl_no_verify = os.environ.get("SSL_NO_VERIFY", "").lower() in ["true", "1", "yes"]
17
- return not ssl_no_verify
15
+ return not environment.SSL_NO_VERIFY
18
16
 
19
17
 
20
18
  def get(url: str, **kwargs) -> httpx.Response:
reflex/utils/path_ops.py CHANGED
@@ -9,6 +9,7 @@ import shutil
9
9
  from pathlib import Path
10
10
 
11
11
  from reflex import constants
12
+ from reflex.config import environment
12
13
 
13
14
  # Shorthand for join.
14
15
  join = os.linesep.join
@@ -129,30 +130,13 @@ def which(program: str | Path) -> str | Path | None:
129
130
  return shutil.which(str(program))
130
131
 
131
132
 
132
- def use_system_install(var_name: str) -> bool:
133
- """Check if the system install should be used.
134
-
135
- Args:
136
- var_name: The name of the environment variable.
137
-
138
- Raises:
139
- ValueError: If the variable name is invalid.
140
-
141
- Returns:
142
- Whether the associated env var should use the system install.
143
- """
144
- if not var_name.startswith("REFLEX_USE_SYSTEM_"):
145
- raise ValueError("Invalid system install variable name.")
146
- return os.getenv(var_name, "").lower() in ["true", "1", "yes"]
147
-
148
-
149
133
  def use_system_node() -> bool:
150
134
  """Check if the system node should be used.
151
135
 
152
136
  Returns:
153
137
  Whether the system node should be used.
154
138
  """
155
- return use_system_install(constants.Node.USE_SYSTEM_VAR)
139
+ return environment.REFLEX_USE_SYSTEM_NODE
156
140
 
157
141
 
158
142
  def use_system_bun() -> bool:
@@ -161,7 +145,7 @@ def use_system_bun() -> bool:
161
145
  Returns:
162
146
  Whether the system bun should be used.
163
147
  """
164
- return use_system_install(constants.Bun.USE_SYSTEM_VAR)
148
+ return environment.REFLEX_USE_SYSTEM_BUN
165
149
 
166
150
 
167
151
  def get_node_bin_path() -> Path | None:
@@ -185,7 +169,8 @@ def get_node_path() -> str | None:
185
169
  """
186
170
  node_path = Path(constants.Node.PATH)
187
171
  if use_system_node() or not node_path.exists():
188
- return str(which("node"))
172
+ system_node_path = which("node")
173
+ return str(system_node_path) if system_node_path else None
189
174
  return str(node_path)
190
175
 
191
176
 
@@ -197,7 +182,8 @@ def get_npm_path() -> str | None:
197
182
  """
198
183
  npm_path = Path(constants.Node.NPM_PATH)
199
184
  if use_system_node() or not npm_path.exists():
200
- return str(which("npm"))
185
+ system_npm_path = which("npm")
186
+ return str(system_npm_path) if system_npm_path else None
201
187
  return str(npm_path)
202
188
 
203
189
 
@@ -33,7 +33,7 @@ from redis.asyncio import Redis
33
33
 
34
34
  from reflex import constants, model
35
35
  from reflex.compiler import templates
36
- from reflex.config import Config, get_config
36
+ from reflex.config import Config, environment, get_config
37
37
  from reflex.utils import console, net, path_ops, processes
38
38
  from reflex.utils.exceptions import GeneratedCodeHasNoFunctionDefs
39
39
  from reflex.utils.format import format_library_name
@@ -69,8 +69,7 @@ def get_web_dir() -> Path:
69
69
  Returns:
70
70
  The working directory.
71
71
  """
72
- workdir = Path(os.getenv("REFLEX_WEB_WORKDIR", constants.Dirs.WEB))
73
- return workdir
72
+ return environment.REFLEX_WEB_WORKDIR
74
73
 
75
74
 
76
75
  def _python_version_check():
@@ -146,14 +145,9 @@ def check_node_version() -> bool:
146
145
  Whether the version of Node.js is valid.
147
146
  """
148
147
  current_version = get_node_version()
149
- if current_version:
150
- # Compare the version numbers
151
- return (
152
- current_version >= version.parse(constants.Node.MIN_VERSION)
153
- if constants.IS_WINDOWS or path_ops.use_system_node()
154
- else current_version == version.parse(constants.Node.VERSION)
155
- )
156
- return False
148
+ return current_version is not None and current_version >= version.parse(
149
+ constants.Node.MIN_VERSION
150
+ )
157
151
 
158
152
 
159
153
  def get_node_version() -> version.Version | None:
@@ -255,7 +249,7 @@ def windows_npm_escape_hatch() -> bool:
255
249
  Returns:
256
250
  If the user has set REFLEX_USE_NPM.
257
251
  """
258
- return os.environ.get("REFLEX_USE_NPM", "").lower() in ["true", "1", "yes"]
252
+ return environment.REFLEX_USE_NPM
259
253
 
260
254
 
261
255
  def get_app(reload: bool = False) -> ModuleType:
@@ -997,7 +991,7 @@ def needs_reinit(frontend: bool = True) -> bool:
997
991
  return False
998
992
 
999
993
  # Make sure the .reflex directory exists.
1000
- if not constants.Reflex.DIR.exists():
994
+ if not environment.REFLEX_DIR.exists():
1001
995
  return True
1002
996
 
1003
997
  # Make sure the .web directory exists in frontend mode.
@@ -1102,7 +1096,7 @@ def ensure_reflex_installation_id() -> Optional[int]:
1102
1096
  """
1103
1097
  try:
1104
1098
  initialize_reflex_user_directory()
1105
- installation_id_file = constants.Reflex.DIR / "installation_id"
1099
+ installation_id_file = environment.REFLEX_DIR / "installation_id"
1106
1100
 
1107
1101
  installation_id = None
1108
1102
  if installation_id_file.exists():
@@ -1127,7 +1121,7 @@ def ensure_reflex_installation_id() -> Optional[int]:
1127
1121
  def initialize_reflex_user_directory():
1128
1122
  """Initialize the reflex user directory."""
1129
1123
  # Create the reflex directory.
1130
- path_ops.mkdir(constants.Reflex.DIR)
1124
+ path_ops.mkdir(environment.REFLEX_DIR)
1131
1125
 
1132
1126
 
1133
1127
  def initialize_frontend_dependencies():
@@ -1150,7 +1144,7 @@ def check_db_initialized() -> bool:
1150
1144
  Returns:
1151
1145
  True if alembic is initialized (or if database is not used).
1152
1146
  """
1153
- if get_config().db_url is not None and not Path(constants.ALEMBIC_CONFIG).exists():
1147
+ if get_config().db_url is not None and not environment.ALEMBIC_CONFIG.exists():
1154
1148
  console.error(
1155
1149
  "Database is not initialized. Run [bold]reflex db init[/bold] first."
1156
1150
  )
@@ -1160,7 +1154,7 @@ def check_db_initialized() -> bool:
1160
1154
 
1161
1155
  def check_schema_up_to_date():
1162
1156
  """Check if the sqlmodel metadata matches the current database schema."""
1163
- if get_config().db_url is None or not Path(constants.ALEMBIC_CONFIG).exists():
1157
+ if get_config().db_url is None or not environment.ALEMBIC_CONFIG.exists():
1164
1158
  return
1165
1159
  with model.Model.get_db_engine().connect() as connection:
1166
1160
  try:
@@ -16,7 +16,7 @@ from itertools import chain
16
16
  from multiprocessing import Pool, cpu_count
17
17
  from pathlib import Path
18
18
  from types import ModuleType, SimpleNamespace
19
- from typing import Any, Callable, Iterable, Type, get_args
19
+ from typing import Any, Callable, Iterable, Type, get_args, get_origin
20
20
 
21
21
  from reflex.components.component import Component
22
22
  from reflex.utils import types as rx_types
@@ -214,7 +214,9 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
214
214
  return res
215
215
 
216
216
 
217
- def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
217
+ def _generate_imports(
218
+ typing_imports: Iterable[str],
219
+ ) -> list[ast.ImportFrom | ast.Import]:
218
220
  """Generate the import statements for the stub file.
219
221
 
220
222
  Args:
@@ -228,6 +230,7 @@ def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
228
230
  ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values])
229
231
  for name, values in DEFAULT_IMPORTS.items()
230
232
  ],
233
+ ast.Import([ast.alias("reflex")]),
231
234
  ]
232
235
 
233
236
 
@@ -372,6 +375,64 @@ def _extract_class_props_as_ast_nodes(
372
375
  return kwargs
373
376
 
374
377
 
378
+ def type_to_ast(typ, cls: type) -> ast.AST:
379
+ """Converts any type annotation into its AST representation.
380
+ Handles nested generic types, unions, etc.
381
+
382
+ Args:
383
+ typ: The type annotation to convert.
384
+ cls: The class where the type annotation is used.
385
+
386
+ Returns:
387
+ The AST representation of the type annotation.
388
+ """
389
+ if typ is type(None):
390
+ return ast.Name(id="None")
391
+
392
+ origin = get_origin(typ)
393
+
394
+ # Handle plain types (int, str, custom classes, etc.)
395
+ if origin is None:
396
+ if hasattr(typ, "__name__"):
397
+ if typ.__module__.startswith("reflex."):
398
+ typ_parts = typ.__module__.split(".")
399
+ cls_parts = cls.__module__.split(".")
400
+
401
+ zipped = list(zip(typ_parts, cls_parts, strict=False))
402
+
403
+ if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts):
404
+ return ast.Name(id=typ.__name__)
405
+
406
+ return ast.Name(id=typ.__module__ + "." + typ.__name__)
407
+ return ast.Name(id=typ.__name__)
408
+ elif hasattr(typ, "_name"):
409
+ return ast.Name(id=typ._name)
410
+ return ast.Name(id=str(typ))
411
+
412
+ # Get the base type name (List, Dict, Optional, etc.)
413
+ base_name = origin._name if hasattr(origin, "_name") else origin.__name__
414
+
415
+ # Get type arguments
416
+ args = get_args(typ)
417
+
418
+ # Handle empty type arguments
419
+ if not args:
420
+ return ast.Name(id=base_name)
421
+
422
+ # Convert all type arguments recursively
423
+ arg_nodes = [type_to_ast(arg, cls) for arg in args]
424
+
425
+ # Special case for single-argument types (like List[T] or Optional[T])
426
+ if len(arg_nodes) == 1:
427
+ slice_value = arg_nodes[0]
428
+ else:
429
+ slice_value = ast.Tuple(elts=arg_nodes, ctx=ast.Load())
430
+
431
+ return ast.Subscript(
432
+ value=ast.Name(id=base_name), slice=ast.Index(value=slice_value), ctx=ast.Load()
433
+ )
434
+
435
+
375
436
  def _get_parent_imports(func):
376
437
  _imports = {"reflex.vars": ["Var"]}
377
438
  for type_hint in inspect.get_annotations(func).values():
@@ -430,13 +491,40 @@ def _generate_component_create_functiondef(
430
491
  def figure_out_return_type(annotation: Any):
431
492
  if inspect.isclass(annotation) and issubclass(annotation, inspect._empty):
432
493
  return ast.Name(id="Optional[EventType]")
494
+
495
+ if not isinstance(annotation, str) and get_origin(annotation) is tuple:
496
+ arguments = get_args(annotation)
497
+
498
+ arguments_without_var = [
499
+ get_args(argument)[0] if get_origin(argument) == Var else argument
500
+ for argument in arguments
501
+ ]
502
+
503
+ # Convert each argument type to its AST representation
504
+ type_args = [type_to_ast(arg, cls=clz) for arg in arguments_without_var]
505
+
506
+ # Join the type arguments with commas for EventType
507
+ args_str = ", ".join(ast.unparse(arg) for arg in type_args)
508
+
509
+ # Create EventType using the joined string
510
+ event_type = ast.Name(id=f"EventType[{args_str}]")
511
+
512
+ # Wrap in Optional
513
+ optional_type = ast.Subscript(
514
+ value=ast.Name(id="Optional"),
515
+ slice=ast.Index(value=event_type),
516
+ ctx=ast.Load(),
517
+ )
518
+
519
+ return ast.Name(id=ast.unparse(optional_type))
520
+
433
521
  if isinstance(annotation, str) and annotation.startswith("Tuple["):
434
522
  inside_of_tuple = annotation.removeprefix("Tuple[").removesuffix("]")
435
523
 
436
524
  if inside_of_tuple == "()":
437
525
  return ast.Name(id="Optional[EventType[[]]]")
438
526
 
439
- arguments: list[str] = [""]
527
+ arguments = [""]
440
528
 
441
529
  bracket_count = 0
442
530
 
reflex/utils/registry.py CHANGED
@@ -1,9 +1,8 @@
1
1
  """Utilities for working with registries."""
2
2
 
3
- import os
4
-
5
3
  import httpx
6
4
 
5
+ from reflex.config import environment
7
6
  from reflex.utils import console, net
8
7
 
9
8
 
@@ -56,7 +55,4 @@ def _get_npm_registry() -> str:
56
55
  Returns:
57
56
  str:
58
57
  """
59
- if npm_registry := os.environ.get("NPM_CONFIG_REGISTRY", ""):
60
- return npm_registry
61
- else:
62
- return get_best_registry()
58
+ return environment.NPM_CONFIG_REGISTRY or get_best_registry()
reflex/utils/types.py CHANGED
@@ -274,6 +274,20 @@ def is_optional(cls: GenericType) -> bool:
274
274
  return is_union(cls) and type(None) in get_args(cls)
275
275
 
276
276
 
277
+ def value_inside_optional(cls: GenericType) -> GenericType:
278
+ """Get the value inside an Optional type or the original type.
279
+
280
+ Args:
281
+ cls: The class to check.
282
+
283
+ Returns:
284
+ The value inside the Optional type or the original type.
285
+ """
286
+ if is_union(cls) and len(args := get_args(cls)) >= 2 and type(None) in args:
287
+ return unionize(*[arg for arg in args if arg is not type(None)])
288
+ return cls
289
+
290
+
277
291
  def get_property_hint(attr: Any | None) -> GenericType | None:
278
292
  """Check if an attribute is a property and return its type hint.
279
293