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.
- reflex/__init__.py +3 -1
- reflex/__init__.pyi +3 -1
- reflex/app.py +116 -86
- reflex/compiler/compiler.py +110 -0
- reflex/compiler/utils.py +13 -12
- reflex/components/base/app_wrap.pyi +1 -0
- reflex/components/base/body.pyi +2 -0
- reflex/components/base/document.pyi +10 -0
- reflex/components/base/fragment.pyi +2 -0
- reflex/components/base/head.pyi +4 -0
- reflex/components/base/link.pyi +4 -0
- reflex/components/base/meta.pyi +8 -0
- reflex/components/base/script.pyi +2 -0
- reflex/components/chakra/base.pyi +7 -0
- reflex/components/chakra/datadisplay/badge.pyi +2 -0
- reflex/components/chakra/datadisplay/code.pyi +4 -0
- reflex/components/chakra/datadisplay/divider.pyi +2 -0
- reflex/components/chakra/datadisplay/keyboard_key.pyi +2 -0
- reflex/components/chakra/datadisplay/list.pyi +8 -0
- reflex/components/chakra/datadisplay/stat.pyi +12 -0
- reflex/components/chakra/datadisplay/table.pyi +18 -0
- reflex/components/chakra/datadisplay/tag.pyi +9 -0
- reflex/components/chakra/disclosure/accordion.pyi +10 -0
- reflex/components/chakra/disclosure/tabs.py +8 -0
- reflex/components/chakra/disclosure/tabs.pyi +10 -0
- reflex/components/chakra/disclosure/transition.pyi +12 -0
- reflex/components/chakra/disclosure/visuallyhidden.pyi +2 -0
- reflex/components/chakra/feedback/alert.pyi +8 -0
- reflex/components/chakra/feedback/circularprogress.pyi +4 -0
- reflex/components/chakra/feedback/progress.pyi +2 -0
- reflex/components/chakra/feedback/skeleton.pyi +6 -0
- reflex/components/chakra/feedback/spinner.pyi +2 -0
- reflex/components/chakra/forms/button.pyi +4 -0
- reflex/components/chakra/forms/checkbox.pyi +4 -0
- reflex/components/chakra/forms/colormodeswitch.pyi +7 -0
- reflex/components/chakra/forms/date_picker.pyi +2 -0
- reflex/components/chakra/forms/date_time_picker.pyi +2 -0
- reflex/components/chakra/forms/editable.pyi +8 -0
- reflex/components/chakra/forms/email.pyi +2 -0
- reflex/components/chakra/forms/form.pyi +10 -0
- reflex/components/chakra/forms/iconbutton.py +1 -0
- reflex/components/chakra/forms/iconbutton.pyi +2 -0
- reflex/components/chakra/forms/input.pyi +12 -0
- reflex/components/chakra/forms/numberinput.pyi +10 -0
- reflex/components/chakra/forms/password.pyi +2 -0
- reflex/components/chakra/forms/pininput.pyi +4 -0
- reflex/components/chakra/forms/radio.pyi +4 -0
- reflex/components/chakra/forms/rangeslider.pyi +8 -0
- reflex/components/chakra/forms/select.pyi +4 -0
- reflex/components/chakra/forms/slider.pyi +10 -0
- reflex/components/chakra/forms/switch.pyi +2 -0
- reflex/components/chakra/forms/textarea.pyi +2 -0
- reflex/components/chakra/forms/time_picker.pyi +2 -0
- reflex/components/chakra/layout/aspect_ratio.pyi +2 -0
- reflex/components/chakra/layout/box.pyi +2 -0
- reflex/components/chakra/layout/card.pyi +7 -0
- reflex/components/chakra/layout/center.pyi +6 -0
- reflex/components/chakra/layout/container.pyi +2 -0
- reflex/components/chakra/layout/flex.pyi +2 -0
- reflex/components/chakra/layout/grid.pyi +6 -0
- reflex/components/chakra/layout/html.pyi +2 -0
- reflex/components/chakra/layout/spacer.pyi +2 -0
- reflex/components/chakra/layout/stack.pyi +6 -0
- reflex/components/chakra/layout/wrap.pyi +4 -0
- reflex/components/chakra/media/avatar.pyi +6 -0
- reflex/components/chakra/media/icon.pyi +4 -0
- reflex/components/chakra/media/image.pyi +2 -0
- reflex/components/chakra/navigation/breadcrumb.pyi +8 -0
- reflex/components/chakra/navigation/link.pyi +2 -0
- reflex/components/chakra/navigation/linkoverlay.pyi +4 -0
- reflex/components/chakra/navigation/stepper.pyi +18 -0
- reflex/components/chakra/overlay/alertdialog.pyi +14 -0
- reflex/components/chakra/overlay/drawer.pyi +14 -0
- reflex/components/chakra/overlay/menu.pyi +16 -0
- reflex/components/chakra/overlay/modal.pyi +14 -0
- reflex/components/chakra/overlay/popover.pyi +18 -0
- reflex/components/chakra/overlay/tooltip.pyi +2 -0
- reflex/components/chakra/typography/heading.pyi +2 -0
- reflex/components/chakra/typography/highlight.pyi +2 -0
- reflex/components/chakra/typography/span.pyi +2 -0
- reflex/components/chakra/typography/text.pyi +2 -0
- reflex/components/component.py +41 -3
- reflex/components/core/__init__.py +2 -0
- reflex/components/core/banner.pyi +3 -0
- reflex/components/core/client_side_routing.pyi +4 -0
- reflex/components/core/colors.py +21 -0
- reflex/components/core/cond.py +11 -2
- reflex/components/core/debounce.pyi +1 -0
- reflex/components/core/layout/__init__.py +1 -0
- reflex/components/core/match.py +44 -18
- reflex/components/core/upload.py +8 -2
- reflex/components/core/upload.pyi +7 -1
- reflex/components/datadisplay/dataeditor.pyi +2 -0
- reflex/components/el/element.pyi +2 -0
- reflex/components/el/elements/base.pyi +2 -0
- reflex/components/el/elements/forms.py +3 -0
- reflex/components/el/elements/forms.pyi +32 -0
- reflex/components/el/elements/inline.pyi +56 -0
- reflex/components/el/elements/media.pyi +28 -0
- reflex/components/el/elements/metadata.pyi +10 -0
- reflex/components/el/elements/other.pyi +14 -0
- reflex/components/el/elements/scripts.pyi +6 -0
- reflex/components/el/elements/sectioning.pyi +30 -0
- reflex/components/el/elements/tables.pyi +20 -0
- reflex/components/el/elements/typography.pyi +30 -0
- reflex/components/gridjs/datatable.pyi +4 -0
- reflex/components/lucide/__init__.py +5 -0
- reflex/components/lucide/icon.py +1484 -0
- reflex/components/lucide/icon.pyi +1594 -0
- reflex/components/markdown/markdown.pyi +2 -0
- reflex/components/moment/moment.pyi +2 -0
- reflex/components/next/base.pyi +2 -0
- reflex/components/next/image.pyi +2 -0
- reflex/components/next/link.pyi +2 -0
- reflex/components/next/video.pyi +2 -0
- reflex/components/plotly/plotly.pyi +4 -0
- reflex/components/radix/primitives/__init__.py +10 -0
- reflex/components/radix/primitives/accordion.py +51 -32
- reflex/components/radix/primitives/accordion.pyi +16 -3
- reflex/components/radix/primitives/base.pyi +4 -0
- reflex/components/radix/primitives/drawer.py +240 -0
- reflex/components/radix/primitives/drawer.pyi +814 -0
- reflex/components/radix/primitives/form.py +40 -7
- reflex/components/radix/primitives/form.pyi +32 -10
- reflex/components/radix/primitives/progress.py +2 -2
- reflex/components/radix/primitives/progress.pyi +6 -0
- reflex/components/radix/primitives/slider.pyi +10 -0
- reflex/components/radix/themes/base.py +46 -12
- reflex/components/radix/themes/base.pyi +23 -9
- reflex/components/radix/themes/components/__init__.py +4 -2
- reflex/components/radix/themes/components/alertdialog.py +13 -12
- reflex/components/radix/themes/components/alertdialog.pyi +23 -351
- reflex/components/radix/themes/components/aspectratio.py +2 -5
- reflex/components/radix/themes/components/aspectratio.pyi +4 -51
- reflex/components/radix/themes/components/avatar.py +3 -4
- reflex/components/radix/themes/components/avatar.pyi +4 -57
- reflex/components/radix/themes/components/badge.py +3 -4
- reflex/components/radix/themes/components/badge.pyi +4 -56
- reflex/components/radix/themes/components/button.py +2 -3
- reflex/components/radix/themes/components/button.pyi +3 -51
- reflex/components/radix/themes/components/callout.py +9 -12
- reflex/components/radix/themes/components/callout.pyi +24 -217
- reflex/components/radix/themes/components/card.py +1 -2
- reflex/components/radix/themes/components/card.pyi +4 -51
- reflex/components/radix/themes/components/checkbox.py +11 -5
- reflex/components/radix/themes/components/checkbox.pyi +13 -112
- reflex/components/radix/themes/components/contextmenu.py +21 -21
- reflex/components/radix/themes/components/contextmenu.pyi +26 -401
- reflex/components/radix/themes/components/dialog.py +13 -16
- reflex/components/radix/themes/components/dialog.pyi +20 -303
- reflex/components/radix/themes/components/dropdownmenu.py +196 -32
- reflex/components/radix/themes/components/dropdownmenu.pyi +147 -389
- reflex/components/radix/themes/components/hovercard.py +5 -5
- reflex/components/radix/themes/components/hovercard.pyi +11 -151
- reflex/components/radix/themes/components/iconbutton.py +56 -4
- reflex/components/radix/themes/components/iconbutton.pyi +25 -71
- reflex/components/radix/themes/components/icons.pyi +4 -0
- reflex/components/radix/themes/components/inset.py +1 -2
- reflex/components/radix/themes/components/inset.pyi +4 -51
- reflex/components/radix/themes/components/popover.py +12 -12
- reflex/components/radix/themes/components/popover.pyi +14 -201
- reflex/components/radix/themes/components/radiogroup.py +47 -20
- reflex/components/radix/themes/components/radiogroup.pyi +26 -171
- reflex/components/radix/themes/components/scrollarea.py +2 -3
- reflex/components/radix/themes/components/scrollarea.pyi +4 -51
- reflex/components/radix/themes/components/select.py +28 -25
- reflex/components/radix/themes/components/select.pyi +43 -419
- reflex/components/radix/themes/components/separator.py +4 -5
- reflex/components/radix/themes/components/separator.pyi +5 -52
- reflex/components/radix/themes/components/slider.py +8 -5
- reflex/components/radix/themes/components/slider.pyi +9 -60
- reflex/components/radix/themes/components/switch.py +6 -4
- reflex/components/radix/themes/components/switch.pyi +5 -53
- reflex/components/radix/themes/components/table.py +14 -15
- reflex/components/radix/themes/components/table.pyi +22 -351
- reflex/components/radix/themes/components/tabs.py +9 -6
- reflex/components/radix/themes/components/tabs.pyi +18 -205
- reflex/components/radix/themes/components/textarea.py +2 -3
- reflex/components/radix/themes/components/textarea.pyi +10 -53
- reflex/components/radix/themes/components/textfield.py +105 -4
- reflex/components/radix/themes/components/textfield.pyi +200 -108
- reflex/components/radix/themes/components/tooltip.py +102 -2
- reflex/components/radix/themes/components/tooltip.pyi +66 -110
- reflex/components/radix/themes/layout/__init__.py +7 -0
- reflex/components/radix/themes/layout/base.pyi +2 -0
- reflex/components/radix/themes/layout/box.py +2 -2
- reflex/components/radix/themes/layout/box.pyi +4 -104
- reflex/components/radix/themes/layout/center.py +19 -0
- reflex/components/radix/themes/layout/center.pyi +261 -0
- reflex/components/radix/themes/layout/container.py +2 -2
- reflex/components/radix/themes/layout/container.pyi +4 -104
- reflex/components/radix/themes/layout/flex.py +2 -2
- reflex/components/radix/themes/layout/flex.pyi +4 -105
- reflex/components/radix/themes/layout/grid.pyi +2 -0
- reflex/components/radix/themes/layout/section.py +2 -2
- reflex/components/radix/themes/layout/section.pyi +4 -104
- reflex/components/radix/themes/layout/spacer.py +19 -0
- reflex/components/radix/themes/layout/spacer.pyi +261 -0
- reflex/components/radix/themes/layout/stack.py +60 -0
- reflex/components/radix/themes/layout/stack.pyi +537 -0
- reflex/components/radix/themes/typography/blockquote.py +2 -3
- reflex/components/radix/themes/typography/blockquote.pyi +4 -51
- reflex/components/radix/themes/typography/code.py +2 -3
- reflex/components/radix/themes/typography/code.pyi +4 -56
- reflex/components/radix/themes/typography/em.py +1 -2
- reflex/components/radix/themes/typography/em.pyi +4 -51
- reflex/components/radix/themes/typography/heading.py +2 -3
- reflex/components/radix/themes/typography/heading.pyi +4 -51
- reflex/components/radix/themes/typography/kbd.py +1 -2
- reflex/components/radix/themes/typography/kbd.pyi +4 -51
- reflex/components/radix/themes/typography/link.py +34 -3
- reflex/components/radix/themes/typography/link.pyi +41 -86
- reflex/components/radix/themes/typography/quote.py +1 -2
- reflex/components/radix/themes/typography/quote.pyi +4 -51
- reflex/components/radix/themes/typography/strong.py +1 -2
- reflex/components/radix/themes/typography/strong.pyi +4 -51
- reflex/components/radix/themes/typography/text.py +2 -3
- reflex/components/radix/themes/typography/text.pyi +4 -51
- reflex/components/radix/themes/typography.py +10 -11
- reflex/components/react_player/audio.pyi +2 -0
- reflex/components/react_player/react_player.pyi +2 -0
- reflex/components/react_player/video.pyi +2 -0
- reflex/components/recharts/cartesian.pyi +38 -0
- reflex/components/recharts/charts.pyi +22 -0
- reflex/components/recharts/general.pyi +10 -0
- reflex/components/recharts/polar.pyi +12 -0
- reflex/components/recharts/recharts.pyi +4 -0
- reflex/components/suneditor/editor.pyi +2 -0
- reflex/components/tags/tag.py +1 -1
- reflex/constants/base.py +5 -1
- reflex/constants/colors.py +80 -0
- reflex/constants/event.py +10 -1
- reflex/page.py +1 -1
- reflex/reflex.py +4 -0
- reflex/style.py +8 -3
- reflex/testing.py +21 -10
- reflex/utils/format.py +13 -9
- reflex/utils/prerequisites.py +41 -3
- reflex/utils/serializers.py +14 -0
- reflex/utils/telemetry.py +8 -2
- reflex/utils/types.py +36 -2
- reflex/vars.py +53 -18
- {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/METADATA +1 -2
- {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/RECORD +247 -233
- {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/WHEEL +1 -1
- {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/LICENSE +0 -0
- {reflex-0.3.9a3.dist-info → reflex-0.3.10.dist-info}/entry_points.txt +0 -0
reflex/components/tags/tag.py
CHANGED
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
|
-
|
|
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
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]) ->
|
|
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
|
-
|
|
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.
|
|
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
|
|
174
|
-
"""Get the source from
|
|
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
|
-
|
|
184
|
+
app_source: function or module or str
|
|
178
185
|
|
|
179
186
|
Returns:
|
|
180
187
|
source code
|
|
181
188
|
"""
|
|
182
|
-
|
|
183
|
-
|
|
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.
|
|
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
|
-
|
|
259
|
-
true_value
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -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
|
|
828
|
-
"""
|
|
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.
|
reflex/utils/serializers.py
CHANGED
|
@@ -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
|
-
|
|
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":
|
|
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.
|
|
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,
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
if
|
|
251
|
-
var_datas.
|
|
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
|
-
|
|
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
|
-
|
|
572
|
-
types.get_args(self._var_type)[0]
|
|
573
|
-
|
|
574
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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)
|