reflex 0.6.5a3__py3-none-any.whl → 0.6.6__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 (52) hide show
  1. reflex/.templates/web/utils/state.js +39 -14
  2. reflex/__init__.py +3 -1
  3. reflex/__init__.pyi +3 -0
  4. reflex/app.py +24 -4
  5. reflex/assets.py +95 -0
  6. reflex/base.py +2 -2
  7. reflex/components/base/error_boundary.py +99 -36
  8. reflex/components/base/error_boundary.pyi +3 -4
  9. reflex/components/component.py +29 -7
  10. reflex/components/core/cond.py +8 -0
  11. reflex/components/core/upload.py +8 -5
  12. reflex/components/datadisplay/code.py +1 -1
  13. reflex/components/datadisplay/logo.py +26 -13
  14. reflex/components/el/__init__.pyi +2 -0
  15. reflex/components/el/elements/__init__.py +1 -0
  16. reflex/components/el/elements/__init__.pyi +3 -0
  17. reflex/components/el/elements/forms.py +1 -0
  18. reflex/components/el/elements/forms.pyi +1 -0
  19. reflex/components/moment/moment.py +4 -3
  20. reflex/components/moment/moment.pyi +12 -2
  21. reflex/components/plotly/plotly.py +1 -1
  22. reflex/components/radix/primitives/drawer.py +5 -23
  23. reflex/components/radix/themes/base.py +3 -0
  24. reflex/components/radix/themes/components/segmented_control.py +3 -1
  25. reflex/components/radix/themes/components/segmented_control.pyi +7 -2
  26. reflex/components/radix/themes/components/text_field.py +3 -0
  27. reflex/components/radix/themes/components/text_field.pyi +4 -0
  28. reflex/components/recharts/recharts.py +2 -14
  29. reflex/components/recharts/recharts.pyi +0 -1
  30. reflex/components/sonner/toast.py +23 -12
  31. reflex/components/sonner/toast.pyi +6 -6
  32. reflex/config.py +60 -9
  33. reflex/constants/base.py +12 -0
  34. reflex/constants/installer.py +3 -3
  35. reflex/constants/style.py +1 -1
  36. reflex/event.py +22 -5
  37. reflex/experimental/assets.py +14 -36
  38. reflex/reflex.py +16 -35
  39. reflex/state.py +90 -25
  40. reflex/utils/exceptions.py +4 -0
  41. reflex/utils/prerequisites.py +174 -40
  42. reflex/utils/redir.py +13 -4
  43. reflex/utils/serializers.py +52 -1
  44. reflex/utils/telemetry.py +2 -1
  45. reflex/utils/types.py +52 -1
  46. reflex/vars/base.py +18 -4
  47. reflex/vars/function.py +283 -37
  48. {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/METADATA +3 -2
  49. {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/RECORD +52 -51
  50. {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/LICENSE +0 -0
  51. {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/WHEEL +0 -0
  52. {reflex-0.6.5a3.dist-info → reflex-0.6.6.dist-info}/entry_points.txt +0 -0
@@ -34,7 +34,7 @@ from redis.asyncio import Redis
34
34
  from reflex import constants, model
35
35
  from reflex.compiler import templates
36
36
  from reflex.config import Config, environment, get_config
37
- from reflex.utils import console, net, path_ops, processes
37
+ from reflex.utils import console, net, path_ops, processes, redir
38
38
  from reflex.utils.exceptions import (
39
39
  GeneratedCodeHasNoFunctionDefs,
40
40
  raise_system_package_missing_error,
@@ -93,6 +93,8 @@ def check_latest_package_version(package_name: str):
93
93
  Args:
94
94
  package_name: The name of the package.
95
95
  """
96
+ if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
97
+ return
96
98
  try:
97
99
  # Get the latest version from PyPI
98
100
  current_version = importlib.metadata.version(package_name)
@@ -1209,7 +1211,7 @@ def check_schema_up_to_date():
1209
1211
  )
1210
1212
 
1211
1213
 
1212
- def prompt_for_template(templates: list[Template]) -> str:
1214
+ def prompt_for_template_options(templates: list[Template]) -> str:
1213
1215
  """Prompt the user to specify a template.
1214
1216
 
1215
1217
  Args:
@@ -1221,9 +1223,14 @@ def prompt_for_template(templates: list[Template]) -> str:
1221
1223
  # Show the user the URLs of each template to preview.
1222
1224
  console.print("\nGet started with a template:")
1223
1225
 
1226
+ def format_demo_url_str(url: str) -> str:
1227
+ return f" ({url})" if url else ""
1228
+
1224
1229
  # Prompt the user to select a template.
1225
1230
  id_to_name = {
1226
- str(idx): f"{template.name} ({template.demo_url}) - {template.description}"
1231
+ str(
1232
+ idx
1233
+ ): f"{template.name.replace('_', ' ').replace('-', ' ')}{format_demo_url_str(template.demo_url)} - {template.description}"
1227
1234
  for idx, template in enumerate(templates)
1228
1235
  }
1229
1236
  for id in range(len(id_to_name)):
@@ -1378,15 +1385,122 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
1378
1385
  shutil.rmtree(unzip_dir)
1379
1386
 
1380
1387
 
1381
- def initialize_app(app_name: str, template: str | None = None):
1388
+ def initialize_default_app(app_name: str):
1389
+ """Initialize the default app.
1390
+
1391
+ Args:
1392
+ app_name: The name of the app.
1393
+ """
1394
+ create_config(app_name)
1395
+ initialize_app_directory(app_name)
1396
+
1397
+
1398
+ def validate_and_create_app_using_remote_template(app_name, template, templates):
1399
+ """Validate and create an app using a remote template.
1400
+
1401
+ Args:
1402
+ app_name: The name of the app.
1403
+ template: The name of the template.
1404
+ templates: The available templates.
1405
+
1406
+ Raises:
1407
+ Exit: If the template is not found.
1408
+ """
1409
+ # If user selects a template, it needs to exist.
1410
+ if template in templates:
1411
+ template_url = templates[template].code_url
1412
+ else:
1413
+ # Check if the template is a github repo.
1414
+ if template.startswith("https://github.com"):
1415
+ template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
1416
+ else:
1417
+ console.error(f"Template `{template}` not found.")
1418
+ raise typer.Exit(1)
1419
+
1420
+ if template_url is None:
1421
+ return
1422
+
1423
+ create_config_init_app_from_remote_template(
1424
+ app_name=app_name, template_url=template_url
1425
+ )
1426
+
1427
+
1428
+ def generate_template_using_ai(template: str | None = None) -> str:
1429
+ """Generate a template using AI(Flexgen).
1430
+
1431
+ Args:
1432
+ template: The name of the template.
1433
+
1434
+ Returns:
1435
+ The generation hash.
1436
+
1437
+ Raises:
1438
+ Exit: If the template and ai flags are used.
1439
+ """
1440
+ if template is None:
1441
+ # If AI is requested and no template specified, redirect the user to reflex.build.
1442
+ return redir.reflex_build_redirect()
1443
+ elif is_generation_hash(template):
1444
+ # Otherwise treat the template as a generation hash.
1445
+ return template
1446
+ else:
1447
+ console.error(
1448
+ "Cannot use `--template` option with `--ai` option. Please remove `--template` option."
1449
+ )
1450
+ raise typer.Exit(2)
1451
+
1452
+
1453
+ def fetch_remote_templates(
1454
+ template: Optional[str] = None,
1455
+ ) -> tuple[str, dict[str, Template]]:
1456
+ """Fetch the available remote templates.
1457
+
1458
+ Args:
1459
+ template: The name of the template.
1460
+
1461
+ Returns:
1462
+ The selected template and the available templates.
1463
+
1464
+ Raises:
1465
+ Exit: If the template is not valid or if the template is not specified.
1466
+ """
1467
+ available_templates = {}
1468
+
1469
+ try:
1470
+ # Get the available templates
1471
+ available_templates = fetch_app_templates(constants.Reflex.VERSION)
1472
+ except Exception as e:
1473
+ console.warn("Failed to fetch templates. Falling back to default template.")
1474
+ console.debug(f"Error while fetching templates: {e}")
1475
+ template = constants.Templates.DEFAULT
1476
+
1477
+ if template == constants.Templates.DEFAULT:
1478
+ return template, available_templates
1479
+
1480
+ if template in available_templates:
1481
+ return template, available_templates
1482
+
1483
+ else:
1484
+ if template is not None:
1485
+ console.error(f"{template!r} is not a valid template name.")
1486
+ console.print(
1487
+ f"Go to the templates page ({constants.Templates.REFLEX_TEMPLATES_URL}) and copy the command to init with a template."
1488
+ )
1489
+ raise typer.Exit(0)
1490
+
1491
+
1492
+ def initialize_app(
1493
+ app_name: str, template: str | None = None, ai: bool = False
1494
+ ) -> str | None:
1382
1495
  """Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
1383
1496
 
1384
1497
  Args:
1385
1498
  app_name: The name of the app.
1386
1499
  template: The name of the template to use.
1500
+ ai: Whether to use AI to generate the template.
1387
1501
 
1388
- Raises:
1389
- Exit: If template is directly provided in the command flag and is invalid.
1502
+ Returns:
1503
+ The name of the template.
1390
1504
  """
1391
1505
  # Local imports to avoid circular imports.
1392
1506
  from reflex.utils import telemetry
@@ -1396,51 +1510,71 @@ def initialize_app(app_name: str, template: str | None = None):
1396
1510
  telemetry.send("reinit")
1397
1511
  return
1398
1512
 
1513
+ generation_hash = None
1514
+ if ai:
1515
+ generation_hash = generate_template_using_ai(template)
1516
+ template = constants.Templates.DEFAULT
1517
+
1399
1518
  templates: dict[str, Template] = {}
1400
1519
 
1401
1520
  # Don't fetch app templates if the user directly asked for DEFAULT.
1402
- if template is None or (template != constants.Templates.DEFAULT):
1403
- try:
1404
- # Get the available templates
1405
- templates = fetch_app_templates(constants.Reflex.VERSION)
1406
- if template is None and len(templates) > 0:
1407
- template = prompt_for_template(list(templates.values()))
1408
- except Exception as e:
1409
- console.warn("Failed to fetch templates. Falling back to default template.")
1410
- console.debug(f"Error while fetching templates: {e}")
1411
- finally:
1412
- template = template or constants.Templates.DEFAULT
1521
+ if template is not None and (template not in (constants.Templates.DEFAULT,)):
1522
+ template, templates = fetch_remote_templates(template)
1523
+
1524
+ if template is None:
1525
+ template = prompt_for_template_options(get_init_cli_prompt_options())
1526
+ if template == constants.Templates.AI:
1527
+ generation_hash = generate_template_using_ai()
1528
+ # change to the default to allow creation of default app
1529
+ template = constants.Templates.DEFAULT
1530
+ elif template == constants.Templates.CHOOSE_TEMPLATES:
1531
+ template, templates = fetch_remote_templates()
1413
1532
 
1414
1533
  # If the blank template is selected, create a blank app.
1415
- if template == constants.Templates.DEFAULT:
1534
+ if template in (constants.Templates.DEFAULT,):
1416
1535
  # Default app creation behavior: a blank app.
1417
- create_config(app_name)
1418
- initialize_app_directory(app_name)
1536
+ initialize_default_app(app_name)
1419
1537
  else:
1420
- # Fetch App templates from the backend server.
1421
- console.debug(f"Available templates: {templates}")
1538
+ validate_and_create_app_using_remote_template(
1539
+ app_name=app_name, template=template, templates=templates
1540
+ )
1422
1541
 
1423
- # If user selects a template, it needs to exist.
1424
- if template in templates:
1425
- template_url = templates[template].code_url
1426
- else:
1427
- # Check if the template is a github repo.
1428
- if template.startswith("https://github.com"):
1429
- template_url = (
1430
- f"{template.strip('/').replace('.git', '')}/archive/main.zip"
1431
- )
1432
- else:
1433
- console.error(f"Template `{template}` not found.")
1434
- raise typer.Exit(1)
1542
+ # If a reflex.build generation hash is available, download the code and apply it to the main module.
1543
+ if generation_hash:
1544
+ initialize_main_module_index_from_generation(
1545
+ app_name, generation_hash=generation_hash
1546
+ )
1547
+ telemetry.send("init", template=template)
1435
1548
 
1436
- if template_url is None:
1437
- return
1549
+ return template
1438
1550
 
1439
- create_config_init_app_from_remote_template(
1440
- app_name=app_name, template_url=template_url
1441
- )
1442
1551
 
1443
- telemetry.send("init", template=template)
1552
+ def get_init_cli_prompt_options() -> list[Template]:
1553
+ """Get the CLI options for initializing a Reflex app.
1554
+
1555
+ Returns:
1556
+ The CLI options.
1557
+ """
1558
+ return [
1559
+ Template(
1560
+ name=constants.Templates.DEFAULT,
1561
+ description="A blank Reflex app.",
1562
+ demo_url=constants.Templates.DEFAULT_TEMPLATE_URL,
1563
+ code_url="",
1564
+ ),
1565
+ Template(
1566
+ name=constants.Templates.AI,
1567
+ description="Generate a template using AI [Experimental]",
1568
+ demo_url="",
1569
+ code_url="",
1570
+ ),
1571
+ Template(
1572
+ name=constants.Templates.CHOOSE_TEMPLATES,
1573
+ description="Choose an existing template.",
1574
+ demo_url="",
1575
+ code_url="",
1576
+ ),
1577
+ ]
1444
1578
 
1445
1579
 
1446
1580
  def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
reflex/utils/redir.py CHANGED
@@ -10,6 +10,18 @@ from .. import constants
10
10
  from . import console
11
11
 
12
12
 
13
+ def open_browser(target_url: str) -> None:
14
+ """Open a browser window to target_url.
15
+
16
+ Args:
17
+ target_url: The URL to open in the browser.
18
+ """
19
+ if not webbrowser.open(target_url):
20
+ console.warn(
21
+ f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
22
+ )
23
+
24
+
13
25
  def open_browser_and_wait(
14
26
  target_url: str, poll_url: str, interval: int = 2
15
27
  ) -> httpx.Response:
@@ -23,10 +35,7 @@ def open_browser_and_wait(
23
35
  Returns:
24
36
  The response from the poll_url.
25
37
  """
26
- if not webbrowser.open(target_url):
27
- console.warn(
28
- f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
29
- )
38
+ open_browser(target_url)
30
39
  console.info("[b]Complete the workflow in the browser to continue.[/b]")
31
40
  while True:
32
41
  try:
@@ -263,7 +263,58 @@ def serialize_base(value: Base) -> dict:
263
263
  Returns:
264
264
  The serialized Base.
265
265
  """
266
- return {k: v for k, v in value.dict().items() if not callable(v)}
266
+ from reflex.vars.base import Var
267
+
268
+ return {
269
+ k: v for k, v in value.dict().items() if isinstance(v, Var) or not callable(v)
270
+ }
271
+
272
+
273
+ try:
274
+ from pydantic.v1 import BaseModel as BaseModelV1
275
+
276
+ @serializer(to=dict)
277
+ def serialize_base_model_v1(model: BaseModelV1) -> dict:
278
+ """Serialize a pydantic v1 BaseModel instance.
279
+
280
+ Args:
281
+ model: The BaseModel to serialize.
282
+
283
+ Returns:
284
+ The serialized BaseModel.
285
+ """
286
+ return model.dict()
287
+
288
+ from pydantic import BaseModel as BaseModelV2
289
+
290
+ if BaseModelV1 is not BaseModelV2:
291
+
292
+ @serializer(to=dict)
293
+ def serialize_base_model_v2(model: BaseModelV2) -> dict:
294
+ """Serialize a pydantic v2 BaseModel instance.
295
+
296
+ Args:
297
+ model: The BaseModel to serialize.
298
+
299
+ Returns:
300
+ The serialized BaseModel.
301
+ """
302
+ return model.model_dump()
303
+ except ImportError:
304
+ # Older pydantic v1 import
305
+ from pydantic import BaseModel as BaseModelV1
306
+
307
+ @serializer(to=dict)
308
+ def serialize_base_model_v1(model: BaseModelV1) -> dict:
309
+ """Serialize a pydantic v1 BaseModel instance.
310
+
311
+ Args:
312
+ model: The BaseModel to serialize.
313
+
314
+ Returns:
315
+ The serialized BaseModel.
316
+ """
317
+ return model.dict()
267
318
 
268
319
 
269
320
  @serializer
reflex/utils/telemetry.py CHANGED
@@ -51,7 +51,8 @@ def get_python_version() -> str:
51
51
  Returns:
52
52
  The Python version.
53
53
  """
54
- return platform.python_version()
54
+ # Remove the "+" from the version string in case user is using a pre-release version.
55
+ return platform.python_version().rstrip("+")
55
56
 
56
57
 
57
58
  def get_reflex_version() -> str:
reflex/utils/types.py CHANGED
@@ -14,9 +14,11 @@ from typing import (
14
14
  Callable,
15
15
  ClassVar,
16
16
  Dict,
17
+ FrozenSet,
17
18
  Iterable,
18
19
  List,
19
20
  Literal,
21
+ Mapping,
20
22
  Optional,
21
23
  Sequence,
22
24
  Tuple,
@@ -29,6 +31,7 @@ from typing import (
29
31
  from typing import get_origin as get_origin_og
30
32
 
31
33
  import sqlalchemy
34
+ from typing_extensions import is_typeddict
32
35
 
33
36
  import reflex
34
37
  from reflex.components.core.breakpoints import Breakpoints
@@ -494,6 +497,14 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
494
497
  if isinstance(instance, Breakpoints):
495
498
  return _breakpoints_satisfies_typing(cls_check, instance)
496
499
 
500
+ if isinstance(cls_check_base, tuple):
501
+ cls_check_base = tuple(
502
+ cls_check_one if not is_typeddict(cls_check_one) else dict
503
+ for cls_check_one in cls_check_base
504
+ )
505
+ if is_typeddict(cls_check_base):
506
+ cls_check_base = dict
507
+
497
508
  # Check if the types match.
498
509
  try:
499
510
  return cls_check_base == Any or issubclass(cls_base, cls_check_base)
@@ -503,6 +514,36 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
503
514
  raise TypeError(f"Invalid type for issubclass: {cls_base}") from te
504
515
 
505
516
 
517
+ def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
518
+ """Check if an object satisfies a typed dict.
519
+
520
+ Args:
521
+ obj: The object to check.
522
+ cls: The typed dict to check against.
523
+
524
+ Returns:
525
+ Whether the object satisfies the typed dict.
526
+ """
527
+ if not isinstance(obj, Mapping):
528
+ return False
529
+
530
+ key_names_to_values = get_type_hints(cls)
531
+ required_keys: FrozenSet[str] = getattr(cls, "__required_keys__", frozenset())
532
+
533
+ if not all(
534
+ isinstance(key, str)
535
+ and key in key_names_to_values
536
+ and _isinstance(value, key_names_to_values[key])
537
+ for key, value in obj.items()
538
+ ):
539
+ return False
540
+
541
+ # TODO in 3.14: Implement https://peps.python.org/pep-0728/ if it's approved
542
+
543
+ # required keys are all present
544
+ return required_keys.issubset(required_keys)
545
+
546
+
506
547
  def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
507
548
  """Check if an object is an instance of a class.
508
549
 
@@ -529,6 +570,16 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
529
570
  origin = get_origin(cls)
530
571
 
531
572
  if origin is None:
573
+ # cls is a typed dict
574
+ if is_typeddict(cls):
575
+ if nested:
576
+ return does_obj_satisfy_typed_dict(obj, cls)
577
+ return isinstance(obj, dict)
578
+
579
+ # cls is a float
580
+ if cls is float:
581
+ return isinstance(obj, (float, int))
582
+
532
583
  # cls is a simple class
533
584
  return isinstance(obj, cls)
534
585
 
@@ -553,7 +604,7 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
553
604
  and len(obj) == len(args)
554
605
  and all(_isinstance(item, arg) for item, arg in zip(obj, args))
555
606
  )
556
- if origin is dict:
607
+ if origin in (dict, Breakpoints):
557
608
  return isinstance(obj, dict) and all(
558
609
  _isinstance(key, args[0]) and _isinstance(value, args[1])
559
610
  for key, value in obj.items()
reflex/vars/base.py CHANGED
@@ -361,21 +361,29 @@ class Var(Generic[VAR_TYPE]):
361
361
  return False
362
362
 
363
363
  def __init_subclass__(
364
- cls, python_types: Tuple[GenericType, ...] | GenericType = types.Unset, **kwargs
364
+ cls,
365
+ python_types: Tuple[GenericType, ...] | GenericType = types.Unset(),
366
+ default_type: GenericType = types.Unset(),
367
+ **kwargs,
365
368
  ):
366
369
  """Initialize the subclass.
367
370
 
368
371
  Args:
369
372
  python_types: The python types that the var represents.
373
+ default_type: The default type of the var. Defaults to the first python type.
370
374
  **kwargs: Additional keyword arguments.
371
375
  """
372
376
  super().__init_subclass__(**kwargs)
373
377
 
374
- if python_types is not types.Unset:
378
+ if python_types or default_type:
375
379
  python_types = (
376
- python_types if isinstance(python_types, tuple) else (python_types,)
380
+ (python_types if isinstance(python_types, tuple) else (python_types,))
381
+ if python_types
382
+ else ()
377
383
  )
378
384
 
385
+ default_type = default_type or (python_types[0] if python_types else Any)
386
+
379
387
  @dataclasses.dataclass(
380
388
  eq=False,
381
389
  frozen=True,
@@ -388,7 +396,7 @@ class Var(Generic[VAR_TYPE]):
388
396
  default=Var(_js_expr="null", _var_type=None),
389
397
  )
390
398
 
391
- _default_var_type: ClassVar[GenericType] = python_types[0]
399
+ _default_var_type: ClassVar[GenericType] = default_type
392
400
 
393
401
  ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation'
394
402
 
@@ -588,6 +596,12 @@ class Var(Generic[VAR_TYPE]):
588
596
  output: type[list] | type[tuple] | type[set],
589
597
  ) -> ArrayVar: ...
590
598
 
599
+ @overload
600
+ def to(
601
+ self,
602
+ output: type[dict],
603
+ ) -> ObjectVar[dict]: ...
604
+
591
605
  @overload
592
606
  def to(
593
607
  self, output: Type[ObjectVar], var_type: Type[VAR_INSIDE]