reflex 0.4.4a2__py3-none-any.whl → 0.4.5__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/.templates/jinja/web/pages/stateful_component.js.jinja2 +5 -1
- reflex/.templates/web/utils/state.js +2 -1
- reflex/app.py +163 -102
- reflex/app_module_for_backend.py +4 -2
- reflex/compiler/compiler.py +103 -11
- reflex/compiler/utils.py +16 -6
- reflex/components/component.py +72 -14
- reflex/components/core/upload.py +2 -2
- reflex/components/core/upload.pyi +1 -1
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/markdown/markdown.py +4 -2
- reflex/components/radix/themes/components/radio_group.py +2 -1
- reflex/components/radix/themes/components/select.py +7 -2
- reflex/components/radix/themes/components/select.pyi +14 -0
- reflex/components/radix/themes/layout/list.py +31 -4
- reflex/components/radix/themes/layout/list.pyi +53 -9
- reflex/constants/installer.py +1 -1
- reflex/constants/route.py +2 -2
- reflex/event.py +4 -0
- reflex/model.py +88 -4
- reflex/state.py +20 -5
- reflex/testing.py +21 -1
- reflex/utils/exec.py +9 -0
- reflex/utils/format.py +46 -19
- reflex/utils/prerequisites.py +31 -10
- reflex/utils/processes.py +9 -2
- reflex/utils/telemetry.py +69 -29
- reflex/vars.py +5 -3
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/METADATA +1 -1
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/RECORD +33 -33
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/LICENSE +0 -0
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/WHEEL +0 -0
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/entry_points.txt +0 -0
reflex/components/component.py
CHANGED
|
@@ -74,6 +74,14 @@ class BaseComponent(Base, ABC):
|
|
|
74
74
|
The dictionary for template of the component.
|
|
75
75
|
"""
|
|
76
76
|
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def get_hooks_internal(self) -> set[str]:
|
|
79
|
+
"""Get the reflex internal hooks for the component and its children.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The code that should appear just before user-defined hooks.
|
|
83
|
+
"""
|
|
84
|
+
|
|
77
85
|
@abstractmethod
|
|
78
86
|
def get_hooks(self) -> set[str]:
|
|
79
87
|
"""Get the React hooks for this component.
|
|
@@ -1141,14 +1149,28 @@ class Component(BaseComponent, ABC):
|
|
|
1141
1149
|
"""
|
|
1142
1150
|
return
|
|
1143
1151
|
|
|
1152
|
+
def get_hooks_internal(self) -> set[str]:
|
|
1153
|
+
"""Get the reflex internal hooks for the component and its children.
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
The code that should appear just before user-defined hooks.
|
|
1157
|
+
"""
|
|
1158
|
+
# Store the code in a set to avoid duplicates.
|
|
1159
|
+
code = self._get_hooks_internal()
|
|
1160
|
+
|
|
1161
|
+
# Add the hook code for the children.
|
|
1162
|
+
for child in self.children:
|
|
1163
|
+
code |= child.get_hooks_internal()
|
|
1164
|
+
|
|
1165
|
+
return code
|
|
1166
|
+
|
|
1144
1167
|
def get_hooks(self) -> Set[str]:
|
|
1145
1168
|
"""Get the React hooks for this component and its children.
|
|
1146
1169
|
|
|
1147
1170
|
Returns:
|
|
1148
1171
|
The code that should appear just before returning the rendered component.
|
|
1149
1172
|
"""
|
|
1150
|
-
|
|
1151
|
-
code = self._get_hooks_internal()
|
|
1173
|
+
code = set()
|
|
1152
1174
|
|
|
1153
1175
|
# Add the hook code for this component.
|
|
1154
1176
|
hooks = self._get_hooks()
|
|
@@ -1265,6 +1287,9 @@ class CustomComponent(Component):
|
|
|
1265
1287
|
# The props of the component.
|
|
1266
1288
|
props: Dict[str, Any] = {}
|
|
1267
1289
|
|
|
1290
|
+
# Props that reference other components.
|
|
1291
|
+
component_props: Dict[str, Component] = {}
|
|
1292
|
+
|
|
1268
1293
|
def __init__(self, *args, **kwargs):
|
|
1269
1294
|
"""Initialize the custom component.
|
|
1270
1295
|
|
|
@@ -1296,19 +1321,21 @@ class CustomComponent(Component):
|
|
|
1296
1321
|
self.props[format.to_camel_case(key)] = value
|
|
1297
1322
|
continue
|
|
1298
1323
|
|
|
1299
|
-
# Convert the type to a Var, then get the type of the var.
|
|
1300
|
-
if not types._issubclass(type_, Var):
|
|
1301
|
-
type_ = Var[type_]
|
|
1302
|
-
type_ = types.get_args(type_)[0]
|
|
1303
|
-
|
|
1304
1324
|
# Handle subclasses of Base.
|
|
1305
|
-
if
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1325
|
+
if isinstance(value, Base):
|
|
1326
|
+
base_value = Var.create(value)
|
|
1327
|
+
|
|
1328
|
+
# Track hooks and imports associated with Component instances.
|
|
1329
|
+
if base_value is not None and isinstance(value, Component):
|
|
1330
|
+
self.component_props[key] = value
|
|
1331
|
+
value = base_value._replace(
|
|
1332
|
+
merge_var_data=VarData( # type: ignore
|
|
1333
|
+
imports=value.get_imports(),
|
|
1334
|
+
hooks=value.get_hooks(),
|
|
1335
|
+
)
|
|
1309
1336
|
)
|
|
1310
|
-
|
|
1311
|
-
value =
|
|
1337
|
+
else:
|
|
1338
|
+
value = base_value
|
|
1312
1339
|
else:
|
|
1313
1340
|
value = Var.create(value, _var_is_string=type(value) is str)
|
|
1314
1341
|
|
|
@@ -1367,6 +1394,16 @@ class CustomComponent(Component):
|
|
|
1367
1394
|
custom_components |= self.get_component(self).get_custom_components(
|
|
1368
1395
|
seen=seen
|
|
1369
1396
|
)
|
|
1397
|
+
|
|
1398
|
+
# Fetch custom components from props as well.
|
|
1399
|
+
for child_component in self.component_props.values():
|
|
1400
|
+
if child_component.tag is None:
|
|
1401
|
+
continue
|
|
1402
|
+
if child_component.tag not in seen:
|
|
1403
|
+
seen.add(child_component.tag)
|
|
1404
|
+
if isinstance(child_component, CustomComponent):
|
|
1405
|
+
custom_components |= {child_component}
|
|
1406
|
+
custom_components |= child_component.get_custom_components(seen=seen)
|
|
1370
1407
|
return custom_components
|
|
1371
1408
|
|
|
1372
1409
|
def _render(self) -> Tag:
|
|
@@ -1393,6 +1430,19 @@ class CustomComponent(Component):
|
|
|
1393
1430
|
for name, prop in self.props.items()
|
|
1394
1431
|
]
|
|
1395
1432
|
|
|
1433
|
+
def _get_vars(self, include_children: bool = False) -> list[Var]:
|
|
1434
|
+
"""Walk all Vars used in this component.
|
|
1435
|
+
|
|
1436
|
+
Args:
|
|
1437
|
+
include_children: Whether to include Vars from children.
|
|
1438
|
+
|
|
1439
|
+
Returns:
|
|
1440
|
+
Each var referenced by the component (props, styles, event handlers).
|
|
1441
|
+
"""
|
|
1442
|
+
return super()._get_vars(include_children=include_children) + [
|
|
1443
|
+
prop for prop in self.props.values() if isinstance(prop, Var)
|
|
1444
|
+
]
|
|
1445
|
+
|
|
1396
1446
|
@lru_cache(maxsize=None) # noqa
|
|
1397
1447
|
def get_component(self) -> Component:
|
|
1398
1448
|
"""Render the component.
|
|
@@ -1757,6 +1807,14 @@ class StatefulComponent(BaseComponent):
|
|
|
1757
1807
|
)
|
|
1758
1808
|
return trigger_memo
|
|
1759
1809
|
|
|
1810
|
+
def get_hooks_internal(self) -> set[str]:
|
|
1811
|
+
"""Get the reflex internal hooks for the component and its children.
|
|
1812
|
+
|
|
1813
|
+
Returns:
|
|
1814
|
+
The code that should appear just before user-defined hooks.
|
|
1815
|
+
"""
|
|
1816
|
+
return set()
|
|
1817
|
+
|
|
1760
1818
|
def get_hooks(self) -> set[str]:
|
|
1761
1819
|
"""Get the React hooks for this component.
|
|
1762
1820
|
|
|
@@ -1865,7 +1923,7 @@ class MemoizationLeaf(Component):
|
|
|
1865
1923
|
The memoization leaf
|
|
1866
1924
|
"""
|
|
1867
1925
|
comp = super().create(*children, **props)
|
|
1868
|
-
if comp.get_hooks():
|
|
1926
|
+
if comp.get_hooks() or comp.get_hooks_internal():
|
|
1869
1927
|
comp._memoization_mode = cls._memoization_mode.copy(
|
|
1870
1928
|
update={"disposition": MemoizationDisposition.ALWAYS}
|
|
1871
1929
|
)
|
reflex/components/core/upload.py
CHANGED
|
@@ -128,7 +128,7 @@ uploaded_files_url_prefix: Var = Var.create_safe(
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
def get_upload_url(file_path: str) -> str:
|
|
131
|
+
def get_upload_url(file_path: str) -> Var[str]:
|
|
132
132
|
"""Get the URL of an uploaded file.
|
|
133
133
|
|
|
134
134
|
Args:
|
|
@@ -139,7 +139,7 @@ def get_upload_url(file_path: str) -> str:
|
|
|
139
139
|
"""
|
|
140
140
|
Upload.is_used = True
|
|
141
141
|
|
|
142
|
-
return f"{uploaded_files_url_prefix}/{file_path}"
|
|
142
|
+
return Var.create_safe(f"{uploaded_files_url_prefix}/{file_path}")
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
def _on_drop_spec(files: Var):
|
|
@@ -162,7 +162,7 @@ class Form(BaseHTML):
|
|
|
162
162
|
props["handle_submit_unique_name"] = ""
|
|
163
163
|
form = super().create(*children, **props)
|
|
164
164
|
form.handle_submit_unique_name = md5(
|
|
165
|
-
str(form.get_hooks()).encode("utf-8")
|
|
165
|
+
str(form.get_hooks_internal().union(form.get_hooks())).encode("utf-8")
|
|
166
166
|
).hexdigest()
|
|
167
167
|
return form
|
|
168
168
|
|
|
@@ -291,8 +291,10 @@ class Markdown(Component):
|
|
|
291
291
|
|
|
292
292
|
def _get_custom_code(self) -> str | None:
|
|
293
293
|
hooks = set()
|
|
294
|
-
for
|
|
295
|
-
|
|
294
|
+
for _component in self.component_map.values():
|
|
295
|
+
comp = _component(_MOCK_ARG)
|
|
296
|
+
hooks |= comp.get_hooks_internal()
|
|
297
|
+
hooks |= comp.get_hooks()
|
|
296
298
|
formatted_hooks = "\n".join(hooks)
|
|
297
299
|
return f"""
|
|
298
300
|
function {self._get_component_map_name()} () {{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Interactive components provided by @radix-ui/themes."""
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
from typing import Any, Dict, List, Literal, Optional, Union
|
|
4
5
|
|
|
@@ -126,7 +127,7 @@ class HighLevelRadioGroup(RadixThemesComponent):
|
|
|
126
127
|
def create(
|
|
127
128
|
cls,
|
|
128
129
|
items: Var[List[Optional[Union[str, int, float, list, dict, bool]]]],
|
|
129
|
-
**props
|
|
130
|
+
**props,
|
|
130
131
|
) -> Component:
|
|
131
132
|
"""Create a radio group component.
|
|
132
133
|
|
|
@@ -184,6 +184,9 @@ class HighLevelSelect(SelectRoot):
|
|
|
184
184
|
# The width of the select.
|
|
185
185
|
width: Var[str]
|
|
186
186
|
|
|
187
|
+
# The positioning mode to use. Default is "item-aligned".
|
|
188
|
+
position: Var[Literal["item-aligned", "popper"]]
|
|
189
|
+
|
|
187
190
|
@classmethod
|
|
188
191
|
def create(cls, items: Union[List[str], Var[List[str]]], **props) -> Component:
|
|
189
192
|
"""Create a select component.
|
|
@@ -196,12 +199,14 @@ class HighLevelSelect(SelectRoot):
|
|
|
196
199
|
The select component.
|
|
197
200
|
"""
|
|
198
201
|
content_props = {
|
|
199
|
-
prop: props.pop(prop)
|
|
202
|
+
prop: props.pop(prop)
|
|
203
|
+
for prop in ["high_contrast", "position"]
|
|
204
|
+
if prop in props
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
trigger_props = {
|
|
203
208
|
prop: props.pop(prop)
|
|
204
|
-
for prop in ["placeholder", "variant", "radius", "width"]
|
|
209
|
+
for prop in ["placeholder", "variant", "radius", "width", "flex_shrink"]
|
|
205
210
|
if prop in props
|
|
206
211
|
}
|
|
207
212
|
|
|
@@ -864,6 +864,12 @@ class HighLevelSelect(SelectRoot):
|
|
|
864
864
|
]
|
|
865
865
|
] = None,
|
|
866
866
|
width: Optional[Union[Var[str], str]] = None,
|
|
867
|
+
position: Optional[
|
|
868
|
+
Union[
|
|
869
|
+
Var[Literal["item-aligned", "popper"]],
|
|
870
|
+
Literal["item-aligned", "popper"],
|
|
871
|
+
]
|
|
872
|
+
] = None,
|
|
867
873
|
size: Optional[
|
|
868
874
|
Union[Var[Literal["1", "2", "3"]], Literal["1", "2", "3"]]
|
|
869
875
|
] = None,
|
|
@@ -945,6 +951,7 @@ class HighLevelSelect(SelectRoot):
|
|
|
945
951
|
variant: The variant of the select.
|
|
946
952
|
radius: The radius of the select.
|
|
947
953
|
width: The width of the select.
|
|
954
|
+
position: The positioning mode to use. Default is "item-aligned".
|
|
948
955
|
size: The size of the select: "1" | "2" | "3"
|
|
949
956
|
default_value: The value of the select when initially rendered. Use when you do not need to control the state of the select.
|
|
950
957
|
value: The controlled value of the select. Should be used in conjunction with on_change.
|
|
@@ -1057,6 +1064,12 @@ class Select(ComponentNamespace):
|
|
|
1057
1064
|
]
|
|
1058
1065
|
] = None,
|
|
1059
1066
|
width: Optional[Union[Var[str], str]] = None,
|
|
1067
|
+
position: Optional[
|
|
1068
|
+
Union[
|
|
1069
|
+
Var[Literal["item-aligned", "popper"]],
|
|
1070
|
+
Literal["item-aligned", "popper"],
|
|
1071
|
+
]
|
|
1072
|
+
] = None,
|
|
1060
1073
|
size: Optional[
|
|
1061
1074
|
Union[Var[Literal["1", "2", "3"]], Literal["1", "2", "3"]]
|
|
1062
1075
|
] = None,
|
|
@@ -1138,6 +1151,7 @@ class Select(ComponentNamespace):
|
|
|
1138
1151
|
variant: The variant of the select.
|
|
1139
1152
|
radius: The radius of the select.
|
|
1140
1153
|
width: The width of the select.
|
|
1154
|
+
position: The positioning mode to use. Default is "item-aligned".
|
|
1141
1155
|
size: The size of the select: "1" | "2" | "3"
|
|
1142
1156
|
default_value: The value of the select when initially rendered. Use when you do not need to control the state of the select.
|
|
1143
1157
|
value: The controlled value of the select. Should be used in conjunction with on_change.
|
|
@@ -5,6 +5,8 @@ from typing import Iterable, Literal, Optional, Union
|
|
|
5
5
|
from reflex.components.component import Component, ComponentNamespace
|
|
6
6
|
from reflex.components.core.foreach import Foreach
|
|
7
7
|
from reflex.components.el.elements.typography import Li
|
|
8
|
+
from reflex.components.lucide.icon import Icon
|
|
9
|
+
from reflex.components.radix.themes.typography.text import Text
|
|
8
10
|
from reflex.style import Style
|
|
9
11
|
from reflex.vars import Var
|
|
10
12
|
|
|
@@ -39,12 +41,16 @@ LiteralListStyleTypeOrdered = Literal[
|
|
|
39
41
|
class BaseList(Flex, LayoutComponent):
|
|
40
42
|
"""Base class for ordered and unordered lists."""
|
|
41
43
|
|
|
44
|
+
# The style of the list. Default to "none".
|
|
45
|
+
list_style_type: Var[
|
|
46
|
+
Union[LiteralListStyleTypeUnordered, LiteralListStyleTypeOrdered]
|
|
47
|
+
]
|
|
48
|
+
|
|
42
49
|
@classmethod
|
|
43
50
|
def create(
|
|
44
51
|
cls,
|
|
45
52
|
*children,
|
|
46
53
|
items: Optional[Union[Var[Iterable], Iterable]] = None,
|
|
47
|
-
list_style_type: str = "",
|
|
48
54
|
**props,
|
|
49
55
|
):
|
|
50
56
|
"""Create a list component.
|
|
@@ -52,21 +58,23 @@ class BaseList(Flex, LayoutComponent):
|
|
|
52
58
|
Args:
|
|
53
59
|
*children: The children of the component.
|
|
54
60
|
items: A list of items to add to the list.
|
|
55
|
-
list_style_type: The style of the list.
|
|
56
61
|
**props: The properties of the component.
|
|
57
62
|
|
|
58
63
|
Returns:
|
|
59
64
|
The list component.
|
|
65
|
+
|
|
60
66
|
"""
|
|
67
|
+
list_style_type = props.pop("list_style_type", "none")
|
|
61
68
|
if not children and items is not None:
|
|
62
69
|
if isinstance(items, Var):
|
|
63
70
|
children = [Foreach.create(items, ListItem.create)]
|
|
64
71
|
else:
|
|
65
72
|
children = [ListItem.create(item) for item in items]
|
|
66
|
-
props["list_style_type"] = list_style_type
|
|
73
|
+
# props["list_style_type"] = list_style_type
|
|
67
74
|
props["direction"] = "column"
|
|
68
75
|
style = props.setdefault("style", {})
|
|
69
76
|
style["list_style_position"] = "outside"
|
|
77
|
+
style["list_style_type"] = list_style_type
|
|
70
78
|
if "gap" in props:
|
|
71
79
|
style["gap"] = props["gap"]
|
|
72
80
|
return super().create(*children, **props)
|
|
@@ -102,6 +110,7 @@ class UnorderedList(BaseList):
|
|
|
102
110
|
|
|
103
111
|
Returns:
|
|
104
112
|
The list component.
|
|
113
|
+
|
|
105
114
|
"""
|
|
106
115
|
return super().create(
|
|
107
116
|
*children, items=items, list_style_type=list_style_type, **props
|
|
@@ -129,6 +138,7 @@ class OrderedList(BaseList):
|
|
|
129
138
|
|
|
130
139
|
Returns:
|
|
131
140
|
The list component.
|
|
141
|
+
|
|
132
142
|
"""
|
|
133
143
|
return super().create(
|
|
134
144
|
*children, items=items, list_style_type=list_style_type, **props
|
|
@@ -138,7 +148,24 @@ class OrderedList(BaseList):
|
|
|
138
148
|
class ListItem(Li):
|
|
139
149
|
"""Display an item of an ordered or unordered list."""
|
|
140
150
|
|
|
141
|
-
|
|
151
|
+
@classmethod
|
|
152
|
+
def create(cls, *children, **props):
|
|
153
|
+
"""Create a list item component.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
*children: The children of the component.
|
|
157
|
+
**props: The properties of the component.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
The list item component.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
for child in children:
|
|
164
|
+
if isinstance(child, Text):
|
|
165
|
+
child.as_ = "span"
|
|
166
|
+
elif isinstance(child, Icon) and "display" not in child.style:
|
|
167
|
+
child.style["display"] = "inline"
|
|
168
|
+
return super().create(*children, **props)
|
|
142
169
|
|
|
143
170
|
|
|
144
171
|
class List(ComponentNamespace):
|
|
@@ -11,6 +11,8 @@ from typing import Iterable, Literal, Optional, Union
|
|
|
11
11
|
from reflex.components.component import Component, ComponentNamespace
|
|
12
12
|
from reflex.components.core.foreach import Foreach
|
|
13
13
|
from reflex.components.el.elements.typography import Li
|
|
14
|
+
from reflex.components.lucide.icon import Icon
|
|
15
|
+
from reflex.components.radix.themes.typography.text import Text
|
|
14
16
|
from reflex.style import Style
|
|
15
17
|
from reflex.vars import Var
|
|
16
18
|
from .base import LayoutComponent
|
|
@@ -41,7 +43,50 @@ class BaseList(Flex, LayoutComponent):
|
|
|
41
43
|
cls,
|
|
42
44
|
*children,
|
|
43
45
|
items: Optional[Union[Union[Var[Iterable], Iterable], Iterable]] = None,
|
|
44
|
-
list_style_type: Optional[
|
|
46
|
+
list_style_type: Optional[
|
|
47
|
+
Union[
|
|
48
|
+
Var[
|
|
49
|
+
Union[
|
|
50
|
+
Literal["none", "disc", "circle", "square"],
|
|
51
|
+
Literal[
|
|
52
|
+
"none",
|
|
53
|
+
"decimal",
|
|
54
|
+
"decimal-leading-zero",
|
|
55
|
+
"lower-roman",
|
|
56
|
+
"upper-roman",
|
|
57
|
+
"lower-greek",
|
|
58
|
+
"lower-latin",
|
|
59
|
+
"upper-latin",
|
|
60
|
+
"armenian",
|
|
61
|
+
"georgian",
|
|
62
|
+
"lower-alpha",
|
|
63
|
+
"upper-alpha",
|
|
64
|
+
"hiragana",
|
|
65
|
+
"katakana",
|
|
66
|
+
],
|
|
67
|
+
]
|
|
68
|
+
],
|
|
69
|
+
Union[
|
|
70
|
+
Literal["none", "disc", "circle", "square"],
|
|
71
|
+
Literal[
|
|
72
|
+
"none",
|
|
73
|
+
"decimal",
|
|
74
|
+
"decimal-leading-zero",
|
|
75
|
+
"lower-roman",
|
|
76
|
+
"upper-roman",
|
|
77
|
+
"lower-greek",
|
|
78
|
+
"lower-latin",
|
|
79
|
+
"upper-latin",
|
|
80
|
+
"armenian",
|
|
81
|
+
"georgian",
|
|
82
|
+
"lower-alpha",
|
|
83
|
+
"upper-alpha",
|
|
84
|
+
"hiragana",
|
|
85
|
+
"katakana",
|
|
86
|
+
],
|
|
87
|
+
],
|
|
88
|
+
]
|
|
89
|
+
] = None,
|
|
45
90
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
|
46
91
|
direction: Optional[
|
|
47
92
|
Union[
|
|
@@ -257,7 +302,7 @@ class BaseList(Flex, LayoutComponent):
|
|
|
257
302
|
Args:
|
|
258
303
|
*children: The children of the component.
|
|
259
304
|
items: A list of items to add to the list.
|
|
260
|
-
list_style_type: The style of the list.
|
|
305
|
+
list_style_type: The style of the list. Default to "none".
|
|
261
306
|
as_child: Change the default rendered element for the one passed as a child, merging their props and behavior.
|
|
262
307
|
direction: How child items are layed out: "row" | "column" | "row-reverse" | "column-reverse"
|
|
263
308
|
align: Alignment of children along the main axis: "start" | "center" | "end" | "baseline" | "stretch"
|
|
@@ -306,6 +351,7 @@ class BaseList(Flex, LayoutComponent):
|
|
|
306
351
|
|
|
307
352
|
Returns:
|
|
308
353
|
The list component.
|
|
354
|
+
|
|
309
355
|
"""
|
|
310
356
|
...
|
|
311
357
|
|
|
@@ -581,6 +627,7 @@ class UnorderedList(BaseList):
|
|
|
581
627
|
|
|
582
628
|
Returns:
|
|
583
629
|
The list component.
|
|
630
|
+
|
|
584
631
|
"""
|
|
585
632
|
...
|
|
586
633
|
|
|
@@ -873,12 +920,11 @@ class OrderedList(BaseList):
|
|
|
873
920
|
|
|
874
921
|
Returns:
|
|
875
922
|
The list component.
|
|
923
|
+
|
|
876
924
|
"""
|
|
877
925
|
...
|
|
878
926
|
|
|
879
927
|
class ListItem(Li):
|
|
880
|
-
...
|
|
881
|
-
|
|
882
928
|
@overload
|
|
883
929
|
@classmethod
|
|
884
930
|
def create( # type: ignore
|
|
@@ -977,7 +1023,7 @@ class ListItem(Li):
|
|
|
977
1023
|
] = None,
|
|
978
1024
|
**props
|
|
979
1025
|
) -> "ListItem":
|
|
980
|
-
"""Create
|
|
1026
|
+
"""Create a list item component.
|
|
981
1027
|
|
|
982
1028
|
Args:
|
|
983
1029
|
*children: The children of the component.
|
|
@@ -1003,13 +1049,11 @@ class ListItem(Li):
|
|
|
1003
1049
|
class_name: The class name for the component.
|
|
1004
1050
|
autofocus: Whether the component should take the focus once the page is loaded
|
|
1005
1051
|
custom_attrs: custom attribute
|
|
1006
|
-
**props: The
|
|
1052
|
+
**props: The properties of the component.
|
|
1007
1053
|
|
|
1008
1054
|
Returns:
|
|
1009
|
-
The component.
|
|
1055
|
+
The list item component.
|
|
1010
1056
|
|
|
1011
|
-
Raises:
|
|
1012
|
-
TypeError: If an invalid child is passed.
|
|
1013
1057
|
"""
|
|
1014
1058
|
...
|
|
1015
1059
|
|
reflex/constants/installer.py
CHANGED
reflex/constants/route.py
CHANGED
|
@@ -50,9 +50,9 @@ class DefaultPage(SimpleNamespace):
|
|
|
50
50
|
"""Default page constants."""
|
|
51
51
|
|
|
52
52
|
# The default title to show for Reflex apps.
|
|
53
|
-
TITLE = "
|
|
53
|
+
TITLE = "{} | {}"
|
|
54
54
|
# The default description to show for Reflex apps.
|
|
55
|
-
DESCRIPTION = "
|
|
55
|
+
DESCRIPTION = ""
|
|
56
56
|
# The default image to show for Reflex apps.
|
|
57
57
|
IMAGE = "favicon.ico"
|
|
58
58
|
# The default meta list to show for Reflex apps.
|
reflex/event.py
CHANGED
|
@@ -147,6 +147,10 @@ class EventHandler(EventActionsMixin):
|
|
|
147
147
|
# The function to call in response to the event.
|
|
148
148
|
fn: Any
|
|
149
149
|
|
|
150
|
+
# The full name of the state class this event handler is attached to.
|
|
151
|
+
# Emtpy string means this event handler is a server side event.
|
|
152
|
+
state_full_name: str = ""
|
|
153
|
+
|
|
150
154
|
class Config:
|
|
151
155
|
"""The Pydantic config."""
|
|
152
156
|
|
reflex/model.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Optional
|
|
8
|
+
from typing import Any, ClassVar, Optional, Type, Union
|
|
9
9
|
|
|
10
10
|
import alembic.autogenerate
|
|
11
11
|
import alembic.command
|
|
@@ -51,6 +51,88 @@ def get_engine(url: str | None = None):
|
|
|
51
51
|
return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
SQLModelOrSqlAlchemy = Union[
|
|
55
|
+
Type[sqlmodel.SQLModel], Type[sqlalchemy.orm.DeclarativeBase]
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ModelRegistry:
|
|
60
|
+
"""Registry for all models."""
|
|
61
|
+
|
|
62
|
+
models: ClassVar[set[SQLModelOrSqlAlchemy]] = set()
|
|
63
|
+
|
|
64
|
+
# Cache the metadata to avoid re-creating it.
|
|
65
|
+
_metadata: ClassVar[sqlalchemy.MetaData | None] = None
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def register(cls, model: SQLModelOrSqlAlchemy):
|
|
69
|
+
"""Register a model. Can be used directly or as a decorator.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
model: The model to register.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The model passed in as an argument (Allows decorator usage)
|
|
76
|
+
"""
|
|
77
|
+
cls.models.add(model)
|
|
78
|
+
return model
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def get_models(cls, include_empty: bool = False) -> set[SQLModelOrSqlAlchemy]:
|
|
82
|
+
"""Get registered models.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
include_empty: If True, include models with empty metadata.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The registered models.
|
|
89
|
+
"""
|
|
90
|
+
if include_empty:
|
|
91
|
+
return cls.models
|
|
92
|
+
return {
|
|
93
|
+
model for model in cls.models if not cls._model_metadata_is_empty(model)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _model_metadata_is_empty(model: SQLModelOrSqlAlchemy) -> bool:
|
|
98
|
+
"""Check if the model metadata is empty.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
model: The model to check.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if the model metadata is empty, False otherwise.
|
|
105
|
+
"""
|
|
106
|
+
return len(model.metadata.tables) == 0
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def get_metadata(cls) -> sqlalchemy.MetaData:
|
|
110
|
+
"""Get the database metadata.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The database metadata.
|
|
114
|
+
"""
|
|
115
|
+
if cls._metadata is not None:
|
|
116
|
+
return cls._metadata
|
|
117
|
+
|
|
118
|
+
models = cls.get_models(include_empty=False)
|
|
119
|
+
|
|
120
|
+
if len(models) == 1:
|
|
121
|
+
metadata = next(iter(models)).metadata
|
|
122
|
+
else:
|
|
123
|
+
# Merge the metadata from all the models.
|
|
124
|
+
# This allows mixing bare sqlalchemy models with sqlmodel models in one database.
|
|
125
|
+
metadata = sqlalchemy.MetaData()
|
|
126
|
+
for model in cls.get_models():
|
|
127
|
+
for table in model.metadata.tables.values():
|
|
128
|
+
table.to_metadata(metadata)
|
|
129
|
+
|
|
130
|
+
# Cache the metadata
|
|
131
|
+
cls._metadata = metadata
|
|
132
|
+
|
|
133
|
+
return metadata
|
|
134
|
+
|
|
135
|
+
|
|
54
136
|
class Model(Base, sqlmodel.SQLModel):
|
|
55
137
|
"""Base class to define a table in the database."""
|
|
56
138
|
|
|
@@ -113,7 +195,7 @@ class Model(Base, sqlmodel.SQLModel):
|
|
|
113
195
|
def create_all():
|
|
114
196
|
"""Create all the tables."""
|
|
115
197
|
engine = get_engine()
|
|
116
|
-
|
|
198
|
+
ModelRegistry.get_metadata().create_all(engine)
|
|
117
199
|
|
|
118
200
|
@staticmethod
|
|
119
201
|
def get_db_engine():
|
|
@@ -224,7 +306,7 @@ class Model(Base, sqlmodel.SQLModel):
|
|
|
224
306
|
) as env:
|
|
225
307
|
env.configure(
|
|
226
308
|
connection=connection,
|
|
227
|
-
target_metadata=
|
|
309
|
+
target_metadata=ModelRegistry.get_metadata(),
|
|
228
310
|
render_item=cls._alembic_render_item,
|
|
229
311
|
process_revision_directives=writer, # type: ignore
|
|
230
312
|
compare_type=False,
|
|
@@ -300,7 +382,6 @@ class Model(Base, sqlmodel.SQLModel):
|
|
|
300
382
|
return True
|
|
301
383
|
|
|
302
384
|
@classmethod
|
|
303
|
-
@property
|
|
304
385
|
def select(cls):
|
|
305
386
|
"""Select rows from the table.
|
|
306
387
|
|
|
@@ -310,6 +391,9 @@ class Model(Base, sqlmodel.SQLModel):
|
|
|
310
391
|
return sqlmodel.select(cls)
|
|
311
392
|
|
|
312
393
|
|
|
394
|
+
ModelRegistry.register(Model)
|
|
395
|
+
|
|
396
|
+
|
|
313
397
|
def session(url: str | None = None) -> sqlmodel.Session:
|
|
314
398
|
"""Get a session to interact with the database.
|
|
315
399
|
|