reflex 0.7.14a6__py3-none-any.whl → 0.8.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/app/rxconfig.py.jinja2 +4 -1
- reflex/.templates/jinja/web/package.json.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +21 -11
- 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/styles/styles.css.jinja2 +1 -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/styles/__reflex_style_reset.css +399 -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 +251 -100
- reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
- reflex/.templates/web/vite.config.js +39 -0
- reflex/__init__.py +1 -6
- reflex/__init__.pyi +327 -192
- reflex/app.py +86 -135
- reflex/base.py +1 -87
- reflex/compiler/compiler.py +70 -19
- reflex/compiler/templates.py +3 -3
- reflex/compiler/utils.py +91 -33
- reflex/components/__init__.py +0 -2
- reflex/components/__init__.pyi +34 -18
- reflex/components/base/__init__.py +1 -5
- reflex/components/base/__init__.pyi +30 -21
- reflex/components/base/app_wrap.pyi +7 -7
- reflex/components/base/body.pyi +7 -7
- reflex/components/base/document.py +18 -14
- reflex/components/base/document.pyi +88 -38
- reflex/components/base/error_boundary.pyi +7 -7
- reflex/components/base/fragment.pyi +7 -7
- reflex/components/base/link.pyi +12 -12
- reflex/components/base/meta.py +4 -15
- reflex/components/base/meta.pyi +31 -31
- reflex/components/base/script.py +60 -58
- reflex/components/base/script.pyi +248 -34
- reflex/components/base/strict_mode.pyi +7 -7
- reflex/components/component.py +146 -217
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +77 -37
- reflex/components/core/auto_scroll.pyi +7 -7
- reflex/components/core/banner.pyi +33 -33
- reflex/components/core/client_side_routing.py +7 -6
- reflex/components/core/client_side_routing.pyi +8 -59
- reflex/components/core/clipboard.pyi +7 -7
- reflex/components/core/debounce.py +1 -0
- reflex/components/core/debounce.pyi +7 -7
- reflex/components/core/foreach.py +5 -4
- reflex/components/core/helmet.py +14 -0
- reflex/components/{next/base.pyi → core/helmet.pyi} +12 -10
- reflex/components/core/html.pyi +7 -7
- reflex/components/core/match.py +3 -3
- reflex/components/core/sticky.pyi +21 -20
- reflex/components/core/upload.py +4 -2
- reflex/components/core/upload.pyi +26 -25
- reflex/components/datadisplay/__init__.pyi +13 -7
- reflex/components/datadisplay/code.py +14 -79
- reflex/components/datadisplay/code.pyi +11 -13
- reflex/components/datadisplay/dataeditor.pyi +38 -15
- reflex/components/datadisplay/shiki_code_block.py +5 -3
- reflex/components/datadisplay/shiki_code_block.pyi +16 -15
- reflex/components/dynamic.py +5 -5
- reflex/components/el/__init__.pyi +506 -246
- reflex/components/el/element.pyi +7 -7
- reflex/components/el/elements/__init__.pyi +504 -245
- reflex/components/el/elements/base.pyi +7 -7
- reflex/components/el/elements/forms.pyi +146 -101
- reflex/components/el/elements/inline.pyi +142 -142
- reflex/components/el/elements/media.pyi +131 -130
- reflex/components/el/elements/metadata.pyi +32 -32
- reflex/components/el/elements/other.pyi +37 -37
- reflex/components/el/elements/scripts.pyi +17 -17
- reflex/components/el/elements/sectioning.pyi +77 -77
- reflex/components/el/elements/tables.pyi +52 -52
- reflex/components/el/elements/typography.pyi +77 -77
- reflex/components/field.py +175 -0
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +14 -14
- reflex/components/lucide/icon.py +6 -2
- reflex/components/lucide/icon.pyi +19 -17
- reflex/components/markdown/markdown.py +5 -3
- reflex/components/markdown/markdown.pyi +7 -7
- reflex/components/moment/moment.py +1 -1
- reflex/components/moment/moment.pyi +7 -7
- reflex/components/plotly/plotly.py +12 -6
- reflex/components/plotly/plotly.pyi +50 -49
- reflex/components/props.py +376 -27
- reflex/components/radix/__init__.pyi +123 -65
- reflex/components/radix/primitives/__init__.pyi +6 -4
- reflex/components/radix/primitives/accordion.py +8 -1
- reflex/components/radix/primitives/accordion.pyi +37 -37
- reflex/components/radix/primitives/base.pyi +12 -12
- reflex/components/radix/primitives/drawer.pyi +56 -55
- reflex/components/radix/primitives/form.pyi +63 -53
- reflex/components/radix/primitives/progress.pyi +26 -25
- reflex/components/radix/primitives/slider.pyi +27 -27
- reflex/components/radix/themes/__init__.pyi +5 -6
- reflex/components/radix/themes/base.py +3 -3
- reflex/components/radix/themes/base.pyi +42 -42
- reflex/components/radix/themes/color_mode.py +5 -6
- reflex/components/radix/themes/color_mode.pyi +17 -17
- reflex/components/radix/themes/components/__init__.pyi +75 -38
- reflex/components/radix/themes/components/alert_dialog.pyi +37 -37
- reflex/components/radix/themes/components/aspect_ratio.pyi +7 -7
- reflex/components/radix/themes/components/avatar.pyi +7 -7
- reflex/components/radix/themes/components/badge.pyi +7 -7
- reflex/components/radix/themes/components/button.pyi +7 -7
- reflex/components/radix/themes/components/callout.pyi +26 -25
- reflex/components/radix/themes/components/card.pyi +7 -7
- reflex/components/radix/themes/components/checkbox.pyi +16 -15
- reflex/components/radix/themes/components/checkbox_cards.pyi +12 -12
- reflex/components/radix/themes/components/checkbox_group.pyi +12 -12
- reflex/components/radix/themes/components/context_menu.pyi +67 -67
- reflex/components/radix/themes/components/data_list.pyi +22 -22
- reflex/components/radix/themes/components/dialog.pyi +36 -35
- reflex/components/radix/themes/components/dropdown_menu.pyi +42 -42
- reflex/components/radix/themes/components/hover_card.pyi +21 -20
- reflex/components/radix/themes/components/icon_button.pyi +7 -7
- reflex/components/radix/themes/components/inset.pyi +7 -7
- reflex/components/radix/themes/components/popover.pyi +22 -22
- reflex/components/radix/themes/components/progress.pyi +7 -7
- reflex/components/radix/themes/components/radio.pyi +7 -7
- reflex/components/radix/themes/components/radio_cards.pyi +12 -12
- reflex/components/radix/themes/components/radio_group.pyi +21 -20
- reflex/components/radix/themes/components/scroll_area.pyi +7 -7
- reflex/components/radix/themes/components/segmented_control.pyi +12 -12
- reflex/components/radix/themes/components/select.pyi +46 -45
- reflex/components/radix/themes/components/separator.pyi +7 -7
- reflex/components/radix/themes/components/skeleton.pyi +7 -7
- reflex/components/radix/themes/components/slider.pyi +17 -9
- reflex/components/radix/themes/components/spinner.pyi +7 -7
- reflex/components/radix/themes/components/switch.pyi +7 -7
- reflex/components/radix/themes/components/table.pyi +37 -37
- reflex/components/radix/themes/components/tabs.pyi +26 -25
- reflex/components/radix/themes/components/text_area.pyi +15 -9
- reflex/components/radix/themes/components/text_field.pyi +32 -19
- reflex/components/radix/themes/components/tooltip.pyi +7 -7
- reflex/components/radix/themes/layout/__init__.pyi +27 -14
- reflex/components/radix/themes/layout/base.pyi +7 -7
- reflex/components/radix/themes/layout/box.pyi +7 -7
- reflex/components/radix/themes/layout/center.pyi +7 -7
- reflex/components/radix/themes/layout/container.pyi +7 -7
- reflex/components/radix/themes/layout/flex.pyi +7 -7
- reflex/components/radix/themes/layout/grid.pyi +7 -7
- reflex/components/radix/themes/layout/list.pyi +26 -25
- reflex/components/radix/themes/layout/section.pyi +7 -7
- reflex/components/radix/themes/layout/spacer.pyi +7 -7
- reflex/components/radix/themes/layout/stack.pyi +17 -17
- reflex/components/radix/themes/typography/__init__.pyi +7 -5
- reflex/components/radix/themes/typography/blockquote.pyi +7 -7
- reflex/components/radix/themes/typography/code.pyi +7 -7
- reflex/components/radix/themes/typography/heading.pyi +7 -7
- reflex/components/radix/themes/typography/link.py +46 -11
- reflex/components/radix/themes/typography/link.pyi +312 -9
- reflex/components/radix/themes/typography/text.pyi +36 -35
- reflex/components/react_player/audio.pyi +10 -8
- reflex/components/react_player/react_player.pyi +7 -7
- reflex/components/react_player/video.pyi +10 -8
- reflex/components/recharts/__init__.pyi +208 -100
- reflex/components/recharts/cartesian.py +10 -8
- reflex/components/recharts/cartesian.pyi +90 -94
- reflex/components/recharts/charts.py +4 -2
- reflex/components/recharts/charts.pyi +49 -49
- reflex/components/recharts/general.pyi +31 -31
- reflex/components/recharts/polar.py +8 -4
- reflex/components/recharts/polar.pyi +23 -23
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/recharts/recharts.pyi +12 -12
- reflex/components/sonner/toast.py +3 -3
- reflex/components/sonner/toast.pyi +9 -9
- reflex/config.py +10 -113
- reflex/constants/__init__.py +2 -2
- reflex/constants/base.py +28 -11
- reflex/constants/compiler.py +12 -3
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +26 -20
- reflex/constants/route.py +27 -8
- reflex/constants/state.py +2 -0
- reflex/custom_components/custom_components.py +0 -14
- reflex/environment.py +77 -5
- reflex/event.py +178 -81
- reflex/experimental/__init__.py +0 -30
- reflex/istate/__init__.py +69 -0
- reflex/istate/manager.py +1 -0
- 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 +215 -0
- reflex/plugins/sitemap.py +206 -0
- reflex/plugins/tailwind_v3.py +15 -108
- reflex/plugins/tailwind_v4.py +18 -110
- reflex/reflex.py +1 -0
- reflex/route.py +157 -75
- reflex/state.py +171 -155
- reflex/testing.py +86 -16
- reflex/utils/build.py +38 -82
- reflex/utils/exec.py +83 -175
- reflex/utils/export.py +2 -2
- reflex/utils/format.py +1 -5
- reflex/utils/imports.py +5 -16
- reflex/utils/misc.py +67 -0
- reflex/utils/prerequisites.py +66 -68
- reflex/utils/processes.py +24 -47
- reflex/utils/pyi_generator.py +44 -49
- reflex/utils/serializers.py +14 -1
- reflex/utils/telemetry.py +0 -15
- reflex/utils/types.py +197 -62
- reflex/vars/__init__.py +2 -0
- reflex/vars/base.py +367 -134
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/METADATA +15 -8
- reflex-0.8.0.dist-info/RECORD +403 -0
- 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/RECORD +0 -408
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
reflex/testing.py
CHANGED
|
@@ -22,9 +22,8 @@ import types
|
|
|
22
22
|
from collections.abc import AsyncIterator, Callable, Coroutine, Sequence
|
|
23
23
|
from http.server import SimpleHTTPRequestHandler
|
|
24
24
|
from pathlib import Path
|
|
25
|
-
from typing import TYPE_CHECKING, Any, TypeVar
|
|
25
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
26
26
|
|
|
27
|
-
import psutil
|
|
28
27
|
import uvicorn
|
|
29
28
|
|
|
30
29
|
import reflex
|
|
@@ -241,7 +240,7 @@ class AppHarness:
|
|
|
241
240
|
def _initialize_app(self):
|
|
242
241
|
# disable telemetry reporting for tests
|
|
243
242
|
|
|
244
|
-
os.environ["
|
|
243
|
+
os.environ["REFLEX_TELEMETRY_ENABLED"] = "false"
|
|
245
244
|
CustomComponent.create().get_component.cache_clear()
|
|
246
245
|
self.app_path.mkdir(parents=True, exist_ok=True)
|
|
247
246
|
if self.app_source is not None:
|
|
@@ -344,8 +343,12 @@ class AppHarness:
|
|
|
344
343
|
)
|
|
345
344
|
self.backend.shutdown = self._get_backend_shutdown_handler()
|
|
346
345
|
with chdir(self.app_path):
|
|
346
|
+
print( # noqa: T201
|
|
347
|
+
"Creating backend in a new thread..."
|
|
348
|
+
) # for pytest diagnosis
|
|
347
349
|
self.backend_thread = threading.Thread(target=self.backend.run)
|
|
348
350
|
self.backend_thread.start()
|
|
351
|
+
print("Backend started.") # for pytest diagnosis #noqa: T201
|
|
349
352
|
|
|
350
353
|
async def _reset_backend_state_manager(self):
|
|
351
354
|
"""Reset the StateManagerRedis event loop affinity.
|
|
@@ -377,11 +380,15 @@ class AppHarness:
|
|
|
377
380
|
# Set up the frontend.
|
|
378
381
|
with chdir(self.app_path):
|
|
379
382
|
config = reflex.config.get_config()
|
|
383
|
+
print("Polling for servers...") # for pytest diagnosis #noqa: T201
|
|
380
384
|
config.api_url = "http://{}:{}".format(
|
|
381
|
-
*self._poll_for_servers().getsockname(),
|
|
385
|
+
*self._poll_for_servers(timeout=30).getsockname(),
|
|
382
386
|
)
|
|
387
|
+
print("Building frontend...") # for pytest diagnosis #noqa: T201
|
|
383
388
|
reflex.utils.build.setup_frontend(self.app_path)
|
|
384
389
|
|
|
390
|
+
print("Frontend starting...") # for pytest diagnosis #noqa: T201
|
|
391
|
+
|
|
385
392
|
# Start the frontend.
|
|
386
393
|
self.frontend_process = reflex.utils.processes.new_process(
|
|
387
394
|
[
|
|
@@ -392,19 +399,20 @@ class AppHarness:
|
|
|
392
399
|
"dev",
|
|
393
400
|
],
|
|
394
401
|
cwd=self.app_path / reflex.utils.prerequisites.get_web_dir(),
|
|
395
|
-
env={"PORT": "0"},
|
|
402
|
+
env={"PORT": "0", "NO_COLOR": "1"},
|
|
396
403
|
**FRONTEND_POPEN_ARGS,
|
|
397
404
|
)
|
|
398
405
|
|
|
399
406
|
def _wait_frontend(self):
|
|
407
|
+
if self.frontend_process is None or self.frontend_process.stdout is None:
|
|
408
|
+
msg = "Frontend process has no stdout."
|
|
409
|
+
raise RuntimeError(msg)
|
|
400
410
|
while self.frontend_url is None:
|
|
401
|
-
line = (
|
|
402
|
-
self.frontend_process.stdout.readline() # pyright: ignore [reportOptionalMemberAccess]
|
|
403
|
-
)
|
|
411
|
+
line = self.frontend_process.stdout.readline()
|
|
404
412
|
if not line:
|
|
405
413
|
break
|
|
406
414
|
print(line) # for pytest diagnosis #noqa: T201
|
|
407
|
-
m = re.search(reflex.constants.
|
|
415
|
+
m = re.search(reflex.constants.ReactRouter.FRONTEND_LISTENING_REGEX, line)
|
|
408
416
|
if m is not None:
|
|
409
417
|
self.frontend_url = m.group(1)
|
|
410
418
|
config = reflex.config.get_config()
|
|
@@ -469,6 +477,8 @@ class AppHarness:
|
|
|
469
477
|
|
|
470
478
|
def stop(self) -> None:
|
|
471
479
|
"""Stop the frontend and backend servers."""
|
|
480
|
+
import psutil
|
|
481
|
+
|
|
472
482
|
# Quit browsers first to avoid any lingering events being sent during shutdown.
|
|
473
483
|
for driver in self._frontends:
|
|
474
484
|
driver.quit()
|
|
@@ -517,7 +527,7 @@ class AppHarness:
|
|
|
517
527
|
target: Callable[[], T],
|
|
518
528
|
timeout: TimeoutType = None,
|
|
519
529
|
step: TimeoutType = None,
|
|
520
|
-
) -> T |
|
|
530
|
+
) -> T | Literal[False]:
|
|
521
531
|
"""Generic polling logic.
|
|
522
532
|
|
|
523
533
|
Args:
|
|
@@ -535,9 +545,10 @@ class AppHarness:
|
|
|
535
545
|
step = POLL_INTERVAL
|
|
536
546
|
deadline = time.time() + timeout
|
|
537
547
|
while time.time() < deadline:
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
548
|
+
with contextlib.suppress(Exception):
|
|
549
|
+
success = target()
|
|
550
|
+
if success:
|
|
551
|
+
return success
|
|
541
552
|
time.sleep(step)
|
|
542
553
|
return False
|
|
543
554
|
|
|
@@ -840,6 +851,57 @@ class AppHarness:
|
|
|
840
851
|
raise TimeoutError(msg)
|
|
841
852
|
return state_manager.states
|
|
842
853
|
|
|
854
|
+
@staticmethod
|
|
855
|
+
def poll_for_or_raise_timeout(
|
|
856
|
+
target: Callable[[], T],
|
|
857
|
+
timeout: TimeoutType = None,
|
|
858
|
+
step: TimeoutType = None,
|
|
859
|
+
) -> T:
|
|
860
|
+
"""Poll target callable for a truthy return value.
|
|
861
|
+
|
|
862
|
+
Like `_poll_for`, but raises a `TimeoutError` if the target does not
|
|
863
|
+
return a truthy value within the timeout.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
target: callable that returns truthy if polling condition is met.
|
|
867
|
+
timeout: max polling time
|
|
868
|
+
step: interval between checking target()
|
|
869
|
+
|
|
870
|
+
Returns:
|
|
871
|
+
return value of target() if truthy within timeout
|
|
872
|
+
|
|
873
|
+
Raises:
|
|
874
|
+
TimeoutError: when target does not return a truthy value within timeout
|
|
875
|
+
"""
|
|
876
|
+
result = AppHarness._poll_for(
|
|
877
|
+
target=target,
|
|
878
|
+
timeout=timeout,
|
|
879
|
+
step=step,
|
|
880
|
+
)
|
|
881
|
+
if result is False:
|
|
882
|
+
msg = "Target did not return a truthy value while polling."
|
|
883
|
+
raise TimeoutError(msg)
|
|
884
|
+
return result
|
|
885
|
+
|
|
886
|
+
@staticmethod
|
|
887
|
+
def expect(
|
|
888
|
+
target: Callable[[], T],
|
|
889
|
+
timeout: TimeoutType = None,
|
|
890
|
+
step: TimeoutType = None,
|
|
891
|
+
):
|
|
892
|
+
"""Expect a target callable to return a truthy value within the timeout.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
target: callable that returns truthy if polling condition is met.
|
|
896
|
+
timeout: max polling time
|
|
897
|
+
step: interval between checking target()
|
|
898
|
+
"""
|
|
899
|
+
AppHarness.poll_for_or_raise_timeout(
|
|
900
|
+
target=target,
|
|
901
|
+
timeout=timeout,
|
|
902
|
+
step=step,
|
|
903
|
+
)
|
|
904
|
+
|
|
843
905
|
|
|
844
906
|
class SimpleHTTPRequestHandlerCustomErrors(SimpleHTTPRequestHandler):
|
|
845
907
|
"""SimpleHTTPRequestHandler with custom error page handling."""
|
|
@@ -922,7 +984,7 @@ class Subdir404TCPServer(socketserver.TCPServer):
|
|
|
922
984
|
class AppHarnessProd(AppHarness):
|
|
923
985
|
"""AppHarnessProd executes a reflex app in-process for testing.
|
|
924
986
|
|
|
925
|
-
In prod mode, instead of running `
|
|
987
|
+
In prod mode, instead of running `react-router dev` the app is exported as static
|
|
926
988
|
files and served via the builtin python http.server with custom 404 redirect
|
|
927
989
|
handling. Additionally, the backend runs in multi-worker mode.
|
|
928
990
|
"""
|
|
@@ -937,7 +999,7 @@ class AppHarnessProd(AppHarness):
|
|
|
937
999
|
/ reflex.constants.Dirs.STATIC
|
|
938
1000
|
)
|
|
939
1001
|
error_page_map = {
|
|
940
|
-
404: web_root / "404.html",
|
|
1002
|
+
404: web_root / "404" / "index.html",
|
|
941
1003
|
}
|
|
942
1004
|
with Subdir404TCPServer(
|
|
943
1005
|
("", 0),
|
|
@@ -954,9 +1016,11 @@ class AppHarnessProd(AppHarness):
|
|
|
954
1016
|
# Set up the frontend.
|
|
955
1017
|
with chdir(self.app_path):
|
|
956
1018
|
config = reflex.config.get_config()
|
|
1019
|
+
print("Polling for servers...") # for pytest diagnosis #noqa: T201
|
|
957
1020
|
config.api_url = "http://{}:{}".format(
|
|
958
|
-
*self._poll_for_servers().getsockname(),
|
|
1021
|
+
*self._poll_for_servers(timeout=30).getsockname(),
|
|
959
1022
|
)
|
|
1023
|
+
print("Building frontend...") # for pytest diagnosis #noqa: T201
|
|
960
1024
|
|
|
961
1025
|
get_config().loglevel = reflex.constants.LogLevel.INFO
|
|
962
1026
|
|
|
@@ -973,6 +1037,8 @@ class AppHarnessProd(AppHarness):
|
|
|
973
1037
|
env=reflex.constants.Env.PROD,
|
|
974
1038
|
)
|
|
975
1039
|
|
|
1040
|
+
print("Frontend starting...") # for pytest diagnosis #noqa: T201
|
|
1041
|
+
|
|
976
1042
|
self.frontend_thread = threading.Thread(target=self._run_frontend)
|
|
977
1043
|
self.frontend_thread.start()
|
|
978
1044
|
|
|
@@ -996,8 +1062,12 @@ class AppHarnessProd(AppHarness):
|
|
|
996
1062
|
),
|
|
997
1063
|
)
|
|
998
1064
|
self.backend.shutdown = self._get_backend_shutdown_handler()
|
|
1065
|
+
print( # noqa: T201
|
|
1066
|
+
"Creating backend in a new thread..."
|
|
1067
|
+
)
|
|
999
1068
|
self.backend_thread = threading.Thread(target=self.backend.run)
|
|
1000
1069
|
self.backend_thread.start()
|
|
1070
|
+
print("Backend started.") # for pytest diagnosis #noqa: T201
|
|
1001
1071
|
|
|
1002
1072
|
def _poll_for_servers(self, timeout: TimeoutType = None) -> socket.socket:
|
|
1003
1073
|
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:
|