reflex 0.5.2__py3-none-any.whl → 0.5.3__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 (166) hide show
  1. reflex/.templates/web/postcss.config.js +1 -0
  2. reflex/.templates/web/styles/tailwind.css +4 -1
  3. reflex/__init__.py +298 -204
  4. reflex/__init__.pyi +196 -157
  5. reflex/app.py +13 -2
  6. reflex/compiler/compiler.py +1 -1
  7. reflex/components/__init__.py +31 -17
  8. reflex/components/__init__.pyi +26 -0
  9. reflex/components/base/__init__.py +25 -9
  10. reflex/components/base/__init__.pyi +26 -0
  11. reflex/components/base/fragment.py +3 -0
  12. reflex/components/base/fragment.pyi +2 -0
  13. reflex/components/base/head.py +3 -0
  14. reflex/components/base/head.pyi +2 -0
  15. reflex/components/base/script.py +3 -0
  16. reflex/components/base/script.pyi +2 -0
  17. reflex/components/core/__init__.py +51 -37
  18. reflex/components/core/__init__.pyi +39 -0
  19. reflex/components/core/banner.py +7 -1
  20. reflex/components/core/banner.pyi +6 -1
  21. reflex/components/core/debounce.py +3 -0
  22. reflex/components/core/debounce.pyi +2 -0
  23. reflex/components/core/foreach.py +3 -0
  24. reflex/components/core/html.py +4 -1
  25. reflex/components/core/html.pyi +2 -0
  26. reflex/components/core/match.py +3 -0
  27. reflex/components/core/responsive.py +1 -1
  28. reflex/components/core/upload.py +5 -2
  29. reflex/components/core/upload.pyi +4 -2
  30. reflex/components/datadisplay/__init__.py +17 -8
  31. reflex/components/datadisplay/__init__.pyi +14 -0
  32. reflex/components/datadisplay/code.py +3 -0
  33. reflex/components/datadisplay/code.pyi +2 -0
  34. reflex/components/datadisplay/dataeditor.py +4 -0
  35. reflex/components/datadisplay/dataeditor.pyi +3 -0
  36. reflex/components/el/__init__.py +15 -1
  37. reflex/components/el/__init__.pyi +228 -0
  38. reflex/components/el/elements/__init__.py +129 -220
  39. reflex/components/el/elements/__init__.pyi +342 -0
  40. reflex/components/el/elements/forms.py +15 -0
  41. reflex/components/el/elements/forms.pyi +14 -0
  42. reflex/components/el/elements/inline.py +30 -0
  43. reflex/components/el/elements/inline.pyi +29 -0
  44. reflex/components/el/elements/media.py +16 -0
  45. reflex/components/el/elements/media.pyi +15 -0
  46. reflex/components/el/elements/metadata.py +7 -0
  47. reflex/components/el/elements/metadata.pyi +6 -0
  48. reflex/components/el/elements/other.py +9 -0
  49. reflex/components/el/elements/other.pyi +8 -0
  50. reflex/components/el/elements/scripts.py +5 -0
  51. reflex/components/el/elements/scripts.pyi +4 -0
  52. reflex/components/el/elements/sectioning.py +17 -0
  53. reflex/components/el/elements/sectioning.pyi +16 -0
  54. reflex/components/el/elements/tables.py +12 -0
  55. reflex/components/el/elements/tables.pyi +11 -0
  56. reflex/components/el/elements/typography.py +16 -0
  57. reflex/components/el/elements/typography.pyi +15 -0
  58. reflex/components/moment/__init__.py +1 -1
  59. reflex/components/plotly/plotly.py +185 -7
  60. reflex/components/plotly/plotly.pyi +62 -4
  61. reflex/components/radix/__init__.py +14 -2
  62. reflex/components/radix/__init__.pyi +73 -0
  63. reflex/components/radix/primitives/__init__.py +13 -5
  64. reflex/components/radix/primitives/__init__.pyi +11 -0
  65. reflex/components/radix/themes/__init__.py +20 -6
  66. reflex/components/radix/themes/__init__.pyi +13 -0
  67. reflex/components/radix/themes/base.py +26 -20
  68. reflex/components/radix/themes/base.pyi +4 -1
  69. reflex/components/radix/themes/color_mode.py +3 -1
  70. reflex/components/radix/themes/color_mode.pyi +3 -1
  71. reflex/components/radix/themes/components/__init__.py +11 -79
  72. reflex/components/radix/themes/components/__init__.pyi +44 -0
  73. reflex/components/radix/themes/components/alert_dialog.py +2 -2
  74. reflex/components/radix/themes/components/alert_dialog.pyi +2 -2
  75. reflex/components/radix/themes/components/badge.py +2 -2
  76. reflex/components/radix/themes/components/badge.pyi +2 -2
  77. reflex/components/radix/themes/components/button.py +2 -2
  78. reflex/components/radix/themes/components/button.pyi +2 -2
  79. reflex/components/radix/themes/components/callout.py +4 -4
  80. reflex/components/radix/themes/components/callout.pyi +4 -4
  81. reflex/components/radix/themes/components/card.py +2 -2
  82. reflex/components/radix/themes/components/card.pyi +2 -2
  83. reflex/components/radix/themes/components/dialog.py +2 -2
  84. reflex/components/radix/themes/components/dialog.pyi +2 -2
  85. reflex/components/radix/themes/components/hover_card.py +2 -2
  86. reflex/components/radix/themes/components/hover_card.pyi +2 -2
  87. reflex/components/radix/themes/components/icon_button.py +2 -2
  88. reflex/components/radix/themes/components/icon_button.pyi +2 -2
  89. reflex/components/radix/themes/components/inset.py +2 -2
  90. reflex/components/radix/themes/components/inset.pyi +2 -2
  91. reflex/components/radix/themes/components/popover.py +2 -2
  92. reflex/components/radix/themes/components/popover.pyi +2 -2
  93. reflex/components/radix/themes/components/table.py +8 -8
  94. reflex/components/radix/themes/components/table.pyi +8 -8
  95. reflex/components/radix/themes/components/text_area.py +11 -2
  96. reflex/components/radix/themes/components/text_area.pyi +18 -3
  97. reflex/components/radix/themes/components/text_field.py +3 -3
  98. reflex/components/radix/themes/components/text_field.pyi +3 -3
  99. reflex/components/radix/themes/layout/__init__.py +12 -38
  100. reflex/components/radix/themes/layout/__init__.pyi +21 -0
  101. reflex/components/radix/themes/layout/box.py +5 -2
  102. reflex/components/radix/themes/layout/box.pyi +4 -2
  103. reflex/components/radix/themes/layout/center.py +3 -0
  104. reflex/components/radix/themes/layout/center.pyi +2 -0
  105. reflex/components/radix/themes/layout/container.py +5 -2
  106. reflex/components/radix/themes/layout/container.pyi +4 -2
  107. reflex/components/radix/themes/layout/flex.py +5 -2
  108. reflex/components/radix/themes/layout/flex.pyi +4 -2
  109. reflex/components/radix/themes/layout/grid.py +5 -2
  110. reflex/components/radix/themes/layout/grid.pyi +4 -2
  111. reflex/components/radix/themes/layout/list.py +14 -0
  112. reflex/components/radix/themes/layout/list.pyi +3 -0
  113. reflex/components/radix/themes/layout/section.py +7 -4
  114. reflex/components/radix/themes/layout/section.pyi +5 -3
  115. reflex/components/radix/themes/layout/spacer.py +3 -0
  116. reflex/components/radix/themes/layout/spacer.pyi +2 -0
  117. reflex/components/radix/themes/layout/stack.py +5 -0
  118. reflex/components/radix/themes/layout/stack.pyi +4 -0
  119. reflex/components/radix/themes/typography/__init__.py +11 -16
  120. reflex/components/radix/themes/typography/__init__.pyi +12 -0
  121. reflex/components/radix/themes/typography/blockquote.py +5 -2
  122. reflex/components/radix/themes/typography/blockquote.pyi +4 -2
  123. reflex/components/radix/themes/typography/code.py +5 -2
  124. reflex/components/radix/themes/typography/code.pyi +4 -2
  125. reflex/components/radix/themes/typography/heading.py +5 -2
  126. reflex/components/radix/themes/typography/heading.pyi +4 -2
  127. reflex/components/radix/themes/typography/link.py +3 -0
  128. reflex/components/radix/themes/typography/link.pyi +2 -0
  129. reflex/components/radix/themes/typography/text.py +6 -6
  130. reflex/components/radix/themes/typography/text.pyi +6 -6
  131. reflex/components/recharts/__init__.py +114 -104
  132. reflex/components/recharts/__init__.pyi +106 -0
  133. reflex/components/recharts/cartesian.py +17 -0
  134. reflex/components/recharts/cartesian.pyi +16 -0
  135. reflex/components/recharts/charts.py +12 -0
  136. reflex/components/recharts/charts.pyi +11 -0
  137. reflex/components/recharts/general.py +7 -0
  138. reflex/components/recharts/general.pyi +6 -0
  139. reflex/components/recharts/polar.py +11 -0
  140. reflex/components/recharts/polar.pyi +9 -0
  141. reflex/config.py +3 -0
  142. reflex/constants/__init__.py +0 -2
  143. reflex/constants/base.py +5 -1
  144. reflex/constants/installer.py +2 -1
  145. reflex/experimental/__init__.py +2 -0
  146. reflex/experimental/assets.py +56 -0
  147. reflex/experimental/client_state.py +4 -2
  148. reflex/experimental/hooks.py +8 -6
  149. reflex/experimental/layout.py +3 -1
  150. reflex/state.py +54 -4
  151. reflex/utils/exec.py +8 -0
  152. reflex/utils/format.py +8 -4
  153. reflex/utils/lazy_loader.py +33 -0
  154. reflex/utils/prerequisites.py +1 -14
  155. reflex/utils/pyi_generator.py +71 -20
  156. reflex/utils/serializers.py +9 -4
  157. reflex/utils/types.py +3 -1
  158. reflex/vars.py +92 -6
  159. reflex/vars.pyi +16 -0
  160. {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/METADATA +2 -1
  161. {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/RECORD +164 -151
  162. reflex/config.pyi +0 -112
  163. reflex/constants/base.pyi +0 -94
  164. {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/LICENSE +0 -0
  165. {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/WHEEL +0 -0
  166. {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1,12 @@
1
1
  """Add standard Hooks wrapper for React."""
2
2
 
3
+ from typing import Optional, Union
4
+
3
5
  from reflex.utils.imports import ImportVar
4
6
  from reflex.vars import Var, VarData
5
7
 
6
8
 
7
- def _add_react_import(v: Var | None, tags: str | list):
9
+ def _add_react_import(v: Optional[Var], tags: Union[str, list]):
8
10
  if v is None:
9
11
  return
10
12
 
@@ -16,7 +18,7 @@ def _add_react_import(v: Var | None, tags: str | list):
16
18
  )
17
19
 
18
20
 
19
- def const(name, value) -> Var | None:
21
+ def const(name, value) -> Optional[Var]:
20
22
  """Create a constant Var.
21
23
 
22
24
  Args:
@@ -31,7 +33,7 @@ def const(name, value) -> Var | None:
31
33
  return Var.create(f"const {name} = {value}")
32
34
 
33
35
 
34
- def useCallback(func, deps) -> Var | None:
36
+ def useCallback(func, deps) -> Optional[Var]:
35
37
  """Create a useCallback hook with a function and dependencies.
36
38
 
37
39
  Args:
@@ -49,7 +51,7 @@ def useCallback(func, deps) -> Var | None:
49
51
  return v
50
52
 
51
53
 
52
- def useContext(context) -> Var | None:
54
+ def useContext(context) -> Optional[Var]:
53
55
  """Create a useContext hook with a context.
54
56
 
55
57
  Args:
@@ -63,7 +65,7 @@ def useContext(context) -> Var | None:
63
65
  return v
64
66
 
65
67
 
66
- def useRef(default) -> Var | None:
68
+ def useRef(default) -> Optional[Var]:
67
69
  """Create a useRef hook with a default value.
68
70
 
69
71
  Args:
@@ -77,7 +79,7 @@ def useRef(default) -> Var | None:
77
79
  return v
78
80
 
79
81
 
80
- def useState(var_name, default=None) -> Var | None:
82
+ def useState(var_name, default=None) -> Optional[Var]:
81
83
  """Create a useState hook with a variable name and setter name.
82
84
 
83
85
  Args:
@@ -9,7 +9,9 @@ from reflex.components.base.fragment import Fragment
9
9
  from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
10
10
  from reflex.components.radix.primitives.drawer import DrawerRoot, drawer
11
11
  from reflex.components.radix.themes.components.icon_button import IconButton
12
- from reflex.components.radix.themes.layout import Box, Container, HStack
12
+ from reflex.components.radix.themes.layout.box import Box
13
+ from reflex.components.radix.themes.layout.container import Container
14
+ from reflex.components.radix.themes.layout.stack import HStack
13
15
  from reflex.event import call_script
14
16
  from reflex.experimental import hooks
15
17
  from reflex.state import ComponentState
reflex/state.py CHANGED
@@ -7,6 +7,7 @@ import contextlib
7
7
  import copy
8
8
  import functools
9
9
  import inspect
10
+ import os
10
11
  import traceback
11
12
  import uuid
12
13
  from abc import ABC, abstractmethod
@@ -35,6 +36,7 @@ except ModuleNotFoundError:
35
36
 
36
37
  import wrapt
37
38
  from redis.asyncio import Redis
39
+ from redis.exceptions import ResponseError
38
40
 
39
41
  from reflex import constants
40
42
  from reflex.base import Base
@@ -1536,6 +1538,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1536
1538
  if actual_var is not None:
1537
1539
  actual_var.mark_dirty(instance=self)
1538
1540
 
1541
+ def _expired_computed_vars(self) -> set[str]:
1542
+ """Determine ComputedVars that need to be recalculated based on the expiration time.
1543
+
1544
+ Returns:
1545
+ Set of computed vars to include in the delta.
1546
+ """
1547
+ return set(
1548
+ cvar
1549
+ for cvar in self.computed_vars
1550
+ if self.computed_vars[cvar].needs_update(instance=self)
1551
+ )
1552
+
1539
1553
  def _dirty_computed_vars(self, from_vars: set[str] | None = None) -> set[str]:
1540
1554
  """Determine ComputedVars that need to be recalculated based on the given vars.
1541
1555
 
@@ -1588,6 +1602,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1588
1602
  # and always dirty computed vars (cache=False)
1589
1603
  delta_vars = (
1590
1604
  self.dirty_vars.intersection(self.base_vars)
1605
+ .union(self.dirty_vars.intersection(self.computed_vars))
1591
1606
  .union(self._dirty_computed_vars())
1592
1607
  .union(self._always_dirty_computed_vars)
1593
1608
  )
@@ -1621,6 +1636,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1621
1636
  self.parent_state.dirty_substates.add(self.get_name())
1622
1637
  self.parent_state._mark_dirty()
1623
1638
 
1639
+ # Append expired computed vars to dirty_vars to trigger recalculation
1640
+ self.dirty_vars.update(self._expired_computed_vars())
1641
+
1624
1642
  # have to mark computed vars dirty to allow access to newly computed
1625
1643
  # values within the same ComputedVar function
1626
1644
  self._mark_dirty_computed_vars()
@@ -2622,13 +2640,26 @@ class StateManagerRedis(StateManager):
2622
2640
  Args:
2623
2641
  lock_key: The redis key for the lock.
2624
2642
  lock_id: The ID of the lock.
2643
+
2644
+ Raises:
2645
+ ResponseError: when the keyspace config cannot be set.
2625
2646
  """
2626
2647
  state_is_locked = False
2627
2648
  lock_key_channel = f"__keyspace@0__:{lock_key.decode()}"
2628
2649
  # Enable keyspace notifications for the lock key, so we know when it is available.
2629
- await self.redis.config_set(
2630
- "notify-keyspace-events", self._redis_notify_keyspace_events
2631
- )
2650
+ try:
2651
+ await self.redis.config_set(
2652
+ "notify-keyspace-events",
2653
+ self._redis_notify_keyspace_events,
2654
+ )
2655
+ except ResponseError:
2656
+ # Some redis servers only allow out-of-band configuration, so ignore errors here.
2657
+ ignore_config_error = os.environ.get(
2658
+ "REFLEX_IGNORE_REDIS_CONFIG_ERROR",
2659
+ None,
2660
+ )
2661
+ if not ignore_config_error:
2662
+ raise
2632
2663
  async with self.redis.pubsub() as pubsub:
2633
2664
  await pubsub.psubscribe(lock_key_channel)
2634
2665
  while not state_is_locked:
@@ -2836,6 +2867,11 @@ class MutableProxy(wrapt.ObjectProxy):
2836
2867
  ]
2837
2868
  )
2838
2869
 
2870
+ # These internal attributes on rx.Base should NOT be wrapped in a MutableProxy.
2871
+ __never_wrap_base_attrs__ = set(Base.__dict__) - {"set"} | set(
2872
+ pydantic.BaseModel.__dict__
2873
+ )
2874
+
2839
2875
  __mutable_types__ = (list, dict, set, Base)
2840
2876
 
2841
2877
  def __init__(self, wrapped: Any, state: BaseState, field_name: str):
@@ -2885,7 +2921,10 @@ class MutableProxy(wrapt.ObjectProxy):
2885
2921
  Returns:
2886
2922
  The wrapped value.
2887
2923
  """
2888
- if isinstance(value, self.__mutable_types__):
2924
+ # Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
2925
+ if isinstance(value, self.__mutable_types__) and not isinstance(
2926
+ value, MutableProxy
2927
+ ):
2889
2928
  return type(self)(
2890
2929
  wrapped=value,
2891
2930
  state=self._self_state,
@@ -2932,6 +2971,17 @@ class MutableProxy(wrapt.ObjectProxy):
2932
2971
  self._wrap_recursive_decorator,
2933
2972
  )
2934
2973
 
2974
+ if (
2975
+ isinstance(self.__wrapped__, Base)
2976
+ and __name not in self.__never_wrap_base_attrs__
2977
+ and hasattr(value, "__func__")
2978
+ ):
2979
+ # Wrap methods called on Base subclasses, which might do _anything_
2980
+ return wrapt.FunctionWrapper(
2981
+ functools.partial(value.__func__, self),
2982
+ self._wrap_recursive_decorator,
2983
+ )
2984
+
2935
2985
  if isinstance(value, self.__mutable_types__) and __name not in (
2936
2986
  "__wrapped__",
2937
2987
  "_self_state",
reflex/utils/exec.py CHANGED
@@ -113,6 +113,14 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True):
113
113
  else:
114
114
  console.print("New packages detected: Updating app...")
115
115
  else:
116
+ if any(
117
+ [x in line for x in ("bin executable does not exist on disk",)]
118
+ ):
119
+ console.error(
120
+ "Try setting `REFLEX_USE_NPM=1` and re-running `reflex init` and `reflex run` to use npm instead of bun:\n"
121
+ "`REFLEX_USE_NPM=1 reflex init`\n"
122
+ "`REFLEX_USE_NPM=1 reflex run`"
123
+ )
116
124
  new_hash = detect_package_change(json_file_path)
117
125
  if new_hash != last_hash:
118
126
  last_hash = new_hash
reflex/utils/format.py CHANGED
@@ -9,8 +9,7 @@ import re
9
9
  from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
10
10
 
11
11
  from reflex import constants
12
- from reflex.utils import exceptions, serializers, types
13
- from reflex.utils.serializers import serialize
12
+ from reflex.utils import exceptions, types
14
13
  from reflex.vars import BaseVar, Var
15
14
 
16
15
  if TYPE_CHECKING:
@@ -400,6 +399,7 @@ def format_prop(
400
399
  """
401
400
  # import here to avoid circular import.
402
401
  from reflex.event import EventChain
402
+ from reflex.utils import serializers
403
403
 
404
404
  try:
405
405
  # Handle var props.
@@ -687,6 +687,8 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
687
687
  Raises:
688
688
  TypeError: If the given value is not a valid state.
689
689
  """
690
+ from reflex.utils import serializers
691
+
690
692
  # Handle dicts.
691
693
  if isinstance(value, dict):
692
694
  return {k: format_state(v, k) for k, v in value.items()}
@@ -700,7 +702,7 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
700
702
  return value
701
703
 
702
704
  # Serialize the value.
703
- serialized = serialize(value)
705
+ serialized = serializers.serialize(value)
704
706
  if serialized is not None:
705
707
  return serialized
706
708
 
@@ -803,7 +805,9 @@ def json_dumps(obj: Any) -> str:
803
805
  Returns:
804
806
  A string
805
807
  """
806
- return json.dumps(obj, ensure_ascii=False, default=serialize)
808
+ from reflex.utils import serializers
809
+
810
+ return json.dumps(obj, ensure_ascii=False, default=serializers.serialize)
807
811
 
808
812
 
809
813
  def unwrap_vars(value: str) -> str:
@@ -0,0 +1,33 @@
1
+ """Module to implement lazy loading in reflex."""
2
+ import copy
3
+
4
+ import lazy_loader as lazy
5
+
6
+
7
+ def attach(package_name, submodules=None, submod_attrs=None):
8
+ """Replaces a package's __getattr__, __dir__, and __all__ attributes using lazy.attach.
9
+ The lazy loader __getattr__ doesn't support tuples as list values. We needed to add
10
+ this functionality (tuples) in Reflex to support 'import as _' statements. This function
11
+ reformats the submod_attrs dictionary to flatten the module list before passing it to
12
+ lazy_loader.
13
+
14
+ Args:
15
+ package_name: name of the package.
16
+ submodules : List of submodules to attach.
17
+ submod_attrs : Dictionary of submodule -> list of attributes / functions.
18
+ These attributes are imported as they are used.
19
+
20
+ Returns:
21
+ __getattr__, __dir__, __all__
22
+ """
23
+ _submod_attrs = copy.deepcopy(submod_attrs)
24
+ if _submod_attrs:
25
+ for k, v in _submod_attrs.items():
26
+ # when flattening the list, only keep the alias in the tuple(mod[1])
27
+ _submod_attrs[k] = [
28
+ mod if not isinstance(mod, tuple) else mod[1] for mod in v
29
+ ]
30
+
31
+ return lazy.attach(
32
+ package_name=package_name, submodules=submodules, submod_attrs=_submod_attrs
33
+ )
@@ -332,19 +332,6 @@ def parse_redis_url() -> str | dict | None:
332
332
  return dict(host=redis_url, port=int(redis_port), db=0)
333
333
 
334
334
 
335
- def get_production_backend_url() -> str:
336
- """Get the production backend URL.
337
-
338
- Returns:
339
- The production backend URL.
340
- """
341
- config = get_config()
342
- return constants.PRODUCTION_BACKEND_URL.format(
343
- username=config.username,
344
- app_name=config.app_name,
345
- )
346
-
347
-
348
335
  def validate_app_name(app_name: str | None = None) -> str:
349
336
  """Validate the app name.
350
337
 
@@ -625,7 +612,7 @@ def _update_next_config(
625
612
  next_config = {
626
613
  "basePath": config.frontend_path or "",
627
614
  "compress": config.next_compression,
628
- "reactStrictMode": True,
615
+ "reactStrictMode": config.react_strict_mode,
629
616
  "trailingSlash": True,
630
617
  }
631
618
  if transpile_packages:
@@ -29,11 +29,9 @@ from reflex.vars import Var
29
29
 
30
30
  logger = logging.getLogger("pyi_generator")
31
31
 
32
- INIT_FILE = Path("reflex/__init__.pyi").resolve()
33
32
  PWD = Path(".").resolve()
34
33
 
35
34
  EXCLUDED_FILES = [
36
- "__init__.py",
37
35
  # "app.py",
38
36
  "component.py",
39
37
  "bare.py",
@@ -322,6 +320,7 @@ def _extract_class_props_as_ast_nodes(
322
320
  all_props = []
323
321
  kwargs = []
324
322
  for target_class in clzs:
323
+ event_triggers = target_class().get_event_triggers()
325
324
  # Import from the target class to ensure type hints are resolvable.
326
325
  exec(f"from {target_class.__module__} import *", type_hint_globals)
327
326
  for name, value in target_class.__annotations__.items():
@@ -329,6 +328,7 @@ def _extract_class_props_as_ast_nodes(
329
328
  name in spec.kwonlyargs
330
329
  or name in EXCLUDED_PROPS
331
330
  or name in all_props
331
+ or name in event_triggers
332
332
  or (isinstance(value, str) and "ClassVar" in value)
333
333
  ):
334
334
  continue
@@ -775,6 +775,13 @@ class StubGenerator(ast.NodeTransformer):
775
775
  # Remove annotated assignments in Component classes (props)
776
776
  return None
777
777
 
778
+ # remove dunder method assignments for lazy_loader.attach
779
+ for target in node.targets:
780
+ if isinstance(target, ast.Tuple):
781
+ for name in target.elts:
782
+ if isinstance(name, ast.Name) and name.id.startswith("_"):
783
+ return
784
+
778
785
  return node
779
786
 
780
787
  def visit_AnnAssign(self, node: ast.AnnAssign) -> ast.AnnAssign | None:
@@ -805,6 +812,23 @@ class StubGenerator(ast.NodeTransformer):
805
812
  return node
806
813
 
807
814
 
815
+ class InitStubGenerator(StubGenerator):
816
+ """A node transformer that will generate the stubs for a given init file."""
817
+
818
+ def visit_Import(
819
+ self, node: ast.Import | ast.ImportFrom
820
+ ) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom]:
821
+ """Collect import statements from the init module.
822
+
823
+ Args:
824
+ node: The import node to visit.
825
+
826
+ Returns:
827
+ The modified import node(s).
828
+ """
829
+ return [node]
830
+
831
+
808
832
  class PyiGenerator:
809
833
  """A .pyi file generator that will scan all defined Component in Reflex and
810
834
  generate the approriate stub.
@@ -842,6 +866,37 @@ class PyiGenerator:
842
866
  pyi_path.write_text("\n".join(pyi_content))
843
867
  logger.info(f"Wrote {relpath}")
844
868
 
869
+ def _get_init_lazy_imports(self, mod, new_tree):
870
+ # retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present.
871
+ sub_mods = getattr(mod, "_SUBMODULES", None)
872
+ sub_mod_attrs = getattr(mod, "_SUBMOD_ATTRS", None)
873
+
874
+ if not sub_mods and not sub_mod_attrs:
875
+ return
876
+ sub_mods_imports = []
877
+ sub_mod_attrs_imports = []
878
+
879
+ if sub_mods:
880
+ sub_mods_imports = [
881
+ f"from . import {mod} as {mod}" for mod in sorted(sub_mods)
882
+ ]
883
+ sub_mods_imports.append("")
884
+
885
+ if sub_mod_attrs:
886
+ sub_mod_attrs = {
887
+ attr: mod for mod, attrs in sub_mod_attrs.items() for attr in attrs
888
+ }
889
+ # construct the import statement and handle special cases for aliases
890
+ sub_mod_attrs_imports = [
891
+ f"from .{path} import {mod if not isinstance(mod, tuple) else mod[0]} as {mod if not isinstance(mod, tuple) else mod[1]}"
892
+ for mod, path in sub_mod_attrs.items()
893
+ ]
894
+ sub_mod_attrs_imports.append("")
895
+
896
+ text = "\n" + "\n".join([*sub_mods_imports, *sub_mod_attrs_imports])
897
+ text += ast.unparse(new_tree) + "\n"
898
+ return text
899
+
845
900
  def _scan_file(self, module_path: Path):
846
901
  module_import = (
847
902
  _relative_to_pwd(module_path)
@@ -860,13 +915,22 @@ class PyiGenerator:
860
915
  and obj != Component
861
916
  and inspect.getmodule(obj) == module
862
917
  }
863
- if not class_names:
918
+ is_init_file = _relative_to_pwd(module_path).name == "__init__.py"
919
+ if not class_names and not is_init_file:
864
920
  return
865
921
 
866
- new_tree = StubGenerator(module, class_names).visit(
867
- ast.parse(inspect.getsource(module))
868
- )
869
- self._write_pyi_file(module_path, ast.unparse(new_tree))
922
+ if is_init_file:
923
+ new_tree = InitStubGenerator(module, class_names).visit(
924
+ ast.parse(inspect.getsource(module))
925
+ )
926
+ init_imports = self._get_init_lazy_imports(module, new_tree)
927
+ if init_imports:
928
+ self._write_pyi_file(module_path, init_imports)
929
+ else:
930
+ new_tree = StubGenerator(module, class_names).visit(
931
+ ast.parse(inspect.getsource(module))
932
+ )
933
+ self._write_pyi_file(module_path, ast.unparse(new_tree))
870
934
 
871
935
  def _scan_files_multiprocess(self, files: list[Path]):
872
936
  with Pool(processes=cpu_count()) as pool:
@@ -922,16 +986,3 @@ class PyiGenerator:
922
986
  self._scan_files(file_targets)
923
987
  else:
924
988
  self._scan_files_multiprocess(file_targets)
925
-
926
-
927
- def generate_init():
928
- """Generate a pyi file for the main __init__.py."""
929
- from reflex import _MAPPING # type: ignore
930
-
931
- imports = [
932
- f"from {path if mod != path.rsplit('.')[-1] or mod == 'page' else '.'.join(path.rsplit('.')[:-1])} import {mod} as {mod}"
933
- for mod, path in _MAPPING.items()
934
- ]
935
- imports.append("")
936
- with contextlib.suppress(Exception):
937
- INIT_FILE.write_text("\n".join(imports))
@@ -12,7 +12,7 @@ from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union, get_type_
12
12
 
13
13
  from reflex.base import Base
14
14
  from reflex.constants.colors import Color, format_color
15
- from reflex.utils import exceptions, format, types
15
+ from reflex.utils import exceptions, types
16
16
 
17
17
  # Mapping from type to a serializer.
18
18
  # The serializer should convert the type to a JSON object.
@@ -154,6 +154,8 @@ def serialize_primitive(value: Union[bool, int, float, None]) -> str:
154
154
  Returns:
155
155
  The serialized number/bool/None.
156
156
  """
157
+ from reflex.utils import format
158
+
157
159
  return format.json_dumps(value)
158
160
 
159
161
 
@@ -180,6 +182,8 @@ def serialize_list(value: Union[List, Tuple, Set]) -> str:
180
182
  Returns:
181
183
  The serialized list.
182
184
  """
185
+ from reflex.utils import format
186
+
183
187
  # Dump the list to a string.
184
188
  fprop = format.json_dumps(list(value))
185
189
 
@@ -202,6 +206,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
202
206
  """
203
207
  # Import here to avoid circular imports.
204
208
  from reflex.event import EventHandler
209
+ from reflex.utils import format
205
210
 
206
211
  prop_dict = {}
207
212
 
@@ -255,7 +260,7 @@ def serialize_enum(en: Enum) -> str:
255
260
  en: The enum to serialize.
256
261
 
257
262
  Returns:
258
- The serialized enum.
263
+ The serialized enum.
259
264
  """
260
265
  return en.value
261
266
 
@@ -313,7 +318,7 @@ try:
313
318
  from plotly.io import to_json
314
319
 
315
320
  @serializer
316
- def serialize_figure(figure: Figure) -> list:
321
+ def serialize_figure(figure: Figure) -> dict:
317
322
  """Serialize a plotly figure.
318
323
 
319
324
  Args:
@@ -322,7 +327,7 @@ try:
322
327
  Returns:
323
328
  The serialized figure.
324
329
  """
325
- return json.loads(str(to_json(figure)))["data"]
330
+ return json.loads(str(to_json(figure)))
326
331
 
327
332
  except ImportError:
328
333
  pass
reflex/utils/types.py CHANGED
@@ -42,7 +42,7 @@ from sqlalchemy.orm import (
42
42
 
43
43
  from reflex import constants
44
44
  from reflex.base import Base
45
- from reflex.utils import console, serializers
45
+ from reflex.utils import console
46
46
 
47
47
  if sys.version_info >= (3, 12):
48
48
  from typing import override
@@ -392,6 +392,8 @@ def is_valid_var_type(type_: Type) -> bool:
392
392
  Returns:
393
393
  Whether the type is a valid prop type.
394
394
  """
395
+ from reflex.utils import serializers
396
+
395
397
  if is_union(type_):
396
398
  return all((is_valid_var_type(arg) for arg in get_args(type_)))
397
399
  return _issubclass(type_, StateVar) or serializers.has_serializer(type_)