reflex 0.6.8a2__py3-none-any.whl → 0.7.0__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/custom_components/pyproject.toml.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +7 -7
- reflex/.templates/jinja/web/pages/utils.js.jinja2 +3 -3
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +1 -4
- reflex/.templates/web/utils/state.js +65 -36
- reflex/__init__.py +4 -17
- reflex/__init__.pyi +1 -2
- reflex/app.py +286 -129
- reflex/app_mixins/lifespan.py +9 -9
- reflex/app_mixins/middleware.py +6 -6
- reflex/app_module_for_backend.py +3 -7
- reflex/base.py +7 -7
- reflex/compiler/compiler.py +8 -0
- reflex/compiler/utils.py +57 -18
- reflex/components/base/app_wrap.pyi +16 -16
- reflex/components/base/bare.py +1 -1
- reflex/components/base/body.pyi +16 -16
- reflex/components/base/document.pyi +76 -76
- reflex/components/base/error_boundary.py +2 -1
- reflex/components/base/error_boundary.pyi +19 -22
- reflex/components/base/fragment.pyi +16 -16
- reflex/components/base/head.pyi +31 -31
- reflex/components/base/link.pyi +31 -31
- reflex/components/base/meta.py +2 -2
- reflex/components/base/meta.pyi +61 -61
- reflex/components/base/script.pyi +19 -19
- reflex/components/base/strict_mode.py +10 -0
- reflex/components/base/strict_mode.pyi +57 -0
- reflex/components/component.py +38 -77
- reflex/components/core/banner.py +159 -4
- reflex/components/core/banner.pyi +162 -76
- reflex/components/core/breakpoints.py +3 -1
- reflex/components/core/client_side_routing.py +1 -1
- reflex/components/core/client_side_routing.pyi +32 -32
- reflex/components/core/clipboard.pyi +17 -20
- reflex/components/core/cond.py +9 -10
- reflex/components/core/debounce.py +1 -1
- reflex/components/core/debounce.pyi +17 -17
- reflex/components/core/foreach.py +28 -3
- reflex/components/core/html.py +1 -1
- reflex/components/core/html.pyi +16 -16
- reflex/components/core/match.py +5 -5
- reflex/components/core/sticky.py +134 -0
- reflex/components/core/sticky.pyi +449 -0
- reflex/components/core/upload.py +2 -2
- reflex/components/core/upload.pyi +80 -88
- reflex/components/datadisplay/code.py +5 -14
- reflex/components/datadisplay/code.pyi +31 -31
- reflex/components/datadisplay/dataeditor.py +7 -4
- reflex/components/datadisplay/dataeditor.pyi +40 -54
- reflex/components/datadisplay/logo.py +13 -8
- reflex/components/datadisplay/shiki_code_block.py +14 -9
- reflex/components/datadisplay/shiki_code_block.pyi +46 -46
- reflex/components/dynamic.py +22 -3
- reflex/components/el/constants/reflex.py +1 -1
- reflex/components/el/element.py +1 -1
- reflex/components/el/element.pyi +16 -16
- reflex/components/el/elements/base.pyi +16 -16
- reflex/components/el/elements/forms.py +4 -4
- reflex/components/el/elements/forms.pyi +224 -258
- reflex/components/el/elements/inline.pyi +421 -421
- reflex/components/el/elements/media.pyi +376 -376
- reflex/components/el/elements/metadata.pyi +91 -91
- reflex/components/el/elements/other.pyi +106 -106
- reflex/components/el/elements/scripts.pyi +46 -46
- reflex/components/el/elements/sectioning.pyi +226 -226
- reflex/components/el/elements/tables.pyi +151 -151
- reflex/components/el/elements/typography.pyi +226 -226
- reflex/components/gridjs/datatable.pyi +31 -31
- reflex/components/lucide/icon.py +46 -8
- reflex/components/lucide/icon.pyi +85 -31
- reflex/components/markdown/markdown.py +10 -8
- reflex/components/markdown/markdown.pyi +16 -16
- reflex/components/moment/moment.py +2 -2
- reflex/components/moment/moment.pyi +17 -19
- reflex/components/next/base.pyi +16 -16
- reflex/components/next/image.py +16 -4
- reflex/components/next/image.pyi +22 -20
- reflex/components/next/link.py +1 -1
- reflex/components/next/link.pyi +16 -16
- reflex/components/next/video.pyi +16 -16
- reflex/components/plotly/__init__.py +29 -2
- reflex/components/plotly/plotly.py +240 -5
- reflex/components/plotly/plotly.pyi +799 -44
- reflex/components/props.py +3 -3
- reflex/components/radix/__init__.pyi +1 -1
- reflex/components/radix/primitives/accordion.py +9 -5
- reflex/components/radix/primitives/accordion.pyi +110 -108
- reflex/components/radix/primitives/base.pyi +31 -31
- reflex/components/radix/primitives/drawer.py +5 -2
- reflex/components/radix/primitives/drawer.pyi +179 -187
- reflex/components/radix/primitives/form.pyi +160 -172
- reflex/components/radix/primitives/progress.py +1 -1
- reflex/components/radix/primitives/progress.pyi +76 -76
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/radix/primitives/slider.pyi +78 -82
- reflex/components/radix/themes/base.pyi +121 -121
- reflex/components/radix/themes/color_mode.py +11 -9
- reflex/components/radix/themes/color_mode.pyi +47 -49
- reflex/components/radix/themes/components/alert_dialog.py +3 -0
- reflex/components/radix/themes/components/alert_dialog.pyi +110 -112
- reflex/components/radix/themes/components/aspect_ratio.pyi +16 -16
- reflex/components/radix/themes/components/avatar.pyi +16 -16
- reflex/components/radix/themes/components/badge.pyi +16 -16
- reflex/components/radix/themes/components/button.pyi +16 -16
- reflex/components/radix/themes/components/callout.pyi +76 -76
- reflex/components/radix/themes/components/card.py +1 -1
- reflex/components/radix/themes/components/card.pyi +17 -17
- reflex/components/radix/themes/components/checkbox.pyi +49 -55
- reflex/components/radix/themes/components/checkbox_cards.pyi +31 -31
- reflex/components/radix/themes/components/checkbox_group.pyi +31 -31
- reflex/components/radix/themes/components/context_menu.py +5 -0
- reflex/components/radix/themes/components/context_menu.pyi +149 -155
- reflex/components/radix/themes/components/data_list.pyi +61 -61
- reflex/components/radix/themes/components/dialog.py +3 -0
- reflex/components/radix/themes/components/dialog.pyi +113 -117
- reflex/components/radix/themes/components/dropdown_menu.py +5 -0
- reflex/components/radix/themes/components/dropdown_menu.pyi +133 -137
- reflex/components/radix/themes/components/hover_card.py +3 -0
- reflex/components/radix/themes/components/hover_card.pyi +63 -67
- reflex/components/radix/themes/components/icon_button.py +2 -2
- reflex/components/radix/themes/components/icon_button.pyi +17 -16
- reflex/components/radix/themes/components/inset.pyi +16 -16
- reflex/components/radix/themes/components/popover.py +3 -0
- reflex/components/radix/themes/components/popover.pyi +68 -70
- reflex/components/radix/themes/components/progress.pyi +16 -16
- reflex/components/radix/themes/components/radio.pyi +16 -16
- reflex/components/radix/themes/components/radio_cards.py +2 -0
- reflex/components/radix/themes/components/radio_cards.pyi +32 -34
- reflex/components/radix/themes/components/radio_group.py +1 -1
- reflex/components/radix/themes/components/radio_group.pyi +62 -64
- reflex/components/radix/themes/components/scroll_area.pyi +16 -16
- reflex/components/radix/themes/components/segmented_control.pyi +32 -35
- reflex/components/radix/themes/components/select.py +4 -0
- reflex/components/radix/themes/components/select.pyi +145 -157
- reflex/components/radix/themes/components/separator.pyi +16 -16
- reflex/components/radix/themes/components/skeleton.py +3 -0
- reflex/components/radix/themes/components/skeleton.pyi +16 -16
- reflex/components/radix/themes/components/slider.pyi +22 -28
- reflex/components/radix/themes/components/spinner.pyi +16 -16
- reflex/components/radix/themes/components/switch.pyi +17 -19
- reflex/components/radix/themes/components/table.pyi +106 -106
- reflex/components/radix/themes/components/tabs.py +3 -0
- reflex/components/radix/themes/components/tabs.pyi +78 -82
- reflex/components/radix/themes/components/text_area.py +12 -0
- reflex/components/radix/themes/components/text_area.pyi +21 -33
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/text_field.pyi +52 -80
- reflex/components/radix/themes/components/tooltip.py +6 -1
- reflex/components/radix/themes/components/tooltip.pyi +20 -21
- reflex/components/radix/themes/layout/__init__.pyi +1 -1
- reflex/components/radix/themes/layout/base.pyi +16 -16
- reflex/components/radix/themes/layout/box.pyi +16 -16
- reflex/components/radix/themes/layout/center.pyi +16 -16
- reflex/components/radix/themes/layout/container.pyi +16 -16
- reflex/components/radix/themes/layout/flex.pyi +16 -16
- reflex/components/radix/themes/layout/grid.pyi +16 -16
- reflex/components/radix/themes/layout/list.py +2 -2
- reflex/components/radix/themes/layout/list.pyi +76 -76
- reflex/components/radix/themes/layout/section.pyi +16 -16
- reflex/components/radix/themes/layout/spacer.pyi +16 -16
- reflex/components/radix/themes/layout/stack.py +2 -2
- reflex/components/radix/themes/layout/stack.pyi +46 -46
- reflex/components/radix/themes/typography/blockquote.pyi +16 -16
- reflex/components/radix/themes/typography/code.pyi +16 -16
- reflex/components/radix/themes/typography/heading.pyi +16 -16
- reflex/components/radix/themes/typography/link.py +1 -1
- reflex/components/radix/themes/typography/link.pyi +16 -16
- reflex/components/radix/themes/typography/text.py +2 -2
- reflex/components/radix/themes/typography/text.pyi +106 -106
- reflex/components/react_player/audio.pyi +33 -39
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/react_player/react_player.pyi +32 -38
- reflex/components/react_player/video.pyi +33 -39
- reflex/components/recharts/__init__.py +2 -0
- reflex/components/recharts/__init__.pyi +2 -0
- reflex/components/recharts/cartesian.pyi +282 -282
- reflex/components/recharts/charts.py +15 -15
- reflex/components/recharts/charts.pyi +164 -164
- reflex/components/recharts/general.py +19 -4
- reflex/components/recharts/general.pyi +132 -81
- reflex/components/recharts/polar.py +2 -2
- reflex/components/recharts/polar.pyi +55 -55
- reflex/components/recharts/recharts.py +4 -4
- reflex/components/recharts/recharts.pyi +31 -31
- reflex/components/sonner/toast.py +15 -13
- reflex/components/sonner/toast.pyi +22 -22
- reflex/components/suneditor/editor.py +6 -4
- reflex/components/suneditor/editor.pyi +26 -40
- reflex/components/tags/iter_tag.py +3 -3
- reflex/components/tags/tag.py +25 -3
- reflex/config.py +48 -15
- reflex/constants/__init__.py +1 -0
- reflex/constants/base.py +4 -1
- reflex/constants/compiler.py +5 -2
- reflex/constants/config.py +8 -1
- reflex/constants/installer.py +9 -9
- reflex/constants/style.py +1 -1
- reflex/custom_components/custom_components.py +18 -10
- reflex/event.py +221 -231
- reflex/experimental/__init__.py +19 -11
- reflex/experimental/client_state.py +53 -28
- reflex/experimental/hooks.py +5 -5
- reflex/experimental/layout.py +8 -5
- reflex/experimental/layout.pyi +79 -83
- reflex/experimental/misc.py +3 -3
- reflex/istate/wrappers.py +1 -1
- reflex/middleware/hydrate_middleware.py +2 -2
- reflex/model.py +11 -6
- reflex/page.py +5 -5
- reflex/reflex.py +104 -26
- reflex/route.py +1 -1
- reflex/state.py +358 -401
- reflex/style.py +27 -3
- reflex/testing.py +29 -23
- reflex/utils/build.py +6 -2
- reflex/utils/codespaces.py +1 -4
- reflex/utils/compat.py +6 -5
- reflex/utils/console.py +71 -16
- reflex/utils/exceptions.py +89 -26
- reflex/utils/exec.py +69 -74
- reflex/utils/export.py +6 -1
- reflex/utils/format.py +8 -40
- reflex/utils/imports.py +5 -2
- reflex/utils/lazy_loader.py +7 -1
- reflex/utils/path_ops.py +74 -14
- reflex/utils/prerequisites.py +345 -68
- reflex/utils/processes.py +45 -32
- reflex/utils/pyi_generator.py +39 -33
- reflex/utils/registry.py +4 -4
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +5 -4
- reflex/utils/types.py +42 -18
- reflex/vars/base.py +695 -330
- reflex/vars/datetime.py +6 -7
- reflex/vars/dep_tracking.py +344 -0
- reflex/vars/function.py +11 -5
- reflex/vars/number.py +31 -43
- reflex/vars/object.py +74 -64
- reflex/vars/sequence.py +79 -67
- {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/METADATA +7 -8
- reflex-0.7.0.dist-info/RECORD +401 -0
- {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/WHEEL +1 -1
- reflex/experimental/assets.py +0 -37
- reflex-0.6.8a2.dist-info/RECORD +0 -397
- {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/LICENSE +0 -0
- {reflex-0.6.8a2.dist-info → reflex-0.7.0.dist-info}/entry_points.txt +0 -0
reflex/utils/prerequisites.py
CHANGED
|
@@ -7,6 +7,7 @@ import dataclasses
|
|
|
7
7
|
import functools
|
|
8
8
|
import importlib
|
|
9
9
|
import importlib.metadata
|
|
10
|
+
import importlib.util
|
|
10
11
|
import json
|
|
11
12
|
import os
|
|
12
13
|
import platform
|
|
@@ -17,11 +18,12 @@ import stat
|
|
|
17
18
|
import sys
|
|
18
19
|
import tempfile
|
|
19
20
|
import time
|
|
21
|
+
import typing
|
|
20
22
|
import zipfile
|
|
21
23
|
from datetime import datetime
|
|
22
24
|
from pathlib import Path
|
|
23
25
|
from types import ModuleType
|
|
24
|
-
from typing import Callable, List, Optional
|
|
26
|
+
from typing import Any, Callable, List, NamedTuple, Optional
|
|
25
27
|
|
|
26
28
|
import httpx
|
|
27
29
|
import typer
|
|
@@ -36,15 +38,25 @@ from reflex.compiler import templates
|
|
|
36
38
|
from reflex.config import Config, environment, get_config
|
|
37
39
|
from reflex.utils import console, net, path_ops, processes, redir
|
|
38
40
|
from reflex.utils.exceptions import (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
GeneratedCodeHasNoFunctionDefsError,
|
|
42
|
+
SystemPackageMissingError,
|
|
41
43
|
)
|
|
42
44
|
from reflex.utils.format import format_library_name
|
|
43
45
|
from reflex.utils.registry import _get_npm_registry
|
|
44
46
|
|
|
47
|
+
if typing.TYPE_CHECKING:
|
|
48
|
+
from reflex.app import App
|
|
49
|
+
|
|
45
50
|
CURRENTLY_INSTALLING_NODE = False
|
|
46
51
|
|
|
47
52
|
|
|
53
|
+
class AppInfo(NamedTuple):
|
|
54
|
+
"""A tuple containing the app instance and module."""
|
|
55
|
+
|
|
56
|
+
app: App
|
|
57
|
+
module: ModuleType
|
|
58
|
+
|
|
59
|
+
|
|
48
60
|
@dataclasses.dataclass(frozen=True)
|
|
49
61
|
class Template:
|
|
50
62
|
"""A template for a Reflex app."""
|
|
@@ -52,7 +64,7 @@ class Template:
|
|
|
52
64
|
name: str
|
|
53
65
|
description: str
|
|
54
66
|
code_url: str
|
|
55
|
-
demo_url: str
|
|
67
|
+
demo_url: str | None = None
|
|
56
68
|
|
|
57
69
|
|
|
58
70
|
@dataclasses.dataclass(frozen=True)
|
|
@@ -75,16 +87,15 @@ def get_web_dir() -> Path:
|
|
|
75
87
|
return environment.REFLEX_WEB_WORKDIR.get()
|
|
76
88
|
|
|
77
89
|
|
|
78
|
-
def
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
90
|
+
def get_states_dir() -> Path:
|
|
91
|
+
"""Get the working directory for the states.
|
|
92
|
+
|
|
93
|
+
Can be overridden with REFLEX_STATES_WORKDIR.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The working directory.
|
|
97
|
+
"""
|
|
98
|
+
return environment.REFLEX_STATES_WORKDIR.get()
|
|
88
99
|
|
|
89
100
|
|
|
90
101
|
def check_latest_package_version(package_name: str):
|
|
@@ -109,8 +120,6 @@ def check_latest_package_version(package_name: str):
|
|
|
109
120
|
console.warn(
|
|
110
121
|
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
|
|
111
122
|
)
|
|
112
|
-
# Check for deprecated python versions
|
|
113
|
-
_python_version_check()
|
|
114
123
|
except Exception:
|
|
115
124
|
pass
|
|
116
125
|
|
|
@@ -167,7 +176,7 @@ def get_node_version() -> version.Version | None:
|
|
|
167
176
|
try:
|
|
168
177
|
result = processes.new_process([node_path, "-v"], run=True)
|
|
169
178
|
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
|
170
|
-
return version.parse(result.stdout) #
|
|
179
|
+
return version.parse(result.stdout) # pyright: ignore [reportArgumentType]
|
|
171
180
|
except (FileNotFoundError, TypeError):
|
|
172
181
|
return None
|
|
173
182
|
|
|
@@ -180,7 +189,7 @@ def get_fnm_version() -> version.Version | None:
|
|
|
180
189
|
"""
|
|
181
190
|
try:
|
|
182
191
|
result = processes.new_process([constants.Fnm.EXE, "--version"], run=True)
|
|
183
|
-
return version.parse(result.stdout.split(" ")[1]) #
|
|
192
|
+
return version.parse(result.stdout.split(" ")[1]) # pyright: ignore [reportOptionalMemberAccess, reportAttributeAccessIssue]
|
|
184
193
|
except (FileNotFoundError, TypeError):
|
|
185
194
|
return None
|
|
186
195
|
except version.InvalidVersion as e:
|
|
@@ -196,10 +205,13 @@ def get_bun_version() -> version.Version | None:
|
|
|
196
205
|
Returns:
|
|
197
206
|
The version of bun.
|
|
198
207
|
"""
|
|
208
|
+
bun_path = path_ops.get_bun_path()
|
|
209
|
+
if bun_path is None:
|
|
210
|
+
return None
|
|
199
211
|
try:
|
|
200
212
|
# Run the bun -v command and capture the output
|
|
201
|
-
result = processes.new_process([str(
|
|
202
|
-
return version.parse(result.stdout) #
|
|
213
|
+
result = processes.new_process([str(bun_path), "-v"], run=True)
|
|
214
|
+
return version.parse(str(result.stdout)) # pyright: ignore [reportArgumentType]
|
|
203
215
|
except FileNotFoundError:
|
|
204
216
|
return None
|
|
205
217
|
except version.InvalidVersion as e:
|
|
@@ -243,7 +255,7 @@ def get_package_manager(on_failure_return_none: bool = False) -> str | None:
|
|
|
243
255
|
"""
|
|
244
256
|
npm_path = path_ops.get_npm_path()
|
|
245
257
|
if npm_path is not None:
|
|
246
|
-
return str(
|
|
258
|
+
return str(npm_path)
|
|
247
259
|
if on_failure_return_none:
|
|
248
260
|
return None
|
|
249
261
|
raise FileNotFoundError("NPM not found. You may need to run `reflex init`.")
|
|
@@ -267,6 +279,22 @@ def windows_npm_escape_hatch() -> bool:
|
|
|
267
279
|
return environment.REFLEX_USE_NPM.get()
|
|
268
280
|
|
|
269
281
|
|
|
282
|
+
def _check_app_name(config: Config):
|
|
283
|
+
"""Check if the app name is set in the config.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
config: The config object.
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
RuntimeError: If the app name is not set in the config.
|
|
290
|
+
"""
|
|
291
|
+
if not config.app_name:
|
|
292
|
+
raise RuntimeError(
|
|
293
|
+
"Cannot get the app module because `app_name` is not set in rxconfig! "
|
|
294
|
+
"If this error occurs in a reflex test case, ensure that `get_app` is mocked."
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
270
298
|
def get_app(reload: bool = False) -> ModuleType:
|
|
271
299
|
"""Get the app module based on the default config.
|
|
272
300
|
|
|
@@ -277,22 +305,23 @@ def get_app(reload: bool = False) -> ModuleType:
|
|
|
277
305
|
The app based on the default config.
|
|
278
306
|
|
|
279
307
|
Raises:
|
|
280
|
-
|
|
308
|
+
Exception: If an error occurs while getting the app module.
|
|
281
309
|
"""
|
|
282
310
|
from reflex.utils import telemetry
|
|
283
311
|
|
|
284
312
|
try:
|
|
285
313
|
environment.RELOAD_CONFIG.set(reload)
|
|
286
314
|
config = get_config()
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
"If this error occurs in a reflex test case, ensure that `get_app` is mocked."
|
|
291
|
-
)
|
|
315
|
+
|
|
316
|
+
_check_app_name(config)
|
|
317
|
+
|
|
292
318
|
module = config.module
|
|
293
319
|
sys.path.insert(0, str(Path.cwd()))
|
|
294
|
-
app =
|
|
295
|
-
|
|
320
|
+
app = (
|
|
321
|
+
__import__(module, fromlist=(constants.CompileVars.APP,))
|
|
322
|
+
if not config.app_module
|
|
323
|
+
else config.app_module
|
|
324
|
+
)
|
|
296
325
|
if reload:
|
|
297
326
|
from reflex.state import reload_state_module
|
|
298
327
|
|
|
@@ -301,11 +330,34 @@ def get_app(reload: bool = False) -> ModuleType:
|
|
|
301
330
|
|
|
302
331
|
# Reload the app module.
|
|
303
332
|
importlib.reload(app)
|
|
304
|
-
|
|
305
|
-
return app
|
|
306
333
|
except Exception as ex:
|
|
307
334
|
telemetry.send_error(ex, context="frontend")
|
|
308
335
|
raise
|
|
336
|
+
else:
|
|
337
|
+
return app
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def get_and_validate_app(reload: bool = False) -> AppInfo:
|
|
341
|
+
"""Get the app instance based on the default config and validate it.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
reload: Re-import the app module from disk
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
The app instance and the app module.
|
|
348
|
+
|
|
349
|
+
Raises:
|
|
350
|
+
RuntimeError: If the app instance is not an instance of rx.App.
|
|
351
|
+
"""
|
|
352
|
+
from reflex.app import App
|
|
353
|
+
|
|
354
|
+
app_module = get_app(reload=reload)
|
|
355
|
+
app = getattr(app_module, constants.CompileVars.APP)
|
|
356
|
+
if not isinstance(app, App):
|
|
357
|
+
raise RuntimeError(
|
|
358
|
+
"The app instance in the specified app_module_import in rxconfig must be an instance of rx.App."
|
|
359
|
+
)
|
|
360
|
+
return AppInfo(app=app, module=app_module)
|
|
309
361
|
|
|
310
362
|
|
|
311
363
|
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
@@ -318,8 +370,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
|
318
370
|
Returns:
|
|
319
371
|
The compiled app based on the default config.
|
|
320
372
|
"""
|
|
321
|
-
app_module =
|
|
322
|
-
app = getattr(app_module, constants.CompileVars.APP)
|
|
373
|
+
app, app_module = get_and_validate_app(reload=reload)
|
|
323
374
|
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
|
324
375
|
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
|
325
376
|
app._apply_decorated_pages()
|
|
@@ -427,6 +478,167 @@ def validate_app_name(app_name: str | None = None) -> str:
|
|
|
427
478
|
return app_name
|
|
428
479
|
|
|
429
480
|
|
|
481
|
+
def rename_path_up_tree(full_path: str | Path, old_name: str, new_name: str) -> Path:
|
|
482
|
+
"""Rename all instances of `old_name` in the path (file and directories) to `new_name`.
|
|
483
|
+
The renaming stops when we reach the directory containing `rxconfig.py`.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
full_path: The full path to start renaming from.
|
|
487
|
+
old_name: The name to be replaced.
|
|
488
|
+
new_name: The replacement name.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
The updated path after renaming.
|
|
492
|
+
"""
|
|
493
|
+
current_path = Path(full_path)
|
|
494
|
+
new_path = None
|
|
495
|
+
|
|
496
|
+
while True:
|
|
497
|
+
directory, base = current_path.parent, current_path.name
|
|
498
|
+
# Stop renaming when we reach the root dir (which contains rxconfig.py)
|
|
499
|
+
if current_path.is_dir() and (current_path / "rxconfig.py").exists():
|
|
500
|
+
new_path = current_path
|
|
501
|
+
break
|
|
502
|
+
|
|
503
|
+
if old_name == base.removesuffix(constants.Ext.PY):
|
|
504
|
+
new_base = base.replace(old_name, new_name)
|
|
505
|
+
new_path = directory / new_base
|
|
506
|
+
current_path.rename(new_path)
|
|
507
|
+
console.debug(f"Renamed {current_path} -> {new_path}")
|
|
508
|
+
current_path = new_path
|
|
509
|
+
else:
|
|
510
|
+
new_path = current_path
|
|
511
|
+
|
|
512
|
+
# Move up the directory tree
|
|
513
|
+
current_path = directory
|
|
514
|
+
|
|
515
|
+
return new_path
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def rename_app(new_app_name: str, loglevel: constants.LogLevel):
|
|
519
|
+
"""Rename the app directory.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
new_app_name: The new name for the app.
|
|
523
|
+
loglevel: The log level to use.
|
|
524
|
+
|
|
525
|
+
Raises:
|
|
526
|
+
Exit: If the command is not ran in the root dir or the app module cannot be imported.
|
|
527
|
+
"""
|
|
528
|
+
# Set the log level.
|
|
529
|
+
console.set_log_level(loglevel)
|
|
530
|
+
|
|
531
|
+
if not constants.Config.FILE.exists():
|
|
532
|
+
console.error(
|
|
533
|
+
"No rxconfig.py found. Make sure you are in the root directory of your app."
|
|
534
|
+
)
|
|
535
|
+
raise typer.Exit(1)
|
|
536
|
+
|
|
537
|
+
sys.path.insert(0, str(Path.cwd()))
|
|
538
|
+
|
|
539
|
+
config = get_config()
|
|
540
|
+
module_path = importlib.util.find_spec(config.module)
|
|
541
|
+
if module_path is None:
|
|
542
|
+
console.error(f"Could not find module {config.module}.")
|
|
543
|
+
raise typer.Exit(1)
|
|
544
|
+
|
|
545
|
+
if not module_path.origin:
|
|
546
|
+
console.error(f"Could not find origin for module {config.module}.")
|
|
547
|
+
raise typer.Exit(1)
|
|
548
|
+
console.info(f"Renaming app directory to {new_app_name}.")
|
|
549
|
+
process_directory(
|
|
550
|
+
Path.cwd(),
|
|
551
|
+
config.app_name,
|
|
552
|
+
new_app_name,
|
|
553
|
+
exclude_dirs=[constants.Dirs.WEB, constants.Dirs.APP_ASSETS],
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
rename_path_up_tree(Path(module_path.origin), config.app_name, new_app_name)
|
|
557
|
+
|
|
558
|
+
console.success(f"App directory renamed to [bold]{new_app_name}[/bold].")
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def rename_imports_and_app_name(file_path: str | Path, old_name: str, new_name: str):
|
|
562
|
+
"""Rename imports the file using string replacement as well as app_name in rxconfig.py.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
file_path: The file to process.
|
|
566
|
+
old_name: The old name to replace.
|
|
567
|
+
new_name: The new name to use.
|
|
568
|
+
"""
|
|
569
|
+
file_path = Path(file_path)
|
|
570
|
+
content = file_path.read_text()
|
|
571
|
+
|
|
572
|
+
# Replace `from old_name.` or `from old_name` with `from new_name`
|
|
573
|
+
content = re.sub(
|
|
574
|
+
rf"\bfrom {re.escape(old_name)}(\b|\.|\s)",
|
|
575
|
+
lambda match: f"from {new_name}{match.group(1)}",
|
|
576
|
+
content,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Replace `import old_name` with `import new_name`
|
|
580
|
+
content = re.sub(
|
|
581
|
+
rf"\bimport {re.escape(old_name)}\b",
|
|
582
|
+
f"import {new_name}",
|
|
583
|
+
content,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# Replace `app_name="old_name"` in rx.Config
|
|
587
|
+
content = re.sub(
|
|
588
|
+
rf'\bapp_name\s*=\s*["\']{re.escape(old_name)}["\']',
|
|
589
|
+
f'app_name="{new_name}"',
|
|
590
|
+
content,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# Replace positional argument `"old_name"` in rx.Config
|
|
594
|
+
content = re.sub(
|
|
595
|
+
rf'\brx\.Config\(\s*["\']{re.escape(old_name)}["\']',
|
|
596
|
+
f'rx.Config("{new_name}"',
|
|
597
|
+
content,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
file_path.write_text(content)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def process_directory(
|
|
604
|
+
directory: str | Path,
|
|
605
|
+
old_name: str,
|
|
606
|
+
new_name: str,
|
|
607
|
+
exclude_dirs: list | None = None,
|
|
608
|
+
extensions: list | None = None,
|
|
609
|
+
):
|
|
610
|
+
"""Process files with specified extensions in a directory, excluding specified directories.
|
|
611
|
+
|
|
612
|
+
Args:
|
|
613
|
+
directory: The root directory to process.
|
|
614
|
+
old_name: The old name to replace.
|
|
615
|
+
new_name: The new name to use.
|
|
616
|
+
exclude_dirs: List of directory names to exclude. Defaults to None.
|
|
617
|
+
extensions: List of file extensions to process.
|
|
618
|
+
"""
|
|
619
|
+
exclude_dirs = exclude_dirs or []
|
|
620
|
+
extensions = extensions or [
|
|
621
|
+
constants.Ext.PY,
|
|
622
|
+
constants.Ext.MD,
|
|
623
|
+
] # include .md files, typically used in reflex-web.
|
|
624
|
+
extensions_set = {ext.lstrip(".") for ext in extensions}
|
|
625
|
+
directory = Path(directory)
|
|
626
|
+
|
|
627
|
+
root_exclude_dirs = {directory / exclude_dir for exclude_dir in exclude_dirs}
|
|
628
|
+
|
|
629
|
+
files = (
|
|
630
|
+
p.resolve()
|
|
631
|
+
for p in directory.glob("**/*")
|
|
632
|
+
if p.is_file() and p.suffix.lstrip(".") in extensions_set
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
for file_path in files:
|
|
636
|
+
if not any(
|
|
637
|
+
file_path.is_relative_to(exclude_dir) for exclude_dir in root_exclude_dirs
|
|
638
|
+
):
|
|
639
|
+
rename_imports_and_app_name(file_path, old_name, new_name)
|
|
640
|
+
|
|
641
|
+
|
|
430
642
|
def create_config(app_name: str):
|
|
431
643
|
"""Create a new rxconfig file.
|
|
432
644
|
|
|
@@ -610,10 +822,14 @@ def initialize_web_directory():
|
|
|
610
822
|
init_reflex_json(project_hash=project_hash)
|
|
611
823
|
|
|
612
824
|
|
|
825
|
+
def _turbopack_flag() -> str:
|
|
826
|
+
return " --turbopack" if environment.REFLEX_USE_TURBOPACK.get() else ""
|
|
827
|
+
|
|
828
|
+
|
|
613
829
|
def _compile_package_json():
|
|
614
830
|
return templates.PACKAGE_JSON.render(
|
|
615
831
|
scripts={
|
|
616
|
-
"dev": constants.PackageJson.Commands.DEV,
|
|
832
|
+
"dev": constants.PackageJson.Commands.DEV + _turbopack_flag(),
|
|
617
833
|
"export": constants.PackageJson.Commands.EXPORT,
|
|
618
834
|
"export_sitemap": constants.PackageJson.Commands.EXPORT_SITEMAP,
|
|
619
835
|
"prod": constants.PackageJson.Commands.PROD,
|
|
@@ -668,7 +884,9 @@ def init_reflex_json(project_hash: int | None):
|
|
|
668
884
|
path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)
|
|
669
885
|
|
|
670
886
|
|
|
671
|
-
def update_next_config(
|
|
887
|
+
def update_next_config(
|
|
888
|
+
export: bool = False, transpile_packages: Optional[List[str]] = None
|
|
889
|
+
):
|
|
672
890
|
"""Update Next.js config from Reflex config.
|
|
673
891
|
|
|
674
892
|
Args:
|
|
@@ -694,7 +912,6 @@ def _update_next_config(
|
|
|
694
912
|
next_config = {
|
|
695
913
|
"basePath": config.frontend_path or "",
|
|
696
914
|
"compress": config.next_compression,
|
|
697
|
-
"reactStrictMode": config.react_strict_mode,
|
|
698
915
|
"trailingSlash": True,
|
|
699
916
|
"staticPageGenerationTimeout": config.static_page_generation_timeout,
|
|
700
917
|
}
|
|
@@ -831,7 +1048,11 @@ def install_node():
|
|
|
831
1048
|
|
|
832
1049
|
|
|
833
1050
|
def install_bun():
|
|
834
|
-
"""Install bun onto the user's system.
|
|
1051
|
+
"""Install bun onto the user's system.
|
|
1052
|
+
|
|
1053
|
+
Raises:
|
|
1054
|
+
SystemPackageMissingError: If "unzip" is missing.
|
|
1055
|
+
"""
|
|
835
1056
|
win_supported = is_windows_bun_supported()
|
|
836
1057
|
one_drive_in_path = windows_check_onedrive_in_path()
|
|
837
1058
|
if constants.IS_WINDOWS and (not win_supported or one_drive_in_path):
|
|
@@ -845,9 +1066,7 @@ def install_bun():
|
|
|
845
1066
|
)
|
|
846
1067
|
|
|
847
1068
|
# Skip if bun is already installed.
|
|
848
|
-
if
|
|
849
|
-
constants.Bun.VERSION
|
|
850
|
-
):
|
|
1069
|
+
if get_bun_version() == version.parse(constants.Bun.VERSION):
|
|
851
1070
|
console.debug("Skipping bun installation as it is already installed.")
|
|
852
1071
|
return
|
|
853
1072
|
|
|
@@ -868,15 +1087,15 @@ def install_bun():
|
|
|
868
1087
|
show_logs=console.is_debug(),
|
|
869
1088
|
)
|
|
870
1089
|
else:
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
raise_system_package_missing_error("unzip")
|
|
1090
|
+
if path_ops.which("unzip") is None:
|
|
1091
|
+
raise SystemPackageMissingError("unzip")
|
|
874
1092
|
|
|
875
1093
|
# Run the bun install script.
|
|
876
1094
|
download_and_run(
|
|
877
1095
|
constants.Bun.INSTALL_URL,
|
|
878
1096
|
f"bun-v{constants.Bun.VERSION}",
|
|
879
1097
|
BUN_INSTALL=str(constants.Bun.ROOT_PATH),
|
|
1098
|
+
BUN_VERSION=str(constants.Bun.VERSION),
|
|
880
1099
|
)
|
|
881
1100
|
|
|
882
1101
|
|
|
@@ -910,7 +1129,7 @@ def cached_procedure(cache_file: str, payload_fn: Callable[..., str]):
|
|
|
910
1129
|
The decorated function.
|
|
911
1130
|
"""
|
|
912
1131
|
|
|
913
|
-
def _inner_decorator(func):
|
|
1132
|
+
def _inner_decorator(func: Callable):
|
|
914
1133
|
def _inner(*args, **kwargs):
|
|
915
1134
|
payload = _read_cached_procedure_file(cache_file)
|
|
916
1135
|
new_payload = payload_fn(*args, **kwargs)
|
|
@@ -970,7 +1189,7 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
970
1189
|
)
|
|
971
1190
|
|
|
972
1191
|
processes.run_process_with_fallback(
|
|
973
|
-
[install_package_manager, "install"],
|
|
1192
|
+
[install_package_manager, "install"],
|
|
974
1193
|
fallback=fallback_command,
|
|
975
1194
|
analytics_enabled=True,
|
|
976
1195
|
show_status_message="Installing base frontend packages",
|
|
@@ -1006,6 +1225,21 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1006
1225
|
)
|
|
1007
1226
|
|
|
1008
1227
|
|
|
1228
|
+
def check_running_mode(frontend: bool, backend: bool) -> tuple[bool, bool]:
|
|
1229
|
+
"""Check if the app is running in frontend or backend mode.
|
|
1230
|
+
|
|
1231
|
+
Args:
|
|
1232
|
+
frontend: Whether to run the frontend of the app.
|
|
1233
|
+
backend: Whether to run the backend of the app.
|
|
1234
|
+
|
|
1235
|
+
Returns:
|
|
1236
|
+
The running modes.
|
|
1237
|
+
"""
|
|
1238
|
+
if not frontend and not backend:
|
|
1239
|
+
return True, True
|
|
1240
|
+
return frontend, backend
|
|
1241
|
+
|
|
1242
|
+
|
|
1009
1243
|
def needs_reinit(frontend: bool = True) -> bool:
|
|
1010
1244
|
"""Check if an app needs to be reinitialized.
|
|
1011
1245
|
|
|
@@ -1072,15 +1306,15 @@ def validate_bun():
|
|
|
1072
1306
|
Raises:
|
|
1073
1307
|
Exit: If custom specified bun does not exist or does not meet requirements.
|
|
1074
1308
|
"""
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
bun_path
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
if bun_path
|
|
1309
|
+
bun_path = path_ops.get_bun_path()
|
|
1310
|
+
|
|
1311
|
+
if bun_path is None:
|
|
1312
|
+
return
|
|
1313
|
+
|
|
1314
|
+
if not path_ops.samefile(bun_path, constants.Bun.DEFAULT_PATH):
|
|
1081
1315
|
console.info(f"Using custom Bun path: {bun_path}")
|
|
1082
1316
|
bun_version = get_bun_version()
|
|
1083
|
-
if
|
|
1317
|
+
if bun_version is None:
|
|
1084
1318
|
console.error(
|
|
1085
1319
|
"Failed to obtain bun version. Make sure the specified bun path in your config is correct."
|
|
1086
1320
|
)
|
|
@@ -1095,7 +1329,7 @@ def validate_bun():
|
|
|
1095
1329
|
raise typer.Exit(1)
|
|
1096
1330
|
|
|
1097
1331
|
|
|
1098
|
-
def validate_frontend_dependencies(init=True):
|
|
1332
|
+
def validate_frontend_dependencies(init: bool = True):
|
|
1099
1333
|
"""Validate frontend dependencies to ensure they meet requirements.
|
|
1100
1334
|
|
|
1101
1335
|
Args:
|
|
@@ -1149,11 +1383,12 @@ def ensure_reflex_installation_id() -> Optional[int]:
|
|
|
1149
1383
|
if installation_id is None:
|
|
1150
1384
|
installation_id = random.getrandbits(128)
|
|
1151
1385
|
installation_id_file.write_text(str(installation_id))
|
|
1152
|
-
# If we get here, installation_id is definitely set
|
|
1153
|
-
return installation_id
|
|
1154
1386
|
except Exception as e:
|
|
1155
1387
|
console.debug(f"Failed to ensure reflex installation id: {e}")
|
|
1156
1388
|
return None
|
|
1389
|
+
else:
|
|
1390
|
+
# If we get here, installation_id is definitely set
|
|
1391
|
+
return installation_id
|
|
1157
1392
|
|
|
1158
1393
|
|
|
1159
1394
|
def initialize_reflex_user_directory():
|
|
@@ -1244,7 +1479,7 @@ def prompt_for_template_options(templates: list[Template]) -> str:
|
|
|
1244
1479
|
# Show the user the URLs of each template to preview.
|
|
1245
1480
|
console.print("\nGet started with a template:")
|
|
1246
1481
|
|
|
1247
|
-
def format_demo_url_str(url: str) -> str:
|
|
1482
|
+
def format_demo_url_str(url: str | None) -> str:
|
|
1248
1483
|
return f" ({url})" if url else ""
|
|
1249
1484
|
|
|
1250
1485
|
# Prompt the user to select a template.
|
|
@@ -1265,7 +1500,7 @@ def prompt_for_template_options(templates: list[Template]) -> str:
|
|
|
1265
1500
|
)
|
|
1266
1501
|
|
|
1267
1502
|
# Return the template.
|
|
1268
|
-
return templates[int(template)].name
|
|
1503
|
+
return templates[int(template)].name # pyright: ignore [reportArgumentType]
|
|
1269
1504
|
|
|
1270
1505
|
|
|
1271
1506
|
def fetch_app_templates(version: str) -> dict[str, Template]:
|
|
@@ -1367,19 +1602,22 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1367
1602
|
except OSError as ose:
|
|
1368
1603
|
console.error(f"Failed to create temp directory for extracting zip: {ose}")
|
|
1369
1604
|
raise typer.Exit(1) from ose
|
|
1605
|
+
|
|
1370
1606
|
try:
|
|
1371
1607
|
zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
|
|
1372
1608
|
# The zip file downloaded from github looks like:
|
|
1373
1609
|
# repo-name-branch/**/*, so we need to remove the top level directory.
|
|
1374
|
-
if len(subdirs := os.listdir(unzip_dir)) != 1:
|
|
1375
|
-
console.error(f"Expected one directory in the zip, found {subdirs}")
|
|
1376
|
-
raise typer.Exit(1)
|
|
1377
|
-
template_dir = unzip_dir / subdirs[0]
|
|
1378
|
-
console.debug(f"Template folder is located at {template_dir}")
|
|
1379
1610
|
except Exception as uze:
|
|
1380
1611
|
console.error(f"Failed to unzip the template: {uze}")
|
|
1381
1612
|
raise typer.Exit(1) from uze
|
|
1382
1613
|
|
|
1614
|
+
if len(subdirs := os.listdir(unzip_dir)) != 1:
|
|
1615
|
+
console.error(f"Expected one directory in the zip, found {subdirs}")
|
|
1616
|
+
raise typer.Exit(1)
|
|
1617
|
+
|
|
1618
|
+
template_dir = unzip_dir / subdirs[0]
|
|
1619
|
+
console.debug(f"Template folder is located at {template_dir}")
|
|
1620
|
+
|
|
1383
1621
|
# Move the rxconfig file here first.
|
|
1384
1622
|
path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE)
|
|
1385
1623
|
new_config = get_config(reload=True)
|
|
@@ -1415,7 +1653,9 @@ def initialize_default_app(app_name: str):
|
|
|
1415
1653
|
initialize_app_directory(app_name)
|
|
1416
1654
|
|
|
1417
1655
|
|
|
1418
|
-
def validate_and_create_app_using_remote_template(
|
|
1656
|
+
def validate_and_create_app_using_remote_template(
|
|
1657
|
+
app_name: str, template: str, templates: dict[str, Template]
|
|
1658
|
+
):
|
|
1419
1659
|
"""Validate and create an app using a remote template.
|
|
1420
1660
|
|
|
1421
1661
|
Args:
|
|
@@ -1605,7 +1845,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
|
|
|
1605
1845
|
generation_hash: The generation hash from reflex.build.
|
|
1606
1846
|
|
|
1607
1847
|
Raises:
|
|
1608
|
-
|
|
1848
|
+
GeneratedCodeHasNoFunctionDefsError: If the fetched code has no function definitions
|
|
1609
1849
|
(the refactored reflex code is expected to have at least one root function defined).
|
|
1610
1850
|
"""
|
|
1611
1851
|
# Download the reflex code for the generation.
|
|
@@ -1622,17 +1862,17 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
|
|
|
1622
1862
|
# Determine the name of the last function, which renders the generated code.
|
|
1623
1863
|
defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
|
|
1624
1864
|
if not defined_funcs:
|
|
1625
|
-
raise
|
|
1865
|
+
raise GeneratedCodeHasNoFunctionDefsError(
|
|
1626
1866
|
f"No function definitions found in generated code from {url!r}."
|
|
1627
1867
|
)
|
|
1628
1868
|
render_func_name = defined_funcs[-1]
|
|
1629
1869
|
|
|
1630
|
-
def replace_content(_match):
|
|
1870
|
+
def replace_content(_match: re.Match) -> str:
|
|
1631
1871
|
return "\n".join(
|
|
1632
1872
|
[
|
|
1633
1873
|
resp.text,
|
|
1634
1874
|
"",
|
|
1635
|
-
"
|
|
1875
|
+
"def index() -> rx.Component:",
|
|
1636
1876
|
f" return {render_func_name}()",
|
|
1637
1877
|
"",
|
|
1638
1878
|
"",
|
|
@@ -1657,7 +1897,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
|
|
|
1657
1897
|
main_module_path.write_text(main_module_code)
|
|
1658
1898
|
|
|
1659
1899
|
|
|
1660
|
-
def format_address_width(address_width) -> int | None:
|
|
1900
|
+
def format_address_width(address_width: str | None) -> int | None:
|
|
1661
1901
|
"""Cast address width to an int.
|
|
1662
1902
|
|
|
1663
1903
|
Args:
|
|
@@ -1756,3 +1996,40 @@ def is_generation_hash(template: str) -> bool:
|
|
|
1756
1996
|
True if the template is composed of 32 or more hex characters.
|
|
1757
1997
|
"""
|
|
1758
1998
|
return re.match(r"^[0-9a-f]{32,}$", template) is not None
|
|
1999
|
+
|
|
2000
|
+
|
|
2001
|
+
def check_config_option_in_tier(
|
|
2002
|
+
option_name: str,
|
|
2003
|
+
allowed_tiers: list[str],
|
|
2004
|
+
fallback_value: Any,
|
|
2005
|
+
help_link: str | None = None,
|
|
2006
|
+
):
|
|
2007
|
+
"""Check if a config option is allowed for the authenticated user's current tier.
|
|
2008
|
+
|
|
2009
|
+
Args:
|
|
2010
|
+
option_name: The name of the option to check.
|
|
2011
|
+
allowed_tiers: The tiers that are allowed to use the option.
|
|
2012
|
+
fallback_value: The fallback value if the option is not allowed.
|
|
2013
|
+
help_link: The help link to show to a user that is authenticated.
|
|
2014
|
+
"""
|
|
2015
|
+
from reflex_cli.v2.utils import hosting
|
|
2016
|
+
|
|
2017
|
+
config = get_config()
|
|
2018
|
+
authenticated_token = hosting.authenticated_token()
|
|
2019
|
+
if not authenticated_token[0]:
|
|
2020
|
+
the_remedy = (
|
|
2021
|
+
"You are currently logged out. Run `reflex login` to access this option."
|
|
2022
|
+
)
|
|
2023
|
+
current_tier = "anonymous"
|
|
2024
|
+
else:
|
|
2025
|
+
current_tier = authenticated_token[1].get("tier", "").lower()
|
|
2026
|
+
the_remedy = (
|
|
2027
|
+
f"Your current subscription tier is `{current_tier}`. "
|
|
2028
|
+
f"Please upgrade to {allowed_tiers} to access this option. "
|
|
2029
|
+
)
|
|
2030
|
+
if help_link:
|
|
2031
|
+
the_remedy += f"See {help_link} for more information."
|
|
2032
|
+
if current_tier not in allowed_tiers:
|
|
2033
|
+
console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
|
|
2034
|
+
setattr(config, option_name, fallback_value)
|
|
2035
|
+
config._set_persistent(**{option_name: fallback_value})
|