reflex 0.3.9a3__py3-none-any.whl → 0.3.10__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 (247) hide show
  1. reflex/__init__.py +3 -1
  2. reflex/__init__.pyi +3 -1
  3. reflex/app.py +116 -86
  4. reflex/compiler/compiler.py +110 -0
  5. reflex/compiler/utils.py +13 -12
  6. reflex/components/base/app_wrap.pyi +1 -0
  7. reflex/components/base/body.pyi +2 -0
  8. reflex/components/base/document.pyi +10 -0
  9. reflex/components/base/fragment.pyi +2 -0
  10. reflex/components/base/head.pyi +4 -0
  11. reflex/components/base/link.pyi +4 -0
  12. reflex/components/base/meta.pyi +8 -0
  13. reflex/components/base/script.pyi +2 -0
  14. reflex/components/chakra/base.pyi +7 -0
  15. reflex/components/chakra/datadisplay/badge.pyi +2 -0
  16. reflex/components/chakra/datadisplay/code.pyi +4 -0
  17. reflex/components/chakra/datadisplay/divider.pyi +2 -0
  18. reflex/components/chakra/datadisplay/keyboard_key.pyi +2 -0
  19. reflex/components/chakra/datadisplay/list.pyi +8 -0
  20. reflex/components/chakra/datadisplay/stat.pyi +12 -0
  21. reflex/components/chakra/datadisplay/table.pyi +18 -0
  22. reflex/components/chakra/datadisplay/tag.pyi +9 -0
  23. reflex/components/chakra/disclosure/accordion.pyi +10 -0
  24. reflex/components/chakra/disclosure/tabs.py +8 -0
  25. reflex/components/chakra/disclosure/tabs.pyi +10 -0
  26. reflex/components/chakra/disclosure/transition.pyi +12 -0
  27. reflex/components/chakra/disclosure/visuallyhidden.pyi +2 -0
  28. reflex/components/chakra/feedback/alert.pyi +8 -0
  29. reflex/components/chakra/feedback/circularprogress.pyi +4 -0
  30. reflex/components/chakra/feedback/progress.pyi +2 -0
  31. reflex/components/chakra/feedback/skeleton.pyi +6 -0
  32. reflex/components/chakra/feedback/spinner.pyi +2 -0
  33. reflex/components/chakra/forms/button.pyi +4 -0
  34. reflex/components/chakra/forms/checkbox.pyi +4 -0
  35. reflex/components/chakra/forms/colormodeswitch.pyi +7 -0
  36. reflex/components/chakra/forms/date_picker.pyi +2 -0
  37. reflex/components/chakra/forms/date_time_picker.pyi +2 -0
  38. reflex/components/chakra/forms/editable.pyi +8 -0
  39. reflex/components/chakra/forms/email.pyi +2 -0
  40. reflex/components/chakra/forms/form.pyi +10 -0
  41. reflex/components/chakra/forms/iconbutton.py +1 -0
  42. reflex/components/chakra/forms/iconbutton.pyi +2 -0
  43. reflex/components/chakra/forms/input.pyi +12 -0
  44. reflex/components/chakra/forms/numberinput.pyi +10 -0
  45. reflex/components/chakra/forms/password.pyi +2 -0
  46. reflex/components/chakra/forms/pininput.pyi +4 -0
  47. reflex/components/chakra/forms/radio.pyi +4 -0
  48. reflex/components/chakra/forms/rangeslider.pyi +8 -0
  49. reflex/components/chakra/forms/select.pyi +4 -0
  50. reflex/components/chakra/forms/slider.pyi +10 -0
  51. reflex/components/chakra/forms/switch.pyi +2 -0
  52. reflex/components/chakra/forms/textarea.pyi +2 -0
  53. reflex/components/chakra/forms/time_picker.pyi +2 -0
  54. reflex/components/chakra/layout/aspect_ratio.pyi +2 -0
  55. reflex/components/chakra/layout/box.pyi +2 -0
  56. reflex/components/chakra/layout/card.pyi +7 -0
  57. reflex/components/chakra/layout/center.pyi +6 -0
  58. reflex/components/chakra/layout/container.pyi +2 -0
  59. reflex/components/chakra/layout/flex.pyi +2 -0
  60. reflex/components/chakra/layout/grid.pyi +6 -0
  61. reflex/components/chakra/layout/html.pyi +2 -0
  62. reflex/components/chakra/layout/spacer.pyi +2 -0
  63. reflex/components/chakra/layout/stack.pyi +6 -0
  64. reflex/components/chakra/layout/wrap.pyi +4 -0
  65. reflex/components/chakra/media/avatar.pyi +6 -0
  66. reflex/components/chakra/media/icon.pyi +4 -0
  67. reflex/components/chakra/media/image.pyi +2 -0
  68. reflex/components/chakra/navigation/breadcrumb.pyi +8 -0
  69. reflex/components/chakra/navigation/link.pyi +2 -0
  70. reflex/components/chakra/navigation/linkoverlay.pyi +4 -0
  71. reflex/components/chakra/navigation/stepper.pyi +18 -0
  72. reflex/components/chakra/overlay/alertdialog.pyi +14 -0
  73. reflex/components/chakra/overlay/drawer.pyi +14 -0
  74. reflex/components/chakra/overlay/menu.pyi +16 -0
  75. reflex/components/chakra/overlay/modal.pyi +14 -0
  76. reflex/components/chakra/overlay/popover.pyi +18 -0
  77. reflex/components/chakra/overlay/tooltip.pyi +2 -0
  78. reflex/components/chakra/typography/heading.pyi +2 -0
  79. reflex/components/chakra/typography/highlight.pyi +2 -0
  80. reflex/components/chakra/typography/span.pyi +2 -0
  81. reflex/components/chakra/typography/text.pyi +2 -0
  82. reflex/components/component.py +41 -3
  83. reflex/components/core/__init__.py +2 -0
  84. reflex/components/core/banner.pyi +3 -0
  85. reflex/components/core/client_side_routing.pyi +4 -0
  86. reflex/components/core/colors.py +21 -0
  87. reflex/components/core/cond.py +11 -2
  88. reflex/components/core/debounce.pyi +1 -0
  89. reflex/components/core/layout/__init__.py +1 -0
  90. reflex/components/core/match.py +44 -18
  91. reflex/components/core/upload.py +8 -2
  92. reflex/components/core/upload.pyi +7 -1
  93. reflex/components/datadisplay/dataeditor.pyi +2 -0
  94. reflex/components/el/element.pyi +2 -0
  95. reflex/components/el/elements/base.pyi +2 -0
  96. reflex/components/el/elements/forms.py +3 -0
  97. reflex/components/el/elements/forms.pyi +32 -0
  98. reflex/components/el/elements/inline.pyi +56 -0
  99. reflex/components/el/elements/media.pyi +28 -0
  100. reflex/components/el/elements/metadata.pyi +10 -0
  101. reflex/components/el/elements/other.pyi +14 -0
  102. reflex/components/el/elements/scripts.pyi +6 -0
  103. reflex/components/el/elements/sectioning.pyi +30 -0
  104. reflex/components/el/elements/tables.pyi +20 -0
  105. reflex/components/el/elements/typography.pyi +30 -0
  106. reflex/components/gridjs/datatable.pyi +4 -0
  107. reflex/components/lucide/__init__.py +5 -0
  108. reflex/components/lucide/icon.py +1484 -0
  109. reflex/components/lucide/icon.pyi +1594 -0
  110. reflex/components/markdown/markdown.pyi +2 -0
  111. reflex/components/moment/moment.pyi +2 -0
  112. reflex/components/next/base.pyi +2 -0
  113. reflex/components/next/image.pyi +2 -0
  114. reflex/components/next/link.pyi +2 -0
  115. reflex/components/next/video.pyi +2 -0
  116. reflex/components/plotly/plotly.pyi +4 -0
  117. reflex/components/radix/primitives/__init__.py +10 -0
  118. reflex/components/radix/primitives/accordion.py +51 -32
  119. reflex/components/radix/primitives/accordion.pyi +16 -3
  120. reflex/components/radix/primitives/base.pyi +4 -0
  121. reflex/components/radix/primitives/drawer.py +240 -0
  122. reflex/components/radix/primitives/drawer.pyi +814 -0
  123. reflex/components/radix/primitives/form.py +40 -7
  124. reflex/components/radix/primitives/form.pyi +32 -10
  125. reflex/components/radix/primitives/progress.py +2 -2
  126. reflex/components/radix/primitives/progress.pyi +6 -0
  127. reflex/components/radix/primitives/slider.pyi +10 -0
  128. reflex/components/radix/themes/base.py +46 -12
  129. reflex/components/radix/themes/base.pyi +23 -9
  130. reflex/components/radix/themes/components/__init__.py +4 -2
  131. reflex/components/radix/themes/components/alertdialog.py +13 -12
  132. reflex/components/radix/themes/components/alertdialog.pyi +23 -351
  133. reflex/components/radix/themes/components/aspectratio.py +2 -5
  134. reflex/components/radix/themes/components/aspectratio.pyi +4 -51
  135. reflex/components/radix/themes/components/avatar.py +3 -4
  136. reflex/components/radix/themes/components/avatar.pyi +4 -57
  137. reflex/components/radix/themes/components/badge.py +3 -4
  138. reflex/components/radix/themes/components/badge.pyi +4 -56
  139. reflex/components/radix/themes/components/button.py +2 -3
  140. reflex/components/radix/themes/components/button.pyi +3 -51
  141. reflex/components/radix/themes/components/callout.py +9 -12
  142. reflex/components/radix/themes/components/callout.pyi +24 -217
  143. reflex/components/radix/themes/components/card.py +1 -2
  144. reflex/components/radix/themes/components/card.pyi +4 -51
  145. reflex/components/radix/themes/components/checkbox.py +11 -5
  146. reflex/components/radix/themes/components/checkbox.pyi +13 -112
  147. reflex/components/radix/themes/components/contextmenu.py +21 -21
  148. reflex/components/radix/themes/components/contextmenu.pyi +26 -401
  149. reflex/components/radix/themes/components/dialog.py +13 -16
  150. reflex/components/radix/themes/components/dialog.pyi +20 -303
  151. reflex/components/radix/themes/components/dropdownmenu.py +196 -32
  152. reflex/components/radix/themes/components/dropdownmenu.pyi +147 -389
  153. reflex/components/radix/themes/components/hovercard.py +5 -5
  154. reflex/components/radix/themes/components/hovercard.pyi +11 -151
  155. reflex/components/radix/themes/components/iconbutton.py +56 -4
  156. reflex/components/radix/themes/components/iconbutton.pyi +25 -71
  157. reflex/components/radix/themes/components/icons.pyi +4 -0
  158. reflex/components/radix/themes/components/inset.py +1 -2
  159. reflex/components/radix/themes/components/inset.pyi +4 -51
  160. reflex/components/radix/themes/components/popover.py +12 -12
  161. reflex/components/radix/themes/components/popover.pyi +14 -201
  162. reflex/components/radix/themes/components/radiogroup.py +47 -20
  163. reflex/components/radix/themes/components/radiogroup.pyi +26 -171
  164. reflex/components/radix/themes/components/scrollarea.py +2 -3
  165. reflex/components/radix/themes/components/scrollarea.pyi +4 -51
  166. reflex/components/radix/themes/components/select.py +28 -25
  167. reflex/components/radix/themes/components/select.pyi +43 -419
  168. reflex/components/radix/themes/components/separator.py +4 -5
  169. reflex/components/radix/themes/components/separator.pyi +5 -52
  170. reflex/components/radix/themes/components/slider.py +8 -5
  171. reflex/components/radix/themes/components/slider.pyi +9 -60
  172. reflex/components/radix/themes/components/switch.py +6 -4
  173. reflex/components/radix/themes/components/switch.pyi +5 -53
  174. reflex/components/radix/themes/components/table.py +14 -15
  175. reflex/components/radix/themes/components/table.pyi +22 -351
  176. reflex/components/radix/themes/components/tabs.py +9 -6
  177. reflex/components/radix/themes/components/tabs.pyi +18 -205
  178. reflex/components/radix/themes/components/textarea.py +2 -3
  179. reflex/components/radix/themes/components/textarea.pyi +10 -53
  180. reflex/components/radix/themes/components/textfield.py +105 -4
  181. reflex/components/radix/themes/components/textfield.pyi +200 -108
  182. reflex/components/radix/themes/components/tooltip.py +102 -2
  183. reflex/components/radix/themes/components/tooltip.pyi +66 -110
  184. reflex/components/radix/themes/layout/__init__.py +7 -0
  185. reflex/components/radix/themes/layout/base.pyi +2 -0
  186. reflex/components/radix/themes/layout/box.py +2 -2
  187. reflex/components/radix/themes/layout/box.pyi +4 -104
  188. reflex/components/radix/themes/layout/center.py +19 -0
  189. reflex/components/radix/themes/layout/center.pyi +261 -0
  190. reflex/components/radix/themes/layout/container.py +2 -2
  191. reflex/components/radix/themes/layout/container.pyi +4 -104
  192. reflex/components/radix/themes/layout/flex.py +2 -2
  193. reflex/components/radix/themes/layout/flex.pyi +4 -105
  194. reflex/components/radix/themes/layout/grid.pyi +2 -0
  195. reflex/components/radix/themes/layout/section.py +2 -2
  196. reflex/components/radix/themes/layout/section.pyi +4 -104
  197. reflex/components/radix/themes/layout/spacer.py +19 -0
  198. reflex/components/radix/themes/layout/spacer.pyi +261 -0
  199. reflex/components/radix/themes/layout/stack.py +60 -0
  200. reflex/components/radix/themes/layout/stack.pyi +537 -0
  201. reflex/components/radix/themes/typography/blockquote.py +2 -3
  202. reflex/components/radix/themes/typography/blockquote.pyi +4 -51
  203. reflex/components/radix/themes/typography/code.py +2 -3
  204. reflex/components/radix/themes/typography/code.pyi +4 -56
  205. reflex/components/radix/themes/typography/em.py +1 -2
  206. reflex/components/radix/themes/typography/em.pyi +4 -51
  207. reflex/components/radix/themes/typography/heading.py +2 -3
  208. reflex/components/radix/themes/typography/heading.pyi +4 -51
  209. reflex/components/radix/themes/typography/kbd.py +1 -2
  210. reflex/components/radix/themes/typography/kbd.pyi +4 -51
  211. reflex/components/radix/themes/typography/link.py +34 -3
  212. reflex/components/radix/themes/typography/link.pyi +41 -86
  213. reflex/components/radix/themes/typography/quote.py +1 -2
  214. reflex/components/radix/themes/typography/quote.pyi +4 -51
  215. reflex/components/radix/themes/typography/strong.py +1 -2
  216. reflex/components/radix/themes/typography/strong.pyi +4 -51
  217. reflex/components/radix/themes/typography/text.py +2 -3
  218. reflex/components/radix/themes/typography/text.pyi +4 -51
  219. reflex/components/radix/themes/typography.py +10 -11
  220. reflex/components/react_player/audio.pyi +2 -0
  221. reflex/components/react_player/react_player.pyi +2 -0
  222. reflex/components/react_player/video.pyi +2 -0
  223. reflex/components/recharts/cartesian.pyi +38 -0
  224. reflex/components/recharts/charts.pyi +22 -0
  225. reflex/components/recharts/general.pyi +10 -0
  226. reflex/components/recharts/polar.pyi +12 -0
  227. reflex/components/recharts/recharts.pyi +4 -0
  228. reflex/components/suneditor/editor.pyi +2 -0
  229. reflex/components/tags/tag.py +1 -1
  230. reflex/constants/base.py +5 -1
  231. reflex/constants/colors.py +80 -0
  232. reflex/constants/event.py +10 -1
  233. reflex/page.py +1 -1
  234. reflex/reflex.py +4 -0
  235. reflex/style.py +8 -3
  236. reflex/testing.py +21 -10
  237. reflex/utils/format.py +13 -9
  238. reflex/utils/prerequisites.py +41 -3
  239. reflex/utils/serializers.py +14 -0
  240. reflex/utils/telemetry.py +8 -2
  241. reflex/utils/types.py +36 -2
  242. reflex/vars.py +53 -18
  243. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/METADATA +1 -2
  244. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/RECORD +247 -233
  245. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/WHEEL +1 -1
  246. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/LICENSE +0 -0
  247. {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/entry_points.txt +0 -0
@@ -64,7 +64,7 @@ class Tag(Base):
64
64
  """
65
65
  self.props.update(
66
66
  {
67
- format.to_camel_case(name): prop
67
+ format.to_camel_case(name, allow_hyphens=True): prop
68
68
  if types._isinstance(prop, Union[EventChain, dict])
69
69
  else Var.create(prop)
70
70
  for name, prop in kwargs.items()
reflex/constants/base.py CHANGED
@@ -61,10 +61,14 @@ class Reflex(SimpleNamespace):
61
61
 
62
62
  # Files and directories used to init a new project.
63
63
  # The directory to store reflex dependencies.
64
- DIR = (
64
+ # Get directory value from enviroment variables if it exists.
65
+ _dir = os.environ.get("REFLEX_DIR", "")
66
+
67
+ DIR = _dir or (
65
68
  # on windows, we use C:/Users/<username>/AppData/Local/reflex.
66
69
  # on macOS, we use ~/Library/Application Support/reflex.
67
70
  # on linux, we use ~/.local/share/reflex.
71
+ # If user sets REFLEX_DIR envroment variable use that instead.
68
72
  PlatformDirs(MODULE_NAME, False).user_data_dir
69
73
  )
70
74
  # The root directory of the reflex library.
@@ -0,0 +1,80 @@
1
+ """The colors used in Reflex are a wrapper around https://www.radix-ui.com/colors."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ ColorType = Literal[
7
+ "gray",
8
+ "mauve",
9
+ "slate",
10
+ "sage",
11
+ "olive",
12
+ "sand",
13
+ "tomato",
14
+ "red",
15
+ "ruby",
16
+ "crimson",
17
+ "pink",
18
+ "plum",
19
+ "purple",
20
+ "violet",
21
+ "iris",
22
+ "indigo",
23
+ "blue",
24
+ "cyan",
25
+ "teal",
26
+ "jade",
27
+ "green",
28
+ "grass",
29
+ "brown",
30
+ "orange",
31
+ "sky",
32
+ "mint",
33
+ "lime",
34
+ "yellow",
35
+ "amber",
36
+ "gold",
37
+ "bronze",
38
+ "gray",
39
+ ]
40
+
41
+ ShadeType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
42
+
43
+
44
+ def format_color(color: ColorType, shade: ShadeType, alpha: bool) -> str:
45
+ """Format a color as a CSS color string.
46
+
47
+ Args:
48
+ color: The color to use.
49
+ shade: The shade of the color to use.
50
+ alpha: Whether to use the alpha variant of the color.
51
+
52
+ Returns:
53
+ The formatted color.
54
+ """
55
+ return f"var(--{color}-{'a' if alpha else ''}{shade})"
56
+
57
+
58
+ @dataclass
59
+ class Color:
60
+ """A color in the Reflex color palette."""
61
+
62
+ # The color palette to use
63
+ color: ColorType
64
+
65
+ # The shade of the color to use
66
+ shade: ShadeType = 7
67
+
68
+ # Whether to use the alpha variant of the color
69
+ alpha: bool = False
70
+
71
+ def __format__(self, format_spec: str) -> str:
72
+ """Format the color as a CSS color string.
73
+
74
+ Args:
75
+ format_spec: The format specifier to use.
76
+
77
+ Returns:
78
+ The formatted color.
79
+ """
80
+ return format_color(self.color, self.shade, self.alpha)
reflex/constants/event.py CHANGED
@@ -65,7 +65,6 @@ class EventTriggers(SimpleNamespace):
65
65
  ON_CHANGE = "on_change"
66
66
  ON_CHANGE_END = "on_change_end"
67
67
  ON_CHANGE_START = "on_change_start"
68
- ON_CHECKED_CHANGE = "on_checked_change"
69
68
  ON_COMPLETE = "on_complete"
70
69
  ON_CONTEXT_MENU = "on_context_menu"
71
70
  ON_DOUBLE_CLICK = "on_double_click"
@@ -80,7 +79,17 @@ class EventTriggers(SimpleNamespace):
80
79
  ON_MOUSE_OUT = "on_mouse_out"
81
80
  ON_MOUSE_OVER = "on_mouse_over"
82
81
  ON_MOUSE_UP = "on_mouse_up"
82
+ ON_OPEN_CHANGE = "on_open_change"
83
+ ON_OPEN_AUTO_FOCUS = "on_open_auto_focus"
84
+ ON_CLOSE_AUTO_FOCUS = "on_close_auto_focus"
85
+ ON_FOCUS_OUTSIDE = "on_focus_outside"
86
+ ON_ESCAPE_KEY_DOWN = "on_escape_key_down"
87
+ ON_POINTER_DOWN_OUTSIDE = "on_pointer_down_outside"
88
+ ON_INTERACT_OUTSIDE = "on_interact_outside"
83
89
  ON_SCROLL = "on_scroll"
84
90
  ON_SUBMIT = "on_submit"
85
91
  ON_MOUNT = "on_mount"
86
92
  ON_UNMOUNT = "on_unmount"
93
+ ON_CLEAR_SERVER_ERRORS = "on_clear_server_errors"
94
+ ON_VALUE_COMMIT = "on_value_commit"
95
+ ON_SELECT = "on_select"
reflex/page.py CHANGED
@@ -12,7 +12,7 @@ def page(
12
12
  title: str | None = None,
13
13
  image: str | None = None,
14
14
  description: str | None = None,
15
- meta: str | None = None,
15
+ meta: list[Any] | None = None,
16
16
  script_tags: list[Any] | None = None,
17
17
  on_load: Any | list[Any] | None = None,
18
18
  ):
reflex/reflex.py CHANGED
@@ -80,6 +80,10 @@ def _init(
80
80
 
81
81
  prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
82
82
 
83
+ prerequisites.initialize_reflex_user_directory()
84
+
85
+ prerequisites.ensure_reflex_installation_id()
86
+
83
87
  # Set up the app directory, only if the config doesn't exist.
84
88
  if not os.path.exists(constants.Config.FILE):
85
89
  if template is None:
reflex/style.py CHANGED
@@ -188,16 +188,19 @@ def _format_emotion_style_pseudo_selector(key: str) -> str:
188
188
  return key
189
189
 
190
190
 
191
- def format_as_emotion(style_dict: dict[str, Any]) -> dict[str, Any] | None:
191
+ def format_as_emotion(style_dict: dict[str, Any]) -> Style | None:
192
192
  """Convert the style to an emotion-compatible CSS-in-JS dict.
193
193
 
194
194
  Args:
195
195
  style_dict: The style dict to convert.
196
196
 
197
197
  Returns:
198
- The emotion dict.
198
+ The emotion style dict.
199
199
  """
200
- emotion_style = {}
200
+ _var_data = style_dict._var_data if isinstance(style_dict, Style) else None
201
+
202
+ emotion_style = Style()
203
+
201
204
  for orig_key, value in style_dict.items():
202
205
  key = _format_emotion_style_pseudo_selector(orig_key)
203
206
  if isinstance(value, list):
@@ -219,6 +222,8 @@ def format_as_emotion(style_dict: dict[str, Any]) -> dict[str, Any] | None:
219
222
  else:
220
223
  emotion_style[key] = value
221
224
  if emotion_style:
225
+ if _var_data is not None:
226
+ emotion_style._var_data = VarData.merge(emotion_style._var_data, _var_data)
222
227
  return emotion_style
223
228
 
224
229
 
reflex/testing.py CHANGED
@@ -102,7 +102,7 @@ class AppHarness:
102
102
  """AppHarness executes a reflex app in-process for testing."""
103
103
 
104
104
  app_name: str
105
- app_source: Optional[types.FunctionType | types.ModuleType]
105
+ app_source: Optional[types.FunctionType | types.ModuleType] | str
106
106
  app_path: pathlib.Path
107
107
  app_module_path: pathlib.Path
108
108
  app_module: Optional[types.ModuleType] = None
@@ -119,7 +119,7 @@ class AppHarness:
119
119
  def create(
120
120
  cls,
121
121
  root: pathlib.Path,
122
- app_source: Optional[types.FunctionType | types.ModuleType] = None,
122
+ app_source: Optional[types.FunctionType | types.ModuleType | str] = None,
123
123
  app_name: Optional[str] = None,
124
124
  ) -> "AppHarness":
125
125
  """Create an AppHarness instance at root.
@@ -127,10 +127,13 @@ class AppHarness:
127
127
  Args:
128
128
  root: the directory that will contain the app under test.
129
129
  app_source: if specified, the source code from this function or module is used
130
- as the main module for the app. If unspecified, then root must already
131
- contain a working reflex app and will be used directly.
130
+ as the main module for the app. It may also be the raw source code text, as a str.
131
+ If unspecified, then root must already contain a working reflex app and will be used directly.
132
132
  app_name: provide the name of the app, otherwise will be derived from app_source or root.
133
133
 
134
+ Raises:
135
+ ValueError: when app_source is a string and app_name is not provided.
136
+
134
137
  Returns:
135
138
  AppHarness instance
136
139
  """
@@ -139,6 +142,10 @@ class AppHarness:
139
142
  app_name = root.name.lower()
140
143
  elif isinstance(app_source, functools.partial):
141
144
  app_name = app_source.func.__name__.lower()
145
+ elif isinstance(app_source, str):
146
+ raise ValueError(
147
+ "app_name must be provided when app_source is a string."
148
+ )
142
149
  else:
143
150
  app_name = app_source.__name__.lower()
144
151
  return cls(
@@ -170,17 +177,21 @@ class AppHarness:
170
177
  glbs.update(overrides)
171
178
  return glbs
172
179
 
173
- def _get_source_from_func(self, func: Any) -> str:
174
- """Get the source from a function or module object.
180
+ def _get_source_from_app_source(self, app_source: Any) -> str:
181
+ """Get the source from app_source.
175
182
 
176
183
  Args:
177
- func: function or module object
184
+ app_source: function or module or str
178
185
 
179
186
  Returns:
180
187
  source code
181
188
  """
182
- source = inspect.getsource(func)
183
- source = re.sub(r"^\s*def\s+\w+\s*\(.*?\):", "", source, flags=re.DOTALL)
189
+ if isinstance(app_source, str):
190
+ return app_source
191
+ source = inspect.getsource(app_source)
192
+ source = re.sub(
193
+ r"^\s*def\s+\w+\s*\(.*?\)(\s+->\s+\w+)?:", "", source, flags=re.DOTALL
194
+ )
184
195
  return textwrap.dedent(source)
185
196
 
186
197
  def _initialize_app(self):
@@ -194,7 +205,7 @@ class AppHarness:
194
205
  source_code = "\n".join(
195
206
  [
196
207
  "\n".join(f"{k} = {v!r}" for k, v in app_globals.items()),
197
- self._get_source_from_func(self.app_source),
208
+ self._get_source_from_app_source(self.app_source),
198
209
  ]
199
210
  )
200
211
  with chdir(self.app_path):
reflex/utils/format.py CHANGED
@@ -255,16 +255,20 @@ def format_cond(
255
255
 
256
256
  # Format prop conds.
257
257
  if is_prop:
258
- prop1 = Var.create_safe(
259
- true_value,
260
- _var_is_string=type(true_value) is str,
261
- )
262
- prop1._var_is_local = True
263
- prop2 = Var.create_safe(
264
- false_value,
265
- _var_is_string=type(false_value) is str,
258
+ if not isinstance(true_value, Var):
259
+ true_value = Var.create_safe(
260
+ true_value,
261
+ _var_is_string=type(true_value) is str,
262
+ )
263
+ prop1 = true_value._replace(
264
+ _var_is_local=True,
266
265
  )
267
- prop2._var_is_local = True
266
+ if not isinstance(false_value, Var):
267
+ false_value = Var.create_safe(
268
+ false_value,
269
+ _var_is_string=type(false_value) is str,
270
+ )
271
+ prop2 = false_value._replace(_var_is_local=True)
268
272
  prop1, prop2 = str(prop1), str(prop2) # avoid f-string semantics for Var
269
273
  return f"{cond} ? {prop1} : {prop2}".replace("{", "").replace("}", "")
270
274
 
@@ -16,7 +16,7 @@ import zipfile
16
16
  from fileinput import FileInput
17
17
  from pathlib import Path
18
18
  from types import ModuleType
19
- from typing import Callable
19
+ from typing import Callable, Optional
20
20
 
21
21
  import httpx
22
22
  import pkg_resources
@@ -824,10 +824,48 @@ def validate_frontend_dependencies(init=True):
824
824
  validate_bun()
825
825
 
826
826
 
827
- def initialize_frontend_dependencies():
828
- """Initialize all the frontend dependencies."""
827
+ def ensure_reflex_installation_id() -> Optional[int]:
828
+ """Ensures that a reflex distinct id has been generated and stored in the reflex directory.
829
+
830
+ Returns:
831
+ Distinct id.
832
+ """
833
+ try:
834
+ initialize_reflex_user_directory()
835
+ installation_id_file = os.path.join(constants.Reflex.DIR, "installation_id")
836
+
837
+ installation_id = None
838
+ if os.path.exists(installation_id_file):
839
+ try:
840
+ with open(installation_id_file, "r") as f:
841
+ installation_id = int(f.read())
842
+ except Exception:
843
+ # If anything goes wrong at all... just regenerate.
844
+ # Like what? Examples:
845
+ # - file not exists
846
+ # - file not readable
847
+ # - content not parseable as an int
848
+ pass
849
+
850
+ if installation_id is None:
851
+ installation_id = random.getrandbits(128)
852
+ with open(installation_id_file, "w") as f:
853
+ f.write(str(installation_id))
854
+ # If we get here, installation_id is definitely set
855
+ return installation_id
856
+ except Exception as e:
857
+ console.debug(f"Failed to ensure reflex installation id: {e}")
858
+ return None
859
+
860
+
861
+ def initialize_reflex_user_directory():
862
+ """Initialize the reflex user directory."""
829
863
  # Create the reflex directory.
830
864
  path_ops.mkdir(constants.Reflex.DIR)
865
+
866
+
867
+ def initialize_frontend_dependencies():
868
+ """Initialize all the frontend dependencies."""
831
869
  # validate dependencies before install
832
870
  validate_frontend_dependencies()
833
871
  # Install the frontend dependencies.
@@ -8,6 +8,7 @@ from datetime import date, datetime, time, timedelta
8
8
  from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union, get_type_hints
9
9
 
10
10
  from reflex.base import Base
11
+ from reflex.constants.colors import Color, format_color
11
12
  from reflex.utils import exceptions, format, types
12
13
 
13
14
  # Mapping from type to a serializer.
@@ -230,6 +231,19 @@ def serialize_datetime(dt: Union[date, datetime, time, timedelta]) -> str:
230
231
  return str(dt)
231
232
 
232
233
 
234
+ @serializer
235
+ def serialize_color(color: Color) -> str:
236
+ """Serialize a color.
237
+
238
+ Args:
239
+ color: The color to serialize.
240
+
241
+ Returns:
242
+ The serialized color.
243
+ """
244
+ return format_color(color.color, color.shade, color.alpha)
245
+
246
+
233
247
  try:
234
248
  from pandas import DataFrame
235
249
 
reflex/utils/telemetry.py CHANGED
@@ -10,6 +10,7 @@ from datetime import datetime
10
10
  import psutil
11
11
 
12
12
  from reflex import constants
13
+ from reflex.utils.prerequisites import ensure_reflex_installation_id
13
14
 
14
15
 
15
16
  def get_os() -> str:
@@ -79,15 +80,20 @@ def send(event: str, telemetry_enabled: bool | None = None) -> bool:
79
80
  if not telemetry_enabled:
80
81
  return False
81
82
 
83
+ installation_id = ensure_reflex_installation_id()
84
+ if installation_id is None:
85
+ return False
86
+
82
87
  try:
83
88
  with open(constants.Dirs.REFLEX_JSON) as f:
84
89
  reflex_json = json.load(f)
85
- distinct_id = reflex_json["project_hash"]
90
+ project_hash = reflex_json["project_hash"]
86
91
  post_hog = {
87
92
  "api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
88
93
  "event": event,
89
94
  "properties": {
90
- "distinct_id": distinct_id,
95
+ "distinct_id": installation_id,
96
+ "distinct_app_id": project_hash,
91
97
  "user_os": get_os(),
92
98
  "reflex_version": get_reflex_version(),
93
99
  "python_version": get_python_version(),
reflex/utils/types.py CHANGED
@@ -18,8 +18,10 @@ from typing import (
18
18
  get_type_hints,
19
19
  )
20
20
 
21
+ import sqlalchemy
21
22
  from pydantic.fields import ModelField
22
- from sqlalchemy.orm import DeclarativeBase, Mapped
23
+ from sqlalchemy.ext.hybrid import hybrid_property
24
+ from sqlalchemy.orm import DeclarativeBase, Mapped, QueryableAttribute, Relationship
23
25
 
24
26
  from reflex.base import Base
25
27
  from reflex.utils import serializers
@@ -104,6 +106,21 @@ def is_optional(cls: GenericType) -> bool:
104
106
  return is_union(cls) and type(None) in get_args(cls)
105
107
 
106
108
 
109
+ def get_property_hint(attr: Any | None) -> GenericType | None:
110
+ """Check if an attribute is a property and return its type hint.
111
+
112
+ Args:
113
+ attr: The descriptor to check.
114
+
115
+ Returns:
116
+ The type hint of the property, if it is a property, else None.
117
+ """
118
+ if not isinstance(attr, (property, hybrid_property)):
119
+ return None
120
+ hints = get_type_hints(attr.fget)
121
+ return hints.get("return", None)
122
+
123
+
107
124
  def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None:
108
125
  """Check if an attribute can be accessed on the cls and return its type.
109
126
 
@@ -118,6 +135,9 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
118
135
  """
119
136
  from reflex.model import Model
120
137
 
138
+ attr = getattr(cls, name, None)
139
+ if hint := get_property_hint(attr):
140
+ return hint
121
141
  if hasattr(cls, "__fields__") and name in cls.__fields__:
122
142
  # pydantic models
123
143
  field = cls.__fields__[name]
@@ -128,7 +148,21 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
128
148
  # Ensure frontend uses null coalescing when accessing.
129
149
  type_ = Optional[type_]
130
150
  return type_
131
- elif isinstance(cls, type) and issubclass(cls, (Model, DeclarativeBase)):
151
+ elif isinstance(cls, type) and issubclass(cls, DeclarativeBase):
152
+ insp = sqlalchemy.inspect(cls)
153
+ if name in insp.columns:
154
+ return insp.columns[name].type.python_type
155
+ if name not in insp.all_orm_descriptors.keys():
156
+ return None
157
+ descriptor = insp.all_orm_descriptors[name]
158
+ if hint := get_property_hint(descriptor):
159
+ return hint
160
+ if isinstance(descriptor, QueryableAttribute):
161
+ prop = descriptor.property
162
+ if not isinstance(prop, Relationship):
163
+ return None
164
+ return prop.mapper.class_
165
+ elif isinstance(cls, type) and issubclass(cls, Model):
132
166
  # Check in the annotations directly (for sqlmodel.Relationship)
133
167
  hints = get_type_hints(cls)
134
168
  if name in hints:
reflex/vars.py CHANGED
@@ -87,6 +87,15 @@ REPLACED_NAMES = {
87
87
  "deps": "_deps",
88
88
  }
89
89
 
90
+ PYTHON_JS_TYPE_MAP = {
91
+ (int, float): "number",
92
+ (str,): "string",
93
+ (bool,): "boolean",
94
+ (list, tuple): "Array",
95
+ (dict,): "Object",
96
+ (None,): "null",
97
+ }
98
+
90
99
 
91
100
  def get_unique_variable_name() -> str:
92
101
  """Get a unique variable name.
@@ -234,6 +243,8 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
234
243
  Returns:
235
244
  The extracted VarDatas.
236
245
  """
246
+ from reflex.style import Style
247
+
237
248
  var_datas = []
238
249
  with contextlib.suppress(TypeError):
239
250
  for sub in value:
@@ -245,10 +256,15 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]:
245
256
  var_datas.extend(_extract_var_data(sub.values()))
246
257
  # Recurse into iterable values (or dict keys).
247
258
  var_datas.extend(_extract_var_data(sub))
248
- # Recurse when value is a dict itself.
249
- values = getattr(value, "values", None)
250
- if callable(values):
251
- var_datas.extend(_extract_var_data(values()))
259
+
260
+ # Style objects should already have _var_data.
261
+ if isinstance(value, Style):
262
+ var_datas.append(value._var_data)
263
+ else:
264
+ # Recurse when value is a dict itself.
265
+ values = getattr(value, "values", None)
266
+ if callable(values):
267
+ var_datas.extend(_extract_var_data(values()))
252
268
  return var_datas
253
269
 
254
270
 
@@ -429,12 +445,9 @@ class Var:
429
445
 
430
446
  Returns:
431
447
  The merged var.
432
-
433
- Raises:
434
- ValueError: If the other value to be merged is None.
435
448
  """
436
449
  if other is None:
437
- raise ValueError("The value to be merged cannot be None.")
450
+ return self._replace()
438
451
  if not isinstance(other, Var):
439
452
  other = Var.create(other)
440
453
  return self._replace(
@@ -568,11 +581,10 @@ class Var:
568
581
  )
569
582
 
570
583
  # Get the type of the indexed var.
571
- type_ = (
572
- types.get_args(self._var_type)[0]
573
- if types.is_generic_alias(self._var_type)
574
- else Any
575
- )
584
+ if types.is_generic_alias(self._var_type):
585
+ type_ = types.get_args(self._var_type)[0]
586
+ elif types._issubclass(self._var_type, str):
587
+ type_ = str
576
588
 
577
589
  # Use `at` to support negative indices.
578
590
  return self._replace(
@@ -736,13 +748,13 @@ class Var:
736
748
  operation_name = format.wrap(operation_name, "(")
737
749
  else:
738
750
  # apply operator to left operand (<operator> left_operand)
739
- operation_name = f"{op}{self._var_full_name}"
751
+ operation_name = f"{op}{get_operand_full_name(self)}"
740
752
  # apply function to operands
741
753
  if fn is not None:
742
754
  operation_name = (
743
755
  f"{fn}({operation_name})"
744
756
  if not invoke_fn
745
- else f"{self._var_full_name}.{fn}()"
757
+ else f"{get_operand_full_name(self)}.{fn}()"
746
758
  )
747
759
 
748
760
  return self._replace(
@@ -836,7 +848,20 @@ class Var:
836
848
  _var_is_string=False,
837
849
  )
838
850
 
839
- def __eq__(self, other: Var) -> Var:
851
+ def _type(self) -> Var:
852
+ """Get the type of the Var in Javascript.
853
+
854
+ Returns:
855
+ A var representing the type check.
856
+ """
857
+ return self._replace(
858
+ _var_name=f"typeof {self._var_full_name}",
859
+ _var_type=str,
860
+ _var_is_string=False,
861
+ _var_full_name_needs_state_prefix=False,
862
+ )
863
+
864
+ def __eq__(self, other: Union[Var, Type]) -> Var:
840
865
  """Perform an equality comparison.
841
866
 
842
867
  Args:
@@ -845,9 +870,12 @@ class Var:
845
870
  Returns:
846
871
  A var representing the equality comparison.
847
872
  """
873
+ for python_types, js_type in PYTHON_JS_TYPE_MAP.items():
874
+ if not isinstance(other, Var) and other in python_types:
875
+ return self.compare("===", Var.create(js_type, _var_is_string=True)) # type: ignore
848
876
  return self.compare("===", other)
849
877
 
850
- def __ne__(self, other: Var) -> Var:
878
+ def __ne__(self, other: Union[Var, Type]) -> Var:
851
879
  """Perform an inequality comparison.
852
880
 
853
881
  Args:
@@ -856,6 +884,9 @@ class Var:
856
884
  Returns:
857
885
  A var representing the inequality comparison.
858
886
  """
887
+ for python_types, js_type in PYTHON_JS_TYPE_MAP.items():
888
+ if not isinstance(other, Var) and other in python_types:
889
+ return self.compare("!==", Var.create(js_type, _var_is_string=True)) # type: ignore
859
890
  return self.compare("!==", other)
860
891
 
861
892
  def __gt__(self, other: Var) -> Var:
@@ -1574,6 +1605,8 @@ class Var:
1574
1605
  Returns:
1575
1606
  The str var without the wrapped curly braces
1576
1607
  """
1608
+ from reflex.style import Style
1609
+
1577
1610
  type_ = (
1578
1611
  get_origin(self._var_type)
1579
1612
  if types.is_generic_alias(self._var_type)
@@ -1583,7 +1616,9 @@ class Var:
1583
1616
  wrapped_var = str(self)
1584
1617
  return (
1585
1618
  wrapped_var
1586
- if not self._var_state and issubclass(type_, dict)
1619
+ if not self._var_state
1620
+ and issubclass(type_, dict)
1621
+ or issubclass(type_, Style)
1587
1622
  else wrapped_var.strip("{}")
1588
1623
  )
1589
1624
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: reflex
3
- Version: 0.3.9a3
3
+ Version: 0.3.10
4
4
  Summary: Web apps in pure Python.
5
5
  Home-page: https://reflex.dev
6
6
  License: Apache-2.0
@@ -15,7 +15,6 @@ 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
19
18
  Requires-Dist: alembic (>=1.11.1,<2.0.0)
20
19
  Requires-Dist: charset-normalizer (>=3.3.2,<4.0.0)
21
20
  Requires-Dist: cloudpickle (>=2.2.1,<3.0.0)