reflex 0.3.2a1__py3-none-any.whl → 0.3.3a1__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/custom_component.js.jinja2 +20 -3
- reflex/.templates/web/next.config.js +1 -0
- reflex/.templates/web/utils/helpers/range.js +43 -0
- reflex/.templates/web/utils/state.js +10 -6
- reflex/__init__.py +312 -40
- reflex/__init__.pyi +477 -0
- reflex/compiler/compiler.py +3 -0
- reflex/components/__init__.py +138 -138
- reflex/components/component.py +29 -22
- reflex/components/datadisplay/__init__.py +3 -1
- reflex/components/datadisplay/code.py +388 -14
- reflex/components/datadisplay/code.pyi +1146 -10
- reflex/components/forms/button.py +3 -0
- reflex/components/forms/checkbox.py +3 -0
- reflex/components/forms/form.py +90 -27
- reflex/components/forms/input.py +3 -0
- reflex/components/forms/numberinput.py +3 -0
- reflex/components/forms/pininput.py +77 -21
- reflex/components/forms/radio.py +3 -0
- reflex/components/forms/rangeslider.py +3 -0
- reflex/components/forms/select.py +3 -0
- reflex/components/forms/slider.py +3 -0
- reflex/components/forms/switch.py +3 -0
- reflex/components/forms/textarea.py +3 -0
- reflex/components/layout/foreach.py +12 -6
- reflex/components/libs/chakra.py +2 -0
- reflex/components/libs/chakra.pyi +323 -24
- reflex/components/tags/iter_tag.py +18 -18
- reflex/components/tags/tag.py +3 -2
- reflex/components/typography/markdown.py +10 -0
- reflex/config.py +12 -0
- reflex/constants/installer.py +4 -4
- reflex/event.py +4 -0
- reflex/page.py +3 -4
- reflex/page.pyi +17 -0
- reflex/reflex.py +3 -0
- reflex/state.py +31 -12
- reflex/testing.py +1 -1
- reflex/utils/build.py +24 -19
- reflex/utils/console.py +5 -1
- reflex/utils/format.py +26 -9
- reflex/utils/prerequisites.py +27 -28
- reflex/utils/processes.py +5 -4
- reflex/vars.py +80 -12
- reflex/vars.pyi +7 -0
- {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/METADATA +3 -2
- {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/RECORD +50 -53
- reflex/.templates/web/.pytest_cache/.gitignore +0 -2
- reflex/.templates/web/.pytest_cache/CACHEDIR.TAG +0 -4
- reflex/.templates/web/.pytest_cache/README.md +0 -8
- reflex/.templates/web/.pytest_cache/v/cache/nodeids +0 -1
- reflex/.templates/web/.pytest_cache/v/cache/stepwise +0 -1
- reflex/.templates/web/styles/code/prism.js +0 -1015
- {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/LICENSE +0 -0
- {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/WHEEL +0 -0
- {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/entry_points.txt +0 -0
|
@@ -3,8 +3,25 @@
|
|
|
3
3
|
{% block export %}
|
|
4
4
|
{% for component in components %}
|
|
5
5
|
|
|
6
|
-
export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) =>
|
|
7
|
-
|
|
8
|
-
)
|
|
6
|
+
export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => {
|
|
7
|
+
{% if component.name == "CodeBlock" and "language" in component.props %}
|
|
8
|
+
if (language) {
|
|
9
|
+
(async () => {
|
|
10
|
+
try {
|
|
11
|
+
const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${language}`);
|
|
12
|
+
SyntaxHighlighter.registerLanguage(language, module.default);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error(`Error importing language module for ${language}:`, error);
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
{% endif %}
|
|
21
|
+
return(
|
|
22
|
+
{{utils.render(component.render)}}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
})
|
|
9
26
|
{% endfor %}
|
|
10
27
|
{% endblock %}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulate the python range() builtin function.
|
|
3
|
+
* inspired by https://dev.to/guyariely/using-python-range-in-javascript-337p
|
|
4
|
+
*
|
|
5
|
+
* If needed outside of an iterator context, use `Array.from(range(10))` or
|
|
6
|
+
* spread syntax `[...range(10)]` to get an array.
|
|
7
|
+
*
|
|
8
|
+
* @param {number} start: the start or end of the range.
|
|
9
|
+
* @param {number} stop: the end of the range.
|
|
10
|
+
* @param {number} step: the step of the range.
|
|
11
|
+
* @returns {object} an object with a Symbol.iterator method over the range
|
|
12
|
+
*/
|
|
13
|
+
export default function range(start, stop, step) {
|
|
14
|
+
return {
|
|
15
|
+
[Symbol.iterator]() {
|
|
16
|
+
if (stop === undefined) {
|
|
17
|
+
stop = start;
|
|
18
|
+
start = 0;
|
|
19
|
+
}
|
|
20
|
+
if (step === undefined) {
|
|
21
|
+
step = 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let i = start - step;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
next() {
|
|
28
|
+
i += step;
|
|
29
|
+
if ((step > 0 && i < stop) || (step < 0 && i > stop)) {
|
|
30
|
+
return {
|
|
31
|
+
value: i,
|
|
32
|
+
done: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
value: undefined,
|
|
37
|
+
done: true,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -12,6 +12,9 @@ import { initialEvents } from "utils/context.js"
|
|
|
12
12
|
const EVENTURL = env.EVENT
|
|
13
13
|
const UPLOADURL = env.UPLOAD
|
|
14
14
|
|
|
15
|
+
// These hostnames indicate that the backend and frontend are reachable via the same domain.
|
|
16
|
+
const SAME_DOMAIN_HOSTNAMES = ["localhost", "0.0.0.0", "::", "0:0:0:0:0:0:0:0"]
|
|
17
|
+
|
|
15
18
|
// Global variable to hold the token.
|
|
16
19
|
let token;
|
|
17
20
|
|
|
@@ -74,12 +77,13 @@ export const getToken = () => {
|
|
|
74
77
|
export const getEventURL = () => {
|
|
75
78
|
// Get backend URL object from the endpoint.
|
|
76
79
|
const endpoint = new URL(EVENTURL);
|
|
77
|
-
if (endpoint.hostname
|
|
78
|
-
//
|
|
79
|
-
// then use the frontend host.
|
|
80
|
+
if (SAME_DOMAIN_HOSTNAMES.includes(endpoint.hostname)) {
|
|
81
|
+
// Use the frontend domain to access the backend
|
|
80
82
|
const frontend_hostname = window.location.hostname;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
endpoint.hostname = frontend_hostname;
|
|
84
|
+
if (window.location.protocol === "https:" && endpoint.protocol === "ws:") {
|
|
85
|
+
endpoint.protocol = "wss:";
|
|
86
|
+
endpoint.port = ""; // Assume websocket is on https port via load balancer.
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
return endpoint
|
|
@@ -569,7 +573,7 @@ export const getRefValues = (refs) => {
|
|
|
569
573
|
return;
|
|
570
574
|
}
|
|
571
575
|
// getAttribute is used by RangeSlider because it doesn't assign value
|
|
572
|
-
return refs.map((ref) => ref.current.value || ref.current.getAttribute("aria-valuenow"));
|
|
576
|
+
return refs.map((ref) => ref.current ? ref.current.value || ref.current.getAttribute("aria-valuenow") : null);
|
|
573
577
|
}
|
|
574
578
|
|
|
575
579
|
/**
|
reflex/__init__.py
CHANGED
|
@@ -4,44 +4,316 @@ Anything imported here will be available in the default Reflex import as `rx.*`.
|
|
|
4
4
|
To signal to typecheckers that something should be reexported,
|
|
5
5
|
we use the Flask "import name as name" syntax.
|
|
6
6
|
"""
|
|
7
|
+
import importlib
|
|
8
|
+
from typing import Type
|
|
7
9
|
|
|
8
|
-
from . import
|
|
9
|
-
from .
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
10
|
+
from reflex.page import page as page
|
|
11
|
+
from reflex.utils.format import to_snake_case
|
|
12
|
+
|
|
13
|
+
_ALL_COMPONENTS = [
|
|
14
|
+
"Accordion",
|
|
15
|
+
"AccordionButton",
|
|
16
|
+
"AccordionIcon",
|
|
17
|
+
"AccordionItem",
|
|
18
|
+
"AccordionPanel",
|
|
19
|
+
"Alert",
|
|
20
|
+
"AlertDescription",
|
|
21
|
+
"AlertDialog",
|
|
22
|
+
"AlertDialogBody",
|
|
23
|
+
"AlertDialogContent",
|
|
24
|
+
"AlertDialogFooter",
|
|
25
|
+
"AlertDialogHeader",
|
|
26
|
+
"AlertDialogOverlay",
|
|
27
|
+
"AlertIcon",
|
|
28
|
+
"AlertTitle",
|
|
29
|
+
"AspectRatio",
|
|
30
|
+
"Audio",
|
|
31
|
+
"Avatar",
|
|
32
|
+
"AvatarBadge",
|
|
33
|
+
"AvatarGroup",
|
|
34
|
+
"Badge",
|
|
35
|
+
"Box",
|
|
36
|
+
"Breadcrumb",
|
|
37
|
+
"BreadcrumbItem",
|
|
38
|
+
"BreadcrumbLink",
|
|
39
|
+
"BreadcrumbSeparator",
|
|
40
|
+
"Button",
|
|
41
|
+
"ButtonGroup",
|
|
42
|
+
"Card",
|
|
43
|
+
"CardBody",
|
|
44
|
+
"CardFooter",
|
|
45
|
+
"CardHeader",
|
|
46
|
+
"Center",
|
|
47
|
+
"Checkbox",
|
|
48
|
+
"CheckboxGroup",
|
|
49
|
+
"CircularProgress",
|
|
50
|
+
"CircularProgressLabel",
|
|
51
|
+
"Circle",
|
|
52
|
+
"Code",
|
|
53
|
+
"CodeBlock",
|
|
54
|
+
"Collapse",
|
|
55
|
+
"ColorModeButton",
|
|
56
|
+
"ColorModeIcon",
|
|
57
|
+
"ColorModeSwitch",
|
|
58
|
+
"Component",
|
|
59
|
+
"Cond",
|
|
60
|
+
"ConnectionBanner",
|
|
61
|
+
"ConnectionModal",
|
|
62
|
+
"Container",
|
|
63
|
+
"DataTable",
|
|
64
|
+
"DataEditor",
|
|
65
|
+
"DebounceInput",
|
|
66
|
+
"Divider",
|
|
67
|
+
"Drawer",
|
|
68
|
+
"DrawerBody",
|
|
69
|
+
"DrawerCloseButton",
|
|
70
|
+
"DrawerContent",
|
|
71
|
+
"DrawerFooter",
|
|
72
|
+
"DrawerHeader",
|
|
73
|
+
"DrawerOverlay",
|
|
74
|
+
"Editable",
|
|
75
|
+
"EditableInput",
|
|
76
|
+
"EditablePreview",
|
|
77
|
+
"EditableTextarea",
|
|
78
|
+
"Editor",
|
|
79
|
+
"Email",
|
|
80
|
+
"Fade",
|
|
81
|
+
"Flex",
|
|
82
|
+
"Foreach",
|
|
83
|
+
"Form",
|
|
84
|
+
"FormControl",
|
|
85
|
+
"FormErrorMessage",
|
|
86
|
+
"FormHelperText",
|
|
87
|
+
"FormLabel",
|
|
88
|
+
"Fragment",
|
|
89
|
+
"Grid",
|
|
90
|
+
"GridItem",
|
|
91
|
+
"Heading",
|
|
92
|
+
"Highlight",
|
|
93
|
+
"Hstack",
|
|
94
|
+
"Html",
|
|
95
|
+
"Icon",
|
|
96
|
+
"IconButton",
|
|
97
|
+
"Image",
|
|
98
|
+
"Input",
|
|
99
|
+
"InputGroup",
|
|
100
|
+
"InputLeftAddon",
|
|
101
|
+
"InputLeftElement",
|
|
102
|
+
"InputRightAddon",
|
|
103
|
+
"InputRightElement",
|
|
104
|
+
"Kbd",
|
|
105
|
+
"Link",
|
|
106
|
+
"LinkBox",
|
|
107
|
+
"LinkOverlay",
|
|
108
|
+
"List",
|
|
109
|
+
"ListItem",
|
|
110
|
+
"Markdown",
|
|
111
|
+
"Menu",
|
|
112
|
+
"MenuButton",
|
|
113
|
+
"MenuDivider",
|
|
114
|
+
"MenuGroup",
|
|
115
|
+
"MenuItem",
|
|
116
|
+
"MenuItemOption",
|
|
117
|
+
"MenuList",
|
|
118
|
+
"MenuOptionGroup",
|
|
119
|
+
"Modal",
|
|
120
|
+
"ModalBody",
|
|
121
|
+
"ModalCloseButton",
|
|
122
|
+
"ModalContent",
|
|
123
|
+
"ModalFooter",
|
|
124
|
+
"ModalHeader",
|
|
125
|
+
"ModalOverlay",
|
|
126
|
+
"Moment",
|
|
127
|
+
"MultiSelect",
|
|
128
|
+
"MultiSelectOption",
|
|
129
|
+
"NextLink",
|
|
130
|
+
"NumberDecrementStepper",
|
|
131
|
+
"NumberIncrementStepper",
|
|
132
|
+
"NumberInput",
|
|
133
|
+
"NumberInputField",
|
|
134
|
+
"NumberInputStepper",
|
|
135
|
+
"Option",
|
|
136
|
+
"OrderedList",
|
|
137
|
+
"Password",
|
|
138
|
+
"PinInput",
|
|
139
|
+
"PinInputField",
|
|
140
|
+
"Plotly",
|
|
141
|
+
"Popover",
|
|
142
|
+
"PopoverAnchor",
|
|
143
|
+
"PopoverArrow",
|
|
144
|
+
"PopoverBody",
|
|
145
|
+
"PopoverCloseButton",
|
|
146
|
+
"PopoverContent",
|
|
147
|
+
"PopoverFooter",
|
|
148
|
+
"PopoverHeader",
|
|
149
|
+
"PopoverTrigger",
|
|
150
|
+
"Progress",
|
|
151
|
+
"Radio",
|
|
152
|
+
"RadioGroup",
|
|
153
|
+
"RangeSlider",
|
|
154
|
+
"RangeSliderFilledTrack",
|
|
155
|
+
"RangeSliderThumb",
|
|
156
|
+
"RangeSliderTrack",
|
|
157
|
+
"ResponsiveGrid",
|
|
158
|
+
"ScaleFade",
|
|
159
|
+
"Script",
|
|
160
|
+
"Select",
|
|
161
|
+
"Skeleton",
|
|
162
|
+
"SkeletonCircle",
|
|
163
|
+
"SkeletonText",
|
|
164
|
+
"Slide",
|
|
165
|
+
"SlideFade",
|
|
166
|
+
"Slider",
|
|
167
|
+
"SliderFilledTrack",
|
|
168
|
+
"SliderMark",
|
|
169
|
+
"SliderThumb",
|
|
170
|
+
"SliderTrack",
|
|
171
|
+
"Spacer",
|
|
172
|
+
"Span",
|
|
173
|
+
"Spinner",
|
|
174
|
+
"Square",
|
|
175
|
+
"Stack",
|
|
176
|
+
"Stat",
|
|
177
|
+
"StatArrow",
|
|
178
|
+
"StatGroup",
|
|
179
|
+
"StatHelpText",
|
|
180
|
+
"StatLabel",
|
|
181
|
+
"StatArrow",
|
|
182
|
+
"StatGroup",
|
|
183
|
+
"StatHelpText",
|
|
184
|
+
"StatLabel",
|
|
185
|
+
"StatNumber",
|
|
186
|
+
"Step",
|
|
187
|
+
"StepDescription",
|
|
188
|
+
"StepIcon",
|
|
189
|
+
"StepIndicator",
|
|
190
|
+
"StepNumber",
|
|
191
|
+
"StepSeparator",
|
|
192
|
+
"StepStatus",
|
|
193
|
+
"StepTitle",
|
|
194
|
+
"Stepper",
|
|
195
|
+
"Switch",
|
|
196
|
+
"Tab",
|
|
197
|
+
"TabList",
|
|
198
|
+
"TabPanel",
|
|
199
|
+
"TabPanels",
|
|
200
|
+
"Table",
|
|
201
|
+
"TableCaption",
|
|
202
|
+
"TableContainer",
|
|
203
|
+
"Tabs",
|
|
204
|
+
"Tag",
|
|
205
|
+
"TagCloseButton",
|
|
206
|
+
"TagLabel",
|
|
207
|
+
"TagLeftIcon",
|
|
208
|
+
"TagRightIcon",
|
|
209
|
+
"Tbody",
|
|
210
|
+
"Td",
|
|
211
|
+
"Text",
|
|
212
|
+
"TextArea",
|
|
213
|
+
"Tfoot",
|
|
214
|
+
"Th",
|
|
215
|
+
"Thead",
|
|
216
|
+
"Tooltip",
|
|
217
|
+
"Tr",
|
|
218
|
+
"UnorderedList",
|
|
219
|
+
"Upload",
|
|
220
|
+
"Video",
|
|
221
|
+
"VisuallyHidden",
|
|
222
|
+
"Vstack",
|
|
223
|
+
"Wrap",
|
|
224
|
+
"WrapItem",
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
_ALL_COMPONENTS += [to_snake_case(component) for component in _ALL_COMPONENTS]
|
|
228
|
+
_ALL_COMPONENTS += [
|
|
229
|
+
"components",
|
|
230
|
+
"desktop_only",
|
|
231
|
+
"mobile_only",
|
|
232
|
+
"tablet_only",
|
|
233
|
+
"mobile_and_tablet",
|
|
234
|
+
"tablet_and_desktop",
|
|
235
|
+
"selected_files",
|
|
236
|
+
"clear_selected_files",
|
|
237
|
+
"EditorOptions",
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
_MAPPING = {
|
|
241
|
+
"reflex.admin": ["admin", "AdminDash"],
|
|
242
|
+
"reflex.app": ["app", "App", "UploadFile"],
|
|
243
|
+
"reflex.base": ["base", "Base"],
|
|
244
|
+
"reflex.compiler": ["compiler"],
|
|
245
|
+
"reflex.compiler.utils": ["get_asset_path"],
|
|
246
|
+
"reflex.components": _ALL_COMPONENTS,
|
|
247
|
+
"reflex.components.component": ["memo"],
|
|
248
|
+
"reflex.components.graphing": ["recharts"],
|
|
249
|
+
"reflex.config": ["config", "Config", "DBConfig"],
|
|
250
|
+
"reflex.constants": ["constants", "Env"],
|
|
251
|
+
"reflex.el": ["el"],
|
|
252
|
+
"reflex.event": [
|
|
253
|
+
"event",
|
|
254
|
+
"EventChain",
|
|
255
|
+
"background",
|
|
256
|
+
"call_script",
|
|
257
|
+
"clear_local_storage",
|
|
258
|
+
"console_log",
|
|
259
|
+
"download",
|
|
260
|
+
"prevent_default",
|
|
261
|
+
"redirect",
|
|
262
|
+
"remove_cookie",
|
|
263
|
+
"remove_local_storage",
|
|
264
|
+
"set_clipboard",
|
|
265
|
+
"set_focus",
|
|
266
|
+
"set_value",
|
|
267
|
+
"stop_propagation",
|
|
268
|
+
"upload_files",
|
|
269
|
+
"window_alert",
|
|
270
|
+
],
|
|
271
|
+
"reflex.middleware": ["middleware", "Middleware"],
|
|
272
|
+
"reflex.model": ["model", "session", "Model"],
|
|
273
|
+
"reflex.page": ["page"],
|
|
274
|
+
"reflex.route": ["route"],
|
|
275
|
+
"reflex.state": ["state", "var", "Cookie", "LocalStorage", "State"],
|
|
276
|
+
"reflex.style": ["style", "color_mode", "toggle_color_mode"],
|
|
277
|
+
"reflex.testing": ["testing"],
|
|
278
|
+
"reflex.utils": ["utils"],
|
|
279
|
+
"reflex.vars": ["vars", "cached_var", "Var"],
|
|
280
|
+
}
|
|
281
|
+
_MAPPING = {value: key for key, values in _MAPPING.items() for value in values}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _removeprefix(text, prefix):
|
|
285
|
+
return text[text.startswith(prefix) and len(prefix) :]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
__all__ = [_removeprefix(mod, "reflex.") for mod in _MAPPING]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def __getattr__(name: str) -> Type:
|
|
292
|
+
"""Lazy load all modules.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
name: name of the module to load.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
The module or the attribute of the module.
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
AttributeError: If the module or the attribute does not exist.
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
304
|
+
# Check for import of a module that is not in the mapping.
|
|
305
|
+
if name not in _MAPPING:
|
|
306
|
+
# If the name does not start with reflex, add it.
|
|
307
|
+
if not name.startswith("reflex") and name != "__all__":
|
|
308
|
+
name = f"reflex.{name}"
|
|
309
|
+
return importlib.import_module(name)
|
|
310
|
+
|
|
311
|
+
# Import the module.
|
|
312
|
+
module = importlib.import_module(_MAPPING[name])
|
|
313
|
+
|
|
314
|
+
# Get the attribute from the module if the name is not the module itself.
|
|
315
|
+
return (
|
|
316
|
+
getattr(module, name) if name != _MAPPING[name].rsplit(".")[-1] else module
|
|
317
|
+
)
|
|
318
|
+
except ModuleNotFoundError:
|
|
319
|
+
raise AttributeError(f"module 'reflex' has no attribute {name}") from None
|