reflex 0.7.14a6__py3-none-any.whl → 0.8.0a2__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/app/rxconfig.py.jinja2 +4 -1
- reflex/.templates/jinja/web/package.json.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +16 -10
- reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
- reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
- reflex/.templates/web/app/entry.client.js +8 -0
- reflex/.templates/web/app/routes.js +10 -0
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
- reflex/.templates/web/postcss.config.js +1 -1
- reflex/.templates/web/react-router.config.js +6 -0
- reflex/.templates/web/utils/client_side_routing.js +21 -19
- reflex/.templates/web/utils/react-theme.js +92 -0
- reflex/.templates/web/utils/state.js +160 -67
- reflex/.templates/web/vite.config.js +32 -0
- reflex/__init__.py +1 -6
- reflex/__init__.pyi +0 -4
- reflex/app.py +53 -116
- reflex/base.py +1 -87
- reflex/compiler/compiler.py +41 -8
- reflex/compiler/templates.py +3 -3
- reflex/compiler/utils.py +73 -33
- reflex/components/__init__.py +0 -2
- reflex/components/__init__.pyi +0 -3
- reflex/components/base/__init__.py +1 -5
- reflex/components/base/__init__.pyi +4 -6
- reflex/components/base/app_wrap.pyi +5 -4
- reflex/components/base/body.pyi +5 -4
- reflex/components/base/document.py +18 -14
- reflex/components/base/document.pyi +83 -27
- reflex/components/base/error_boundary.pyi +5 -4
- reflex/components/base/fragment.pyi +5 -4
- reflex/components/base/link.pyi +9 -7
- reflex/components/base/meta.pyi +17 -13
- reflex/components/base/script.py +60 -58
- reflex/components/base/script.pyi +246 -31
- reflex/components/base/strict_mode.pyi +5 -4
- reflex/components/component.py +146 -217
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +1 -0
- reflex/components/core/auto_scroll.pyi +5 -4
- reflex/components/core/banner.pyi +25 -19
- reflex/components/core/client_side_routing.py +7 -6
- reflex/components/core/client_side_routing.pyi +6 -56
- reflex/components/core/clipboard.pyi +5 -4
- reflex/components/core/debounce.py +1 -0
- reflex/components/core/debounce.pyi +5 -4
- reflex/components/core/foreach.py +3 -2
- reflex/components/core/helmet.py +14 -0
- reflex/components/{next/base.pyi → core/helmet.pyi} +10 -7
- reflex/components/core/html.pyi +5 -4
- reflex/components/core/sticky.pyi +17 -13
- reflex/components/core/upload.py +2 -1
- reflex/components/core/upload.pyi +21 -16
- reflex/components/datadisplay/code.py +2 -72
- reflex/components/datadisplay/code.pyi +9 -10
- reflex/components/datadisplay/dataeditor.pyi +11 -6
- reflex/components/datadisplay/shiki_code_block.pyi +13 -10
- reflex/components/dynamic.py +5 -5
- reflex/components/el/element.pyi +5 -4
- reflex/components/el/elements/base.pyi +5 -4
- reflex/components/el/elements/forms.pyi +69 -52
- reflex/components/el/elements/inline.pyi +113 -85
- reflex/components/el/elements/media.pyi +105 -79
- reflex/components/el/elements/metadata.pyi +25 -19
- reflex/components/el/elements/other.pyi +29 -22
- reflex/components/el/elements/scripts.pyi +13 -10
- reflex/components/el/elements/sectioning.pyi +61 -46
- reflex/components/el/elements/tables.pyi +41 -31
- reflex/components/el/elements/typography.pyi +61 -46
- reflex/components/field.py +175 -0
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +11 -9
- reflex/components/lucide/icon.py +6 -2
- reflex/components/lucide/icon.pyi +15 -10
- reflex/components/markdown/markdown.pyi +5 -4
- reflex/components/moment/moment.pyi +5 -4
- reflex/components/plotly/plotly.pyi +19 -10
- reflex/components/props.py +376 -27
- reflex/components/radix/primitives/accordion.py +8 -1
- reflex/components/radix/primitives/accordion.pyi +29 -22
- reflex/components/radix/primitives/base.pyi +9 -7
- reflex/components/radix/primitives/drawer.pyi +45 -34
- reflex/components/radix/primitives/form.pyi +41 -31
- reflex/components/radix/primitives/progress.pyi +21 -16
- reflex/components/radix/primitives/slider.pyi +21 -16
- reflex/components/radix/themes/base.py +3 -3
- reflex/components/radix/themes/base.pyi +33 -25
- reflex/components/radix/themes/color_mode.pyi +13 -10
- reflex/components/radix/themes/components/alert_dialog.pyi +29 -22
- reflex/components/radix/themes/components/aspect_ratio.pyi +5 -4
- reflex/components/radix/themes/components/avatar.pyi +5 -4
- reflex/components/radix/themes/components/badge.pyi +5 -4
- reflex/components/radix/themes/components/button.pyi +5 -4
- reflex/components/radix/themes/components/callout.pyi +21 -16
- reflex/components/radix/themes/components/card.pyi +5 -4
- reflex/components/radix/themes/components/checkbox.pyi +13 -10
- reflex/components/radix/themes/components/checkbox_cards.pyi +9 -7
- reflex/components/radix/themes/components/checkbox_group.pyi +9 -7
- reflex/components/radix/themes/components/context_menu.pyi +53 -40
- reflex/components/radix/themes/components/data_list.pyi +17 -13
- reflex/components/radix/themes/components/dialog.pyi +29 -22
- reflex/components/radix/themes/components/dropdown_menu.pyi +33 -25
- reflex/components/radix/themes/components/hover_card.pyi +17 -13
- reflex/components/radix/themes/components/icon_button.pyi +5 -4
- reflex/components/radix/themes/components/inset.pyi +5 -4
- reflex/components/radix/themes/components/popover.pyi +17 -13
- reflex/components/radix/themes/components/progress.pyi +5 -4
- reflex/components/radix/themes/components/radio.pyi +5 -4
- reflex/components/radix/themes/components/radio_cards.pyi +9 -7
- reflex/components/radix/themes/components/radio_group.pyi +17 -13
- reflex/components/radix/themes/components/scroll_area.pyi +5 -4
- reflex/components/radix/themes/components/segmented_control.pyi +9 -7
- reflex/components/radix/themes/components/select.pyi +37 -28
- reflex/components/radix/themes/components/separator.pyi +5 -4
- reflex/components/radix/themes/components/skeleton.pyi +5 -4
- reflex/components/radix/themes/components/slider.pyi +5 -4
- reflex/components/radix/themes/components/spinner.pyi +5 -4
- reflex/components/radix/themes/components/switch.pyi +5 -4
- reflex/components/radix/themes/components/table.pyi +29 -22
- reflex/components/radix/themes/components/tabs.pyi +21 -16
- reflex/components/radix/themes/components/text_area.pyi +5 -4
- reflex/components/radix/themes/components/text_field.pyi +13 -10
- reflex/components/radix/themes/components/tooltip.pyi +5 -4
- reflex/components/radix/themes/layout/base.pyi +5 -4
- reflex/components/radix/themes/layout/box.pyi +5 -4
- reflex/components/radix/themes/layout/center.pyi +5 -4
- reflex/components/radix/themes/layout/container.pyi +5 -4
- reflex/components/radix/themes/layout/flex.pyi +5 -4
- reflex/components/radix/themes/layout/grid.pyi +5 -4
- reflex/components/radix/themes/layout/list.pyi +21 -16
- reflex/components/radix/themes/layout/section.pyi +5 -4
- reflex/components/radix/themes/layout/spacer.pyi +5 -4
- reflex/components/radix/themes/layout/stack.pyi +13 -10
- reflex/components/radix/themes/typography/blockquote.pyi +5 -4
- reflex/components/radix/themes/typography/code.pyi +5 -4
- reflex/components/radix/themes/typography/heading.pyi +5 -4
- reflex/components/radix/themes/typography/link.py +46 -11
- reflex/components/radix/themes/typography/link.pyi +311 -6
- reflex/components/radix/themes/typography/text.pyi +29 -22
- reflex/components/react_player/audio.pyi +5 -4
- reflex/components/react_player/react_player.pyi +5 -4
- reflex/components/react_player/video.pyi +5 -4
- reflex/components/recharts/cartesian.py +2 -1
- reflex/components/recharts/cartesian.pyi +65 -46
- reflex/components/recharts/charts.py +4 -2
- reflex/components/recharts/charts.pyi +36 -24
- reflex/components/recharts/general.pyi +24 -18
- reflex/components/recharts/polar.py +8 -4
- reflex/components/recharts/polar.pyi +16 -10
- reflex/components/recharts/recharts.pyi +9 -7
- reflex/components/sonner/toast.py +2 -2
- reflex/components/sonner/toast.pyi +10 -8
- reflex/config.py +3 -77
- reflex/constants/__init__.py +2 -2
- reflex/constants/base.py +28 -11
- reflex/constants/compiler.py +5 -3
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +22 -16
- reflex/constants/route.py +19 -7
- reflex/constants/state.py +2 -0
- reflex/custom_components/custom_components.py +0 -14
- reflex/environment.py +1 -1
- reflex/event.py +178 -81
- reflex/experimental/__init__.py +0 -30
- reflex/istate/proxy.py +5 -3
- reflex/page.py +0 -27
- reflex/plugins/__init__.py +3 -2
- reflex/plugins/base.py +5 -1
- reflex/plugins/shared_tailwind.py +158 -0
- reflex/plugins/sitemap.py +206 -0
- reflex/plugins/tailwind_v3.py +13 -106
- reflex/plugins/tailwind_v4.py +15 -108
- reflex/reflex.py +1 -0
- reflex/route.py +15 -21
- reflex/state.py +134 -140
- reflex/testing.py +58 -10
- reflex/utils/build.py +38 -82
- reflex/utils/exec.py +59 -161
- reflex/utils/export.py +2 -2
- reflex/utils/imports.py +0 -4
- reflex/utils/misc.py +28 -0
- reflex/utils/prerequisites.py +65 -62
- reflex/utils/processes.py +8 -7
- reflex/utils/pyi_generator.py +21 -9
- reflex/utils/serializers.py +14 -1
- reflex/utils/types.py +196 -61
- reflex/vars/__init__.py +2 -0
- reflex/vars/base.py +367 -134
- {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/METADATA +12 -5
- {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/RECORD +195 -202
- reflex/.templates/web/next.config.js +0 -7
- reflex/components/base/head.py +0 -20
- reflex/components/base/head.pyi +0 -116
- reflex/components/next/__init__.py +0 -10
- reflex/components/next/base.py +0 -7
- reflex/components/next/image.py +0 -117
- reflex/components/next/image.pyi +0 -94
- reflex/components/next/link.py +0 -20
- reflex/components/next/link.pyi +0 -67
- reflex/components/next/video.py +0 -38
- reflex/components/next/video.pyi +0 -68
- reflex/components/suneditor/__init__.py +0 -5
- reflex/components/suneditor/editor.py +0 -269
- reflex/components/suneditor/editor.pyi +0 -199
- reflex/experimental/layout.py +0 -254
- reflex/experimental/layout.pyi +0 -814
- {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/WHEEL +0 -0
- {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.14a6.dist-info → reflex-0.8.0a2.dist-info}/licenses/LICENSE +0 -0
reflex/testing.py
CHANGED
|
@@ -241,7 +241,7 @@ class AppHarness:
|
|
|
241
241
|
def _initialize_app(self):
|
|
242
242
|
# disable telemetry reporting for tests
|
|
243
243
|
|
|
244
|
-
os.environ["
|
|
244
|
+
os.environ["REFLEX_TELEMETRY_ENABLED"] = "false"
|
|
245
245
|
CustomComponent.create().get_component.cache_clear()
|
|
246
246
|
self.app_path.mkdir(parents=True, exist_ok=True)
|
|
247
247
|
if self.app_source is not None:
|
|
@@ -344,8 +344,12 @@ class AppHarness:
|
|
|
344
344
|
)
|
|
345
345
|
self.backend.shutdown = self._get_backend_shutdown_handler()
|
|
346
346
|
with chdir(self.app_path):
|
|
347
|
+
print( # noqa: T201
|
|
348
|
+
"Creating backend in a new thread..."
|
|
349
|
+
) # for pytest diagnosis
|
|
347
350
|
self.backend_thread = threading.Thread(target=self.backend.run)
|
|
348
351
|
self.backend_thread.start()
|
|
352
|
+
print("Backend started.") # for pytest diagnosis #noqa: T201
|
|
349
353
|
|
|
350
354
|
async def _reset_backend_state_manager(self):
|
|
351
355
|
"""Reset the StateManagerRedis event loop affinity.
|
|
@@ -377,11 +381,15 @@ class AppHarness:
|
|
|
377
381
|
# Set up the frontend.
|
|
378
382
|
with chdir(self.app_path):
|
|
379
383
|
config = reflex.config.get_config()
|
|
384
|
+
print("Polling for servers...") # for pytest diagnosis #noqa: T201
|
|
380
385
|
config.api_url = "http://{}:{}".format(
|
|
381
|
-
*self._poll_for_servers().getsockname(),
|
|
386
|
+
*self._poll_for_servers(timeout=30).getsockname(),
|
|
382
387
|
)
|
|
388
|
+
print("Building frontend...") # for pytest diagnosis #noqa: T201
|
|
383
389
|
reflex.utils.build.setup_frontend(self.app_path)
|
|
384
390
|
|
|
391
|
+
print("Frontend starting...") # for pytest diagnosis #noqa: T201
|
|
392
|
+
|
|
385
393
|
# Start the frontend.
|
|
386
394
|
self.frontend_process = reflex.utils.processes.new_process(
|
|
387
395
|
[
|
|
@@ -392,19 +400,20 @@ class AppHarness:
|
|
|
392
400
|
"dev",
|
|
393
401
|
],
|
|
394
402
|
cwd=self.app_path / reflex.utils.prerequisites.get_web_dir(),
|
|
395
|
-
env={"PORT": "0"},
|
|
403
|
+
env={"PORT": "0", "NO_COLOR": "1"},
|
|
396
404
|
**FRONTEND_POPEN_ARGS,
|
|
397
405
|
)
|
|
398
406
|
|
|
399
407
|
def _wait_frontend(self):
|
|
408
|
+
if self.frontend_process is None or self.frontend_process.stdout is None:
|
|
409
|
+
msg = "Frontend process has no stdout."
|
|
410
|
+
raise RuntimeError(msg)
|
|
400
411
|
while self.frontend_url is None:
|
|
401
|
-
line = (
|
|
402
|
-
self.frontend_process.stdout.readline() # pyright: ignore [reportOptionalMemberAccess]
|
|
403
|
-
)
|
|
412
|
+
line = self.frontend_process.stdout.readline()
|
|
404
413
|
if not line:
|
|
405
414
|
break
|
|
406
415
|
print(line) # for pytest diagnosis #noqa: T201
|
|
407
|
-
m = re.search(reflex.constants.
|
|
416
|
+
m = re.search(reflex.constants.ReactRouter.FRONTEND_LISTENING_REGEX, line)
|
|
408
417
|
if m is not None:
|
|
409
418
|
self.frontend_url = m.group(1)
|
|
410
419
|
config = reflex.config.get_config()
|
|
@@ -840,6 +849,37 @@ class AppHarness:
|
|
|
840
849
|
raise TimeoutError(msg)
|
|
841
850
|
return state_manager.states
|
|
842
851
|
|
|
852
|
+
@staticmethod
|
|
853
|
+
def poll_for_result(
|
|
854
|
+
f: Callable[[], T],
|
|
855
|
+
exception: type[Exception] = Exception,
|
|
856
|
+
max_attempts: int = 5,
|
|
857
|
+
seconds_between_attempts: int = 1,
|
|
858
|
+
) -> T:
|
|
859
|
+
"""Poll for a result from a function.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
f: function to call
|
|
863
|
+
exception: exception to catch
|
|
864
|
+
max_attempts: maximum number of attempts
|
|
865
|
+
seconds_between_attempts: seconds to wait between
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
Result of the function
|
|
869
|
+
|
|
870
|
+
Raises:
|
|
871
|
+
AssertionError: if the function does not return a value
|
|
872
|
+
"""
|
|
873
|
+
attempts = 0
|
|
874
|
+
while attempts < max_attempts:
|
|
875
|
+
try:
|
|
876
|
+
return f()
|
|
877
|
+
except exception: # noqa: PERF203
|
|
878
|
+
attempts += 1
|
|
879
|
+
time.sleep(seconds_between_attempts)
|
|
880
|
+
msg = "Function did not return a value"
|
|
881
|
+
raise AssertionError(msg)
|
|
882
|
+
|
|
843
883
|
|
|
844
884
|
class SimpleHTTPRequestHandlerCustomErrors(SimpleHTTPRequestHandler):
|
|
845
885
|
"""SimpleHTTPRequestHandler with custom error page handling."""
|
|
@@ -922,7 +962,7 @@ class Subdir404TCPServer(socketserver.TCPServer):
|
|
|
922
962
|
class AppHarnessProd(AppHarness):
|
|
923
963
|
"""AppHarnessProd executes a reflex app in-process for testing.
|
|
924
964
|
|
|
925
|
-
In prod mode, instead of running `
|
|
965
|
+
In prod mode, instead of running `react-router dev` the app is exported as static
|
|
926
966
|
files and served via the builtin python http.server with custom 404 redirect
|
|
927
967
|
handling. Additionally, the backend runs in multi-worker mode.
|
|
928
968
|
"""
|
|
@@ -937,7 +977,7 @@ class AppHarnessProd(AppHarness):
|
|
|
937
977
|
/ reflex.constants.Dirs.STATIC
|
|
938
978
|
)
|
|
939
979
|
error_page_map = {
|
|
940
|
-
404: web_root / "404.html",
|
|
980
|
+
404: web_root / "404" / "index.html",
|
|
941
981
|
}
|
|
942
982
|
with Subdir404TCPServer(
|
|
943
983
|
("", 0),
|
|
@@ -954,9 +994,11 @@ class AppHarnessProd(AppHarness):
|
|
|
954
994
|
# Set up the frontend.
|
|
955
995
|
with chdir(self.app_path):
|
|
956
996
|
config = reflex.config.get_config()
|
|
997
|
+
print("Polling for servers...") # for pytest diagnosis #noqa: T201
|
|
957
998
|
config.api_url = "http://{}:{}".format(
|
|
958
|
-
*self._poll_for_servers().getsockname(),
|
|
999
|
+
*self._poll_for_servers(timeout=30).getsockname(),
|
|
959
1000
|
)
|
|
1001
|
+
print("Building frontend...") # for pytest diagnosis #noqa: T201
|
|
960
1002
|
|
|
961
1003
|
get_config().loglevel = reflex.constants.LogLevel.INFO
|
|
962
1004
|
|
|
@@ -973,6 +1015,8 @@ class AppHarnessProd(AppHarness):
|
|
|
973
1015
|
env=reflex.constants.Env.PROD,
|
|
974
1016
|
)
|
|
975
1017
|
|
|
1018
|
+
print("Frontend starting...") # for pytest diagnosis #noqa: T201
|
|
1019
|
+
|
|
976
1020
|
self.frontend_thread = threading.Thread(target=self._run_frontend)
|
|
977
1021
|
self.frontend_thread.start()
|
|
978
1022
|
|
|
@@ -996,8 +1040,12 @@ class AppHarnessProd(AppHarness):
|
|
|
996
1040
|
),
|
|
997
1041
|
)
|
|
998
1042
|
self.backend.shutdown = self._get_backend_shutdown_handler()
|
|
1043
|
+
print( # noqa: T201
|
|
1044
|
+
"Creating backend in a new thread..."
|
|
1045
|
+
)
|
|
999
1046
|
self.backend_thread = threading.Thread(target=self.backend.run)
|
|
1000
1047
|
self.backend_thread.start()
|
|
1048
|
+
print("Backend started.") # for pytest diagnosis #noqa: T201
|
|
1001
1049
|
|
|
1002
1050
|
def _poll_for_servers(self, timeout: TimeoutType = None) -> socket.socket:
|
|
1003
1051
|
try:
|
reflex/utils/build.py
CHANGED
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import json
|
|
6
5
|
import os
|
|
7
|
-
import subprocess
|
|
8
6
|
import zipfile
|
|
9
7
|
from pathlib import Path
|
|
10
8
|
|
|
11
9
|
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
|
12
10
|
|
|
13
11
|
from reflex import constants
|
|
14
|
-
from reflex.config import get_config
|
|
15
12
|
from reflex.utils import console, path_ops, prerequisites, processes
|
|
16
13
|
from reflex.utils.exec import is_in_app_harness
|
|
17
14
|
|
|
@@ -27,30 +24,6 @@ def set_env_json():
|
|
|
27
24
|
)
|
|
28
25
|
|
|
29
26
|
|
|
30
|
-
def generate_sitemap_config(deploy_url: str, export: bool = False):
|
|
31
|
-
"""Generate the sitemap config file.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
deploy_url: The URL of the deployed app.
|
|
35
|
-
export: If the sitemap are generated for an export.
|
|
36
|
-
"""
|
|
37
|
-
# Import here to avoid circular imports.
|
|
38
|
-
from reflex.compiler import templates
|
|
39
|
-
|
|
40
|
-
config = {
|
|
41
|
-
"siteUrl": deploy_url,
|
|
42
|
-
"generateRobotsTxt": True,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if export:
|
|
46
|
-
config["outDir"] = constants.Dirs.STATIC
|
|
47
|
-
|
|
48
|
-
config = json.dumps(config)
|
|
49
|
-
|
|
50
|
-
sitemap = prerequisites.get_web_dir() / constants.Next.SITEMAP_CONFIG_FILE
|
|
51
|
-
sitemap.write_text(templates.SITEMAP_CONFIG(config=config))
|
|
52
|
-
|
|
53
|
-
|
|
54
27
|
def _zip(
|
|
55
28
|
component_name: constants.ComponentName,
|
|
56
29
|
target: str | Path,
|
|
@@ -175,102 +148,85 @@ def zip_app(
|
|
|
175
148
|
)
|
|
176
149
|
|
|
177
150
|
|
|
178
|
-
def
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"""Build the app for deployment.
|
|
151
|
+
def _duplicate_index_html_to_parent_dir(directory: Path):
|
|
152
|
+
"""Duplicate index.html in the child directories to the given directory.
|
|
153
|
+
|
|
154
|
+
This makes accessing /route and /route/ work in production.
|
|
183
155
|
|
|
184
156
|
Args:
|
|
185
|
-
|
|
186
|
-
for_export: Whether the build is for export.
|
|
157
|
+
directory: The directory to duplicate index.html to.
|
|
187
158
|
"""
|
|
159
|
+
for child in directory.iterdir():
|
|
160
|
+
if child.is_dir():
|
|
161
|
+
# If the child directory has an index.html, copy it to the parent directory.
|
|
162
|
+
index_html = child / "index.html"
|
|
163
|
+
if index_html.exists():
|
|
164
|
+
target = directory / (child.name + ".html")
|
|
165
|
+
if not target.exists():
|
|
166
|
+
console.debug(f"Copying {index_html} to {target}")
|
|
167
|
+
path_ops.cp(index_html, target)
|
|
168
|
+
else:
|
|
169
|
+
console.debug(f"Skipping {index_html}, already exists at {target}")
|
|
170
|
+
# Recursively call this function for the child directory.
|
|
171
|
+
_duplicate_index_html_to_parent_dir(child)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def build():
|
|
175
|
+
"""Build the app for deployment."""
|
|
188
176
|
wdir = prerequisites.get_web_dir()
|
|
189
177
|
|
|
190
178
|
# Clean the static directory if it exists.
|
|
191
|
-
path_ops.rm(str(wdir / constants.Dirs.
|
|
192
|
-
|
|
193
|
-
# The export command to run.
|
|
194
|
-
command = "export"
|
|
179
|
+
path_ops.rm(str(wdir / constants.Dirs.BUILD_DIR))
|
|
195
180
|
|
|
196
181
|
checkpoints = [
|
|
197
|
-
"
|
|
198
|
-
"
|
|
199
|
-
"
|
|
200
|
-
"prerendered as static HTML",
|
|
201
|
-
"Collecting page data",
|
|
202
|
-
"Finalizing page optimization",
|
|
203
|
-
"Collecting build traces",
|
|
182
|
+
"building for production",
|
|
183
|
+
"building SSR bundle for production",
|
|
184
|
+
"built in",
|
|
204
185
|
]
|
|
205
186
|
|
|
206
|
-
# Generate a sitemap if a deploy URL is provided.
|
|
207
|
-
if deploy_url is not None:
|
|
208
|
-
generate_sitemap_config(deploy_url, export=for_export)
|
|
209
|
-
command = "export-sitemap"
|
|
210
|
-
|
|
211
|
-
checkpoints.extend(["Loading next-sitemap", "Generation completed"])
|
|
212
|
-
|
|
213
187
|
# Start the subprocess with the progress bar.
|
|
214
188
|
process = processes.new_process(
|
|
215
|
-
[
|
|
189
|
+
[
|
|
190
|
+
*prerequisites.get_js_package_executor(raise_on_none=True)[0],
|
|
191
|
+
"run",
|
|
192
|
+
"export",
|
|
193
|
+
],
|
|
216
194
|
cwd=wdir,
|
|
217
195
|
shell=constants.IS_WINDOWS,
|
|
196
|
+
env={
|
|
197
|
+
**os.environ,
|
|
198
|
+
"NO_COLOR": "1",
|
|
199
|
+
},
|
|
218
200
|
)
|
|
219
201
|
processes.show_progress("Creating Production Build", process, checkpoints)
|
|
202
|
+
_duplicate_index_html_to_parent_dir(wdir / constants.Dirs.STATIC)
|
|
220
203
|
|
|
221
204
|
|
|
222
205
|
def setup_frontend(
|
|
223
206
|
root: Path,
|
|
224
|
-
disable_telemetry: bool = True,
|
|
225
207
|
):
|
|
226
208
|
"""Set up the frontend to run the app.
|
|
227
209
|
|
|
228
210
|
Args:
|
|
229
211
|
root: The root path of the project.
|
|
230
|
-
disable_telemetry: Whether to disable the Next telemetry.
|
|
231
212
|
"""
|
|
232
|
-
# Create the assets dir if it doesn't exist.
|
|
233
|
-
path_ops.mkdir(constants.Dirs.APP_ASSETS)
|
|
234
|
-
path_ops.copy_tree(
|
|
235
|
-
src=str(root / constants.Dirs.APP_ASSETS),
|
|
236
|
-
dest=str(root / prerequisites.get_web_dir() / constants.Dirs.PUBLIC),
|
|
237
|
-
ignore=tuple(f"*.{ext}" for ext in constants.Reflex.STYLESHEETS_SUPPORTED),
|
|
238
|
-
)
|
|
239
|
-
|
|
240
213
|
# Set the environment variables in client (env.json).
|
|
241
214
|
set_env_json()
|
|
242
215
|
|
|
243
216
|
# update the last reflex run time.
|
|
244
217
|
prerequisites.set_last_reflex_run_time()
|
|
245
218
|
|
|
246
|
-
# Disable the Next telemetry.
|
|
247
|
-
if disable_telemetry:
|
|
248
|
-
processes.new_process(
|
|
249
|
-
[
|
|
250
|
-
*prerequisites.get_js_package_executor(raise_on_none=True)[0],
|
|
251
|
-
"run",
|
|
252
|
-
"next",
|
|
253
|
-
"telemetry",
|
|
254
|
-
"disable",
|
|
255
|
-
],
|
|
256
|
-
cwd=prerequisites.get_web_dir(),
|
|
257
|
-
stdout=subprocess.DEVNULL,
|
|
258
|
-
shell=constants.IS_WINDOWS,
|
|
259
|
-
)
|
|
260
|
-
|
|
261
219
|
|
|
262
220
|
def setup_frontend_prod(
|
|
263
221
|
root: Path,
|
|
264
|
-
disable_telemetry: bool = True,
|
|
265
222
|
):
|
|
266
223
|
"""Set up the frontend for prod mode.
|
|
267
224
|
|
|
268
225
|
Args:
|
|
269
226
|
root: The root path of the project.
|
|
270
|
-
disable_telemetry: Whether to disable the Next telemetry.
|
|
271
227
|
"""
|
|
272
|
-
setup_frontend(root
|
|
273
|
-
build(
|
|
228
|
+
setup_frontend(root)
|
|
229
|
+
build()
|
|
274
230
|
|
|
275
231
|
|
|
276
232
|
def _looks_like_venv_dir(dir_to_check: str | Path) -> bool:
|
reflex/utils/exec.py
CHANGED
|
@@ -12,7 +12,7 @@ import subprocess
|
|
|
12
12
|
import sys
|
|
13
13
|
from collections.abc import Sequence
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import NamedTuple, TypedDict
|
|
15
|
+
from typing import Any, NamedTuple, TypedDict
|
|
16
16
|
from urllib.parse import urljoin
|
|
17
17
|
|
|
18
18
|
import psutil
|
|
@@ -170,7 +170,12 @@ def run_process_and_launch_url(
|
|
|
170
170
|
|
|
171
171
|
while True:
|
|
172
172
|
if process is None:
|
|
173
|
-
kwargs = {
|
|
173
|
+
kwargs: dict[str, Any] = {
|
|
174
|
+
"env": {
|
|
175
|
+
**os.environ,
|
|
176
|
+
"NO_COLOR": "1",
|
|
177
|
+
}
|
|
178
|
+
}
|
|
174
179
|
if constants.IS_WINDOWS and backend_present:
|
|
175
180
|
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # pyright: ignore [reportAttributeAccessIssue]
|
|
176
181
|
process = processes.new_process(
|
|
@@ -195,7 +200,7 @@ def run_process_and_launch_url(
|
|
|
195
200
|
+ format_change("Dev Dependencies", dev_dependencies_change)
|
|
196
201
|
)
|
|
197
202
|
|
|
198
|
-
match = re.search(constants.
|
|
203
|
+
match = re.search(constants.ReactRouter.FRONTEND_LISTENING_REGEX, line)
|
|
199
204
|
if match:
|
|
200
205
|
if first_run:
|
|
201
206
|
url = match.group(1)
|
|
@@ -367,21 +372,48 @@ def run_backend(
|
|
|
367
372
|
run_uvicorn_backend(host, port, loglevel)
|
|
368
373
|
|
|
369
374
|
|
|
375
|
+
def _has_child_file(directory: Path, file_name: str) -> bool:
|
|
376
|
+
"""Check if a directory has a child file with the given name.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
directory: The directory to check.
|
|
380
|
+
file_name: The name of the file to look for.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
True if the directory has a child file with the given name, False otherwise.
|
|
384
|
+
"""
|
|
385
|
+
return any(child_file.name == file_name for child_file in directory.iterdir())
|
|
386
|
+
|
|
387
|
+
|
|
370
388
|
def get_reload_paths() -> Sequence[Path]:
|
|
371
389
|
"""Get the reload paths for the backend.
|
|
372
390
|
|
|
373
391
|
Returns:
|
|
374
392
|
The reload paths for the backend.
|
|
393
|
+
|
|
394
|
+
Raises:
|
|
395
|
+
RuntimeError: If the `__init__.py` file is found in the app root directory.
|
|
375
396
|
"""
|
|
376
397
|
config = get_config()
|
|
377
398
|
reload_paths = [Path.cwd()]
|
|
378
399
|
if (spec := importlib.util.find_spec(config.module)) is not None and spec.origin:
|
|
379
400
|
module_path = Path(spec.origin).resolve().parent
|
|
380
401
|
|
|
381
|
-
while module_path.parent.name and
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
402
|
+
while module_path.parent.name and _has_child_file(module_path, "__init__.py"):
|
|
403
|
+
if _has_child_file(module_path, "rxconfig.py"):
|
|
404
|
+
init_file = module_path / "__init__.py"
|
|
405
|
+
init_file_content = init_file.read_text()
|
|
406
|
+
if init_file_content.strip():
|
|
407
|
+
msg = "There should not be an `__init__.py` file in your app root directory"
|
|
408
|
+
raise RuntimeError(msg)
|
|
409
|
+
console.warn(
|
|
410
|
+
"Removing `__init__.py` file in the app root directory. "
|
|
411
|
+
"This file can cause issues with module imports. "
|
|
412
|
+
)
|
|
413
|
+
init_file.unlink()
|
|
414
|
+
break
|
|
415
|
+
|
|
416
|
+
# go up a level to find dir without `__init__.py` or with `rxconfig.py`
|
|
385
417
|
module_path = module_path.parent
|
|
386
418
|
|
|
387
419
|
reload_paths = [module_path]
|
|
@@ -500,74 +532,6 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
500
532
|
).serve()
|
|
501
533
|
|
|
502
534
|
|
|
503
|
-
def _deprecate_asgi_config(
|
|
504
|
-
config_name: str,
|
|
505
|
-
reason: str = "",
|
|
506
|
-
):
|
|
507
|
-
console.deprecate(
|
|
508
|
-
f"config.{config_name}",
|
|
509
|
-
reason=reason,
|
|
510
|
-
deprecation_version="0.7.9",
|
|
511
|
-
removal_version="0.8.0",
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
@once
|
|
516
|
-
def _get_backend_workers():
|
|
517
|
-
from reflex.utils import processes
|
|
518
|
-
|
|
519
|
-
config = get_config()
|
|
520
|
-
|
|
521
|
-
gunicorn_workers = config.gunicorn_workers or 0
|
|
522
|
-
|
|
523
|
-
if config.gunicorn_workers is not None:
|
|
524
|
-
_deprecate_asgi_config(
|
|
525
|
-
"gunicorn_workers",
|
|
526
|
-
"If you're using Granian, use GRANIAN_WORKERS instead.",
|
|
527
|
-
)
|
|
528
|
-
|
|
529
|
-
return gunicorn_workers if gunicorn_workers else processes.get_num_workers()
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
@once
|
|
533
|
-
def _get_backend_timeout():
|
|
534
|
-
config = get_config()
|
|
535
|
-
|
|
536
|
-
timeout = config.timeout or 120
|
|
537
|
-
|
|
538
|
-
if config.timeout is not None:
|
|
539
|
-
_deprecate_asgi_config(
|
|
540
|
-
"timeout",
|
|
541
|
-
"If you're using Granian, use GRANIAN_WORKERS_LIFETIME instead.",
|
|
542
|
-
)
|
|
543
|
-
|
|
544
|
-
return timeout
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
@once
|
|
548
|
-
def _get_backend_max_requests():
|
|
549
|
-
config = get_config()
|
|
550
|
-
|
|
551
|
-
gunicorn_max_requests = config.gunicorn_max_requests or 120
|
|
552
|
-
|
|
553
|
-
if config.gunicorn_max_requests is not None:
|
|
554
|
-
_deprecate_asgi_config("gunicorn_max_requests")
|
|
555
|
-
|
|
556
|
-
return gunicorn_max_requests
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
@once
|
|
560
|
-
def _get_backend_max_requests_jitter():
|
|
561
|
-
config = get_config()
|
|
562
|
-
|
|
563
|
-
gunicorn_max_requests_jitter = config.gunicorn_max_requests_jitter or 25
|
|
564
|
-
|
|
565
|
-
if config.gunicorn_max_requests_jitter is not None:
|
|
566
|
-
_deprecate_asgi_config("gunicorn_max_requests_jitter")
|
|
567
|
-
|
|
568
|
-
return gunicorn_max_requests_jitter
|
|
569
|
-
|
|
570
|
-
|
|
571
535
|
def run_backend_prod(
|
|
572
536
|
host: str,
|
|
573
537
|
port: int,
|
|
@@ -601,72 +565,12 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
601
565
|
"""
|
|
602
566
|
from reflex.utils import processes
|
|
603
567
|
|
|
604
|
-
config = get_config()
|
|
605
|
-
|
|
606
568
|
app_module = get_app_instance()
|
|
607
569
|
|
|
608
570
|
command = (
|
|
609
|
-
[
|
|
610
|
-
"uvicorn",
|
|
611
|
-
*(
|
|
612
|
-
(
|
|
613
|
-
"--limit-max-requests",
|
|
614
|
-
str(max_requessts),
|
|
615
|
-
)
|
|
616
|
-
if (
|
|
617
|
-
(max_requessts := _get_backend_max_requests()) is not None
|
|
618
|
-
and max_requessts > 0
|
|
619
|
-
)
|
|
620
|
-
else ()
|
|
621
|
-
),
|
|
622
|
-
*(
|
|
623
|
-
("--timeout-keep-alive", str(timeout))
|
|
624
|
-
if (timeout := _get_backend_timeout()) is not None
|
|
625
|
-
else ()
|
|
626
|
-
),
|
|
627
|
-
*("--host", host),
|
|
628
|
-
*("--port", str(port)),
|
|
629
|
-
*("--workers", str(_get_backend_workers())),
|
|
630
|
-
"--factory",
|
|
631
|
-
app_module,
|
|
632
|
-
]
|
|
571
|
+
["uvicorn", *("--host", host), *("--port", str(port)), "--factory", app_module]
|
|
633
572
|
if constants.IS_WINDOWS
|
|
634
|
-
else [
|
|
635
|
-
"gunicorn",
|
|
636
|
-
*("--worker-class", config.gunicorn_worker_class),
|
|
637
|
-
*(
|
|
638
|
-
(
|
|
639
|
-
"--max-requests",
|
|
640
|
-
str(max_requessts),
|
|
641
|
-
)
|
|
642
|
-
if (
|
|
643
|
-
(max_requessts := _get_backend_max_requests()) is not None
|
|
644
|
-
and max_requessts > 0
|
|
645
|
-
)
|
|
646
|
-
else ()
|
|
647
|
-
),
|
|
648
|
-
*(
|
|
649
|
-
(
|
|
650
|
-
"--max-requests-jitter",
|
|
651
|
-
str(max_requessts_jitter),
|
|
652
|
-
)
|
|
653
|
-
if (
|
|
654
|
-
(max_requessts_jitter := _get_backend_max_requests_jitter())
|
|
655
|
-
is not None
|
|
656
|
-
and max_requessts_jitter > 0
|
|
657
|
-
)
|
|
658
|
-
else ()
|
|
659
|
-
),
|
|
660
|
-
"--preload",
|
|
661
|
-
*(
|
|
662
|
-
("--timeout", str(timeout))
|
|
663
|
-
if (timeout := _get_backend_timeout()) is not None
|
|
664
|
-
else ()
|
|
665
|
-
),
|
|
666
|
-
*("--bind", f"{host}:{port}"),
|
|
667
|
-
*("--threads", str(_get_backend_workers())),
|
|
668
|
-
f"{app_module}()",
|
|
669
|
-
]
|
|
573
|
+
else ["gunicorn", "--preload", *("--bind", f"{host}:{port}"), f"{app_module}()"]
|
|
670
574
|
)
|
|
671
575
|
|
|
672
576
|
command += [
|
|
@@ -691,32 +595,26 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
691
595
|
port: The app port
|
|
692
596
|
loglevel: The log level.
|
|
693
597
|
"""
|
|
598
|
+
from granian.constants import Interfaces
|
|
599
|
+
|
|
694
600
|
from reflex.utils import processes
|
|
695
601
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
env={
|
|
713
|
-
environment.REFLEX_SKIP_COMPILE.name: "true"
|
|
714
|
-
}, # skip compile for prod backend
|
|
715
|
-
)
|
|
716
|
-
except ImportError:
|
|
717
|
-
console.error(
|
|
718
|
-
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian[reload]>=1.6.0"`)'
|
|
719
|
-
)
|
|
602
|
+
command = [
|
|
603
|
+
"granian",
|
|
604
|
+
*("--log-level", "critical"),
|
|
605
|
+
*("--host", host),
|
|
606
|
+
*("--port", str(port)),
|
|
607
|
+
*("--interface", str(Interfaces.ASGI)),
|
|
608
|
+
*("--factory", get_app_instance_from_file()),
|
|
609
|
+
]
|
|
610
|
+
processes.new_process(
|
|
611
|
+
command,
|
|
612
|
+
run=True,
|
|
613
|
+
show_logs=True,
|
|
614
|
+
env={
|
|
615
|
+
environment.REFLEX_SKIP_COMPILE.name: "true"
|
|
616
|
+
}, # skip compile for prod backend
|
|
617
|
+
)
|
|
720
618
|
|
|
721
619
|
|
|
722
620
|
def output_system_info():
|
reflex/utils/export.py
CHANGED
|
@@ -56,13 +56,13 @@ def export(
|
|
|
56
56
|
|
|
57
57
|
if frontend:
|
|
58
58
|
# Ensure module can be imported and app.compile() is called.
|
|
59
|
-
prerequisites.get_compiled_app(
|
|
59
|
+
prerequisites.get_compiled_app(prerender_routes=True)
|
|
60
60
|
# Set up .web directory and install frontend dependencies.
|
|
61
61
|
build.setup_frontend(Path.cwd())
|
|
62
62
|
|
|
63
63
|
# Build the static app.
|
|
64
64
|
if frontend:
|
|
65
|
-
build.build(
|
|
65
|
+
build.build()
|
|
66
66
|
|
|
67
67
|
# Zip up the app.
|
|
68
68
|
if zipping:
|
reflex/utils/imports.py
CHANGED
|
@@ -114,10 +114,6 @@ class ImportVar:
|
|
|
114
114
|
# The path of the package to import from.
|
|
115
115
|
package_path: str = "/"
|
|
116
116
|
|
|
117
|
-
# whether this import package should be added to transpilePackages in next.config.js
|
|
118
|
-
# https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages
|
|
119
|
-
transpile: bool | None = False
|
|
120
|
-
|
|
121
117
|
@property
|
|
122
118
|
def name(self) -> str:
|
|
123
119
|
"""The name of the import.
|
reflex/utils/misc.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"""Miscellaneous functions for the experimental package."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
import sys
|
|
6
|
+
import threading
|
|
4
7
|
from collections.abc import Callable
|
|
8
|
+
from pathlib import Path
|
|
5
9
|
from typing import Any
|
|
6
10
|
|
|
7
11
|
|
|
@@ -23,3 +27,27 @@ async def run_in_thread(func: Callable) -> Any:
|
|
|
23
27
|
msg = "func must be a non-async function"
|
|
24
28
|
raise ValueError(msg)
|
|
25
29
|
return await asyncio.get_event_loop().run_in_executor(None, func)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Global lock for thread-safe sys.path manipulation
|
|
33
|
+
_sys_path_lock = threading.RLock()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@contextlib.contextmanager
|
|
37
|
+
def with_cwd_in_syspath():
|
|
38
|
+
"""Temporarily add current working directory to sys.path in a thread-safe manner.
|
|
39
|
+
|
|
40
|
+
This context manager temporarily prepends the current working directory to sys.path,
|
|
41
|
+
ensuring that modules in the current directory can be imported. The original sys.path
|
|
42
|
+
is restored when exiting the context.
|
|
43
|
+
|
|
44
|
+
Yields:
|
|
45
|
+
None
|
|
46
|
+
"""
|
|
47
|
+
with _sys_path_lock:
|
|
48
|
+
orig_sys_path = sys.path.copy()
|
|
49
|
+
sys.path.insert(0, str(Path.cwd()))
|
|
50
|
+
try:
|
|
51
|
+
yield
|
|
52
|
+
finally:
|
|
53
|
+
sys.path[:] = orig_sys_path
|