reflex 0.8.12a1__py3-none-any.whl → 0.8.13__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/web/utils/state.js +48 -17
- reflex/app.py +17 -12
- reflex/app_mixins/lifespan.py +11 -0
- reflex/compiler/compiler.py +4 -1
- reflex/components/core/upload.py +53 -14
- reflex/components/core/upload.pyi +8 -0
- reflex/components/react_player/audio.pyi +23 -46
- reflex/components/react_player/react_player.py +159 -19
- reflex/components/react_player/react_player.pyi +40 -45
- reflex/components/react_player/video.pyi +23 -46
- reflex/components/recharts/recharts.py +2 -2
- reflex/constants/colors.py +3 -1
- reflex/constants/installer.py +2 -2
- reflex/environment.py +3 -0
- reflex/istate/proxy.py +36 -36
- reflex/plugins/shared_tailwind.py +1 -1
- reflex/reflex.py +54 -3
- reflex/state.py +15 -5
- reflex/testing.py +14 -0
- reflex/utils/build.py +77 -57
- reflex/utils/exec.py +12 -0
- reflex/utils/export.py +7 -2
- reflex/utils/prerequisites.py +82 -7
- reflex/utils/serializers.py +8 -4
- reflex/utils/token_manager.py +10 -0
- reflex/utils/types.py +2 -0
- reflex/vars/__init__.py +56 -26
- reflex/vars/base.py +20 -113
- reflex/vars/color.py +214 -0
- reflex/vars/number.py +2 -2
- reflex/vars/sequence.py +0 -136
- {reflex-0.8.12a1.dist-info → reflex-0.8.13.dist-info}/METADATA +1 -1
- {reflex-0.8.12a1.dist-info → reflex-0.8.13.dist-info}/RECORD +36 -35
- {reflex-0.8.12a1.dist-info → reflex-0.8.13.dist-info}/WHEEL +0 -0
- {reflex-0.8.12a1.dist-info → reflex-0.8.13.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.12a1.dist-info → reflex-0.8.13.dist-info}/licenses/LICENSE +0 -0
reflex/reflex.py
CHANGED
|
@@ -204,7 +204,7 @@ def _run(
|
|
|
204
204
|
args = (frontend,)
|
|
205
205
|
kwargs = {
|
|
206
206
|
"check_if_schema_up_to_date": True,
|
|
207
|
-
"prerender_routes":
|
|
207
|
+
"prerender_routes": exec.should_prerender_routes(),
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
# Granian fails if the app is already imported.
|
|
@@ -216,9 +216,12 @@ def _run(
|
|
|
216
216
|
*args,
|
|
217
217
|
**kwargs,
|
|
218
218
|
)
|
|
219
|
-
compile_future.result()
|
|
219
|
+
return_result = compile_future.result()
|
|
220
220
|
else:
|
|
221
|
-
app_task(*args, **kwargs)
|
|
221
|
+
return_result = app_task(*args, **kwargs)
|
|
222
|
+
|
|
223
|
+
if not return_result:
|
|
224
|
+
raise SystemExit(1)
|
|
222
225
|
|
|
223
226
|
# Get the frontend and backend commands, based on the environment.
|
|
224
227
|
setup_frontend = frontend_cmd = backend_cmd = None
|
|
@@ -422,6 +425,21 @@ def compile(dry: bool, rich: bool):
|
|
|
422
425
|
default=constants.Env.PROD.value,
|
|
423
426
|
help="The environment to export the app in.",
|
|
424
427
|
)
|
|
428
|
+
@click.option(
|
|
429
|
+
"--exclude-from-backend",
|
|
430
|
+
"backend_excluded_dirs",
|
|
431
|
+
multiple=True,
|
|
432
|
+
type=click.Path(exists=True, path_type=Path, resolve_path=True),
|
|
433
|
+
help="Files or directories to exclude from the backend zip. Can be used multiple times.",
|
|
434
|
+
)
|
|
435
|
+
@click.option(
|
|
436
|
+
"--server-side-rendering/--no-server-side-rendering",
|
|
437
|
+
"--ssr/--no-ssr",
|
|
438
|
+
"ssr",
|
|
439
|
+
default=True,
|
|
440
|
+
is_flag=True,
|
|
441
|
+
help="Whether to enable server side rendering for the frontend.",
|
|
442
|
+
)
|
|
425
443
|
def export(
|
|
426
444
|
zip: bool,
|
|
427
445
|
frontend_only: bool,
|
|
@@ -429,11 +447,18 @@ def export(
|
|
|
429
447
|
zip_dest_dir: str,
|
|
430
448
|
upload_db_file: bool,
|
|
431
449
|
env: LITERAL_ENV,
|
|
450
|
+
backend_excluded_dirs: tuple[Path, ...] = (),
|
|
451
|
+
ssr: bool = True,
|
|
432
452
|
):
|
|
433
453
|
"""Export the app to a zip file."""
|
|
434
454
|
from reflex.utils import export as export_utils
|
|
435
455
|
from reflex.utils import prerequisites
|
|
436
456
|
|
|
457
|
+
if not environment.REFLEX_SSR.is_set():
|
|
458
|
+
environment.REFLEX_SSR.set(ssr)
|
|
459
|
+
elif environment.REFLEX_SSR.get() != ssr:
|
|
460
|
+
ssr = environment.REFLEX_SSR.get()
|
|
461
|
+
|
|
437
462
|
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.EXPORT)
|
|
438
463
|
|
|
439
464
|
should_frontend_run, should_backend_run = prerequisites.check_running_mode(
|
|
@@ -455,6 +480,8 @@ def export(
|
|
|
455
480
|
upload_db_file=upload_db_file,
|
|
456
481
|
env=constants.Env.DEV if env == constants.Env.DEV else constants.Env.PROD,
|
|
457
482
|
loglevel=config.loglevel.subprocess_level(),
|
|
483
|
+
backend_excluded_dirs=backend_excluded_dirs,
|
|
484
|
+
prerender_routes=ssr,
|
|
458
485
|
)
|
|
459
486
|
|
|
460
487
|
|
|
@@ -660,6 +687,21 @@ def makemigrations(message: str | None):
|
|
|
660
687
|
"--config",
|
|
661
688
|
help="path to the config file",
|
|
662
689
|
)
|
|
690
|
+
@click.option(
|
|
691
|
+
"--exclude-from-backend",
|
|
692
|
+
"backend_excluded_dirs",
|
|
693
|
+
multiple=True,
|
|
694
|
+
type=click.Path(exists=True, path_type=Path, resolve_path=True),
|
|
695
|
+
help="Files or directories to exclude from the backend zip. Can be used multiple times.",
|
|
696
|
+
)
|
|
697
|
+
@click.option(
|
|
698
|
+
"--server-side-rendering/--no-server-side-rendering",
|
|
699
|
+
"--ssr/--no-ssr",
|
|
700
|
+
"ssr",
|
|
701
|
+
default=True,
|
|
702
|
+
is_flag=True,
|
|
703
|
+
help="Whether to enable server side rendering for the frontend.",
|
|
704
|
+
)
|
|
663
705
|
def deploy(
|
|
664
706
|
app_name: str | None,
|
|
665
707
|
app_id: str | None,
|
|
@@ -673,6 +715,8 @@ def deploy(
|
|
|
673
715
|
project_name: str | None,
|
|
674
716
|
token: str | None,
|
|
675
717
|
config_path: str | None,
|
|
718
|
+
backend_excluded_dirs: tuple[Path, ...] = (),
|
|
719
|
+
ssr: bool = True,
|
|
676
720
|
):
|
|
677
721
|
"""Deploy the app to the Reflex hosting service."""
|
|
678
722
|
from reflex_cli.utils import dependency
|
|
@@ -690,6 +734,11 @@ def deploy(
|
|
|
690
734
|
|
|
691
735
|
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.DEPLOY)
|
|
692
736
|
|
|
737
|
+
if not environment.REFLEX_SSR.is_set():
|
|
738
|
+
environment.REFLEX_SSR.set(ssr)
|
|
739
|
+
elif environment.REFLEX_SSR.get() != ssr:
|
|
740
|
+
ssr = environment.REFLEX_SSR.get()
|
|
741
|
+
|
|
693
742
|
# Only check requirements if interactive.
|
|
694
743
|
# There is user interaction for requirements update.
|
|
695
744
|
if interactive:
|
|
@@ -721,6 +770,8 @@ def deploy(
|
|
|
721
770
|
zipping=zipping,
|
|
722
771
|
loglevel=config.loglevel.subprocess_level(),
|
|
723
772
|
upload_db_file=upload_db,
|
|
773
|
+
backend_excluded_dirs=backend_excluded_dirs,
|
|
774
|
+
prerender_routes=ssr,
|
|
724
775
|
)
|
|
725
776
|
),
|
|
726
777
|
regions=list(region),
|
reflex/state.py
CHANGED
|
@@ -7,6 +7,7 @@ import builtins
|
|
|
7
7
|
import contextlib
|
|
8
8
|
import copy
|
|
9
9
|
import dataclasses
|
|
10
|
+
import datetime
|
|
10
11
|
import functools
|
|
11
12
|
import inspect
|
|
12
13
|
import pickle
|
|
@@ -41,7 +42,7 @@ from reflex.event import (
|
|
|
41
42
|
from reflex.istate import HANDLED_PICKLE_ERRORS, debug_failed_pickles
|
|
42
43
|
from reflex.istate.data import RouterData
|
|
43
44
|
from reflex.istate.proxy import ImmutableMutableProxy as ImmutableMutableProxy
|
|
44
|
-
from reflex.istate.proxy import MutableProxy, StateProxy
|
|
45
|
+
from reflex.istate.proxy import MutableProxy, StateProxy, is_mutable_type
|
|
45
46
|
from reflex.istate.storage import ClientStorageBase
|
|
46
47
|
from reflex.model import Model
|
|
47
48
|
from reflex.utils import console, format, prerequisites, types
|
|
@@ -306,6 +307,14 @@ async def _resolve_delta(delta: Delta) -> Delta:
|
|
|
306
307
|
return delta
|
|
307
308
|
|
|
308
309
|
|
|
310
|
+
_deserializers = {
|
|
311
|
+
int: int,
|
|
312
|
+
float: float,
|
|
313
|
+
datetime.datetime: datetime.datetime.fromisoformat,
|
|
314
|
+
datetime.date: datetime.date.fromisoformat,
|
|
315
|
+
datetime.time: datetime.time.fromisoformat,
|
|
316
|
+
}
|
|
317
|
+
|
|
309
318
|
all_base_state_classes: dict[str, None] = {}
|
|
310
319
|
|
|
311
320
|
|
|
@@ -1359,7 +1368,7 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
1359
1368
|
if parent_state is not None:
|
|
1360
1369
|
return getattr(parent_state, name)
|
|
1361
1370
|
|
|
1362
|
-
if
|
|
1371
|
+
if is_mutable_type(type(value)) and (
|
|
1363
1372
|
name in super().__getattribute__("base_vars") or name in backend_vars
|
|
1364
1373
|
):
|
|
1365
1374
|
# track changes in mutable containers (list, dict, set, etc)
|
|
@@ -1872,11 +1881,12 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
1872
1881
|
hinted_args is tuple or hinted_args is tuple
|
|
1873
1882
|
):
|
|
1874
1883
|
payload[arg] = tuple(value)
|
|
1875
|
-
elif
|
|
1876
|
-
|
|
1884
|
+
elif (
|
|
1885
|
+
isinstance(value, str)
|
|
1886
|
+
and (deserializer := _deserializers.get(hinted_args)) is not None
|
|
1877
1887
|
):
|
|
1878
1888
|
try:
|
|
1879
|
-
payload[arg] =
|
|
1889
|
+
payload[arg] = deserializer(value)
|
|
1880
1890
|
except ValueError:
|
|
1881
1891
|
msg = f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
|
|
1882
1892
|
raise ValueError(msg) from None
|
reflex/testing.py
CHANGED
|
@@ -47,6 +47,7 @@ from reflex.state import (
|
|
|
47
47
|
)
|
|
48
48
|
from reflex.utils import console, js_runtimes
|
|
49
49
|
from reflex.utils.export import export
|
|
50
|
+
from reflex.utils.token_manager import TokenManager
|
|
50
51
|
from reflex.utils.types import ASGIApp
|
|
51
52
|
|
|
52
53
|
try:
|
|
@@ -774,6 +775,19 @@ class AppHarness:
|
|
|
774
775
|
self.app_instance._state_manager = app_state_manager
|
|
775
776
|
await self.state_manager.close()
|
|
776
777
|
|
|
778
|
+
def token_manager(self) -> TokenManager:
|
|
779
|
+
"""Get the token manager for the app instance.
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
The current token_manager attached to the app's EventNamespace.
|
|
783
|
+
"""
|
|
784
|
+
assert self.app_instance is not None
|
|
785
|
+
app_event_namespace = self.app_instance.event_namespace
|
|
786
|
+
assert app_event_namespace is not None
|
|
787
|
+
app_token_manager = app_event_namespace._token_manager
|
|
788
|
+
assert app_token_manager is not None
|
|
789
|
+
return app_token_manager
|
|
790
|
+
|
|
777
791
|
def poll_for_content(
|
|
778
792
|
self,
|
|
779
793
|
element: WebElement,
|
reflex/utils/build.py
CHANGED
|
@@ -26,14 +26,14 @@ def set_env_json():
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _zip(
|
|
29
|
+
*,
|
|
29
30
|
component_name: constants.ComponentName,
|
|
30
|
-
target:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
files_to_exclude: set[
|
|
36
|
-
top_level_dirs_to_exclude: set[str] | None = None,
|
|
31
|
+
target: Path,
|
|
32
|
+
root_directory: Path,
|
|
33
|
+
exclude_venv_directories: bool,
|
|
34
|
+
include_db_file: bool = False,
|
|
35
|
+
directory_names_to_exclude: set[str] | None = None,
|
|
36
|
+
files_to_exclude: set[Path] | None = None,
|
|
37
37
|
globs_to_include: list[str] | None = None,
|
|
38
38
|
) -> None:
|
|
39
39
|
"""Zip utility function.
|
|
@@ -41,49 +41,62 @@ def _zip(
|
|
|
41
41
|
Args:
|
|
42
42
|
component_name: The name of the component: backend or frontend.
|
|
43
43
|
target: The target zip file.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
root_directory: The root directory to zip.
|
|
45
|
+
exclude_venv_directories: Whether to exclude venv directories.
|
|
46
|
+
include_db_file: Whether to include local sqlite db files.
|
|
47
|
+
directory_names_to_exclude: The directory names to exclude.
|
|
48
48
|
files_to_exclude: The files to exclude.
|
|
49
|
-
|
|
50
|
-
globs_to_include: Apply these globs from the root_dir and always include them in the zip.
|
|
49
|
+
globs_to_include: Apply these globs from the root_directory and always include them in the zip.
|
|
51
50
|
|
|
52
51
|
"""
|
|
53
52
|
target = Path(target)
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
root_directory = Path(root_directory).resolve()
|
|
54
|
+
directory_names_to_exclude = directory_names_to_exclude or set()
|
|
56
55
|
files_to_exclude = files_to_exclude or set()
|
|
57
|
-
files_to_zip: list[
|
|
56
|
+
files_to_zip: list[Path] = []
|
|
58
57
|
# Traverse the root directory in a top-down manner. In this traversal order,
|
|
59
58
|
# we can modify the dirs list in-place to remove directories we don't want to include.
|
|
60
|
-
for
|
|
61
|
-
|
|
59
|
+
for directory_path, subdirectories_names, subfiles_names in os.walk(
|
|
60
|
+
root_directory, topdown=True, followlinks=True
|
|
61
|
+
):
|
|
62
|
+
directory_path = Path(directory_path).resolve()
|
|
62
63
|
# Modify the dirs in-place so excluded and hidden directories are skipped in next traversal.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
for
|
|
66
|
-
if
|
|
67
|
-
and not
|
|
68
|
-
|
|
64
|
+
subdirectories_names[:] = [
|
|
65
|
+
subdirectory_name
|
|
66
|
+
for subdirectory_name in subdirectories_names
|
|
67
|
+
if subdirectory_name not in directory_names_to_exclude
|
|
68
|
+
and not any(
|
|
69
|
+
(directory_path / subdirectory_name).samefile(exclude)
|
|
70
|
+
for exclude in files_to_exclude
|
|
71
|
+
if exclude.exists()
|
|
72
|
+
)
|
|
73
|
+
and not subdirectory_name.startswith(".")
|
|
74
|
+
and (
|
|
75
|
+
not exclude_venv_directories
|
|
76
|
+
or not _looks_like_venv_directory(directory_path / subdirectory_name)
|
|
77
|
+
)
|
|
69
78
|
]
|
|
70
|
-
# If we are at the top level with root_dir, exclude the top level dirs.
|
|
71
|
-
if top_level_dirs_to_exclude and root == root_dir:
|
|
72
|
-
dirs[:] = [d for d in dirs if d not in top_level_dirs_to_exclude]
|
|
73
79
|
# Modify the files in-place so the hidden files and db files are excluded.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for
|
|
77
|
-
if not
|
|
80
|
+
subfiles_names[:] = [
|
|
81
|
+
subfile_name
|
|
82
|
+
for subfile_name in subfiles_names
|
|
83
|
+
if not subfile_name.startswith(".")
|
|
84
|
+
and (include_db_file or not subfile_name.endswith(".db"))
|
|
78
85
|
]
|
|
79
86
|
files_to_zip += [
|
|
80
|
-
|
|
87
|
+
directory_path / subfile_name
|
|
88
|
+
for subfile_name in subfiles_names
|
|
89
|
+
if not any(
|
|
90
|
+
(directory_path / subfile_name).samefile(excluded_file)
|
|
91
|
+
for excluded_file in files_to_exclude
|
|
92
|
+
if excluded_file.exists()
|
|
93
|
+
)
|
|
81
94
|
]
|
|
82
95
|
if globs_to_include:
|
|
83
96
|
for glob in globs_to_include:
|
|
84
97
|
files_to_zip += [
|
|
85
|
-
|
|
86
|
-
for file in
|
|
98
|
+
file
|
|
99
|
+
for file in root_directory.glob(glob)
|
|
87
100
|
if file.name not in files_to_exclude
|
|
88
101
|
]
|
|
89
102
|
# Create a progress bar for zipping the component.
|
|
@@ -100,14 +113,15 @@ def _zip(
|
|
|
100
113
|
for file in files_to_zip:
|
|
101
114
|
console.debug(f"{target}: {file}", progress=progress)
|
|
102
115
|
progress.advance(task)
|
|
103
|
-
zipf.write(file, Path(file).relative_to(
|
|
116
|
+
zipf.write(file, Path(file).relative_to(root_directory))
|
|
104
117
|
|
|
105
118
|
|
|
106
119
|
def zip_app(
|
|
107
120
|
frontend: bool = True,
|
|
108
121
|
backend: bool = True,
|
|
109
122
|
zip_dest_dir: str | Path | None = None,
|
|
110
|
-
|
|
123
|
+
include_db_file: bool = False,
|
|
124
|
+
backend_excluded_dirs: tuple[Path, ...] = (),
|
|
111
125
|
):
|
|
112
126
|
"""Zip up the app.
|
|
113
127
|
|
|
@@ -115,41 +129,41 @@ def zip_app(
|
|
|
115
129
|
frontend: Whether to zip up the frontend app.
|
|
116
130
|
backend: Whether to zip up the backend app.
|
|
117
131
|
zip_dest_dir: The directory to export the zip file to.
|
|
118
|
-
|
|
132
|
+
include_db_file: Whether to include the database file.
|
|
133
|
+
backend_excluded_dirs: A tuple of files or directories to exclude from the backend zip. Defaults to ().
|
|
119
134
|
"""
|
|
120
135
|
zip_dest_dir = zip_dest_dir or Path.cwd()
|
|
121
136
|
zip_dest_dir = Path(zip_dest_dir)
|
|
122
137
|
files_to_exclude = {
|
|
123
|
-
constants.ComponentName.FRONTEND.zip(),
|
|
124
|
-
constants.ComponentName.BACKEND.zip(),
|
|
138
|
+
Path(constants.ComponentName.FRONTEND.zip()).resolve(),
|
|
139
|
+
Path(constants.ComponentName.BACKEND.zip()).resolve(),
|
|
125
140
|
}
|
|
126
141
|
|
|
127
142
|
if frontend:
|
|
128
143
|
_zip(
|
|
129
144
|
component_name=constants.ComponentName.FRONTEND,
|
|
130
145
|
target=zip_dest_dir / constants.ComponentName.FRONTEND.zip(),
|
|
131
|
-
|
|
146
|
+
root_directory=prerequisites.get_web_dir() / constants.Dirs.STATIC,
|
|
132
147
|
files_to_exclude=files_to_exclude,
|
|
133
|
-
|
|
148
|
+
exclude_venv_directories=False,
|
|
134
149
|
)
|
|
135
150
|
|
|
136
151
|
if backend:
|
|
137
152
|
_zip(
|
|
138
153
|
component_name=constants.ComponentName.BACKEND,
|
|
139
154
|
target=zip_dest_dir / constants.ComponentName.BACKEND.zip(),
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
files_to_exclude=files_to_exclude,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
upload_db_file=upload_db_file,
|
|
155
|
+
root_directory=Path.cwd(),
|
|
156
|
+
directory_names_to_exclude={"__pycache__"},
|
|
157
|
+
files_to_exclude=files_to_exclude | set(backend_excluded_dirs),
|
|
158
|
+
exclude_venv_directories=True,
|
|
159
|
+
include_db_file=include_db_file,
|
|
146
160
|
globs_to_include=[
|
|
147
161
|
str(Path(constants.Dirs.WEB) / constants.Dirs.BACKEND / "*")
|
|
148
162
|
],
|
|
149
163
|
)
|
|
150
164
|
|
|
151
165
|
|
|
152
|
-
def
|
|
166
|
+
def _duplicate_index_html_to_parent_directory(directory: Path):
|
|
153
167
|
"""Duplicate index.html in the child directories to the given directory.
|
|
154
168
|
|
|
155
169
|
This makes accessing /route and /route/ work in production.
|
|
@@ -169,7 +183,7 @@ def _duplicate_index_html_to_parent_dir(directory: Path):
|
|
|
169
183
|
else:
|
|
170
184
|
console.debug(f"Skipping {index_html}, already exists at {target}")
|
|
171
185
|
# Recursively call this function for the child directory.
|
|
172
|
-
|
|
186
|
+
_duplicate_index_html_to_parent_directory(child)
|
|
173
187
|
|
|
174
188
|
|
|
175
189
|
def build():
|
|
@@ -200,11 +214,17 @@ def build():
|
|
|
200
214
|
},
|
|
201
215
|
)
|
|
202
216
|
processes.show_progress("Creating Production Build", process, checkpoints)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
217
|
+
_duplicate_index_html_to_parent_directory(wdir / constants.Dirs.STATIC)
|
|
218
|
+
|
|
219
|
+
spa_fallback = wdir / constants.Dirs.STATIC / constants.ReactRouter.SPA_FALLBACK
|
|
220
|
+
if not spa_fallback.exists():
|
|
221
|
+
spa_fallback = wdir / constants.Dirs.STATIC / "index.html"
|
|
222
|
+
|
|
223
|
+
if spa_fallback.exists():
|
|
224
|
+
path_ops.cp(
|
|
225
|
+
spa_fallback,
|
|
226
|
+
wdir / constants.Dirs.STATIC / "404.html",
|
|
227
|
+
)
|
|
208
228
|
|
|
209
229
|
config = get_config()
|
|
210
230
|
|
|
@@ -247,6 +267,6 @@ def setup_frontend_prod(
|
|
|
247
267
|
build()
|
|
248
268
|
|
|
249
269
|
|
|
250
|
-
def
|
|
251
|
-
|
|
252
|
-
return (
|
|
270
|
+
def _looks_like_venv_directory(directory_to_check: str | Path) -> bool:
|
|
271
|
+
directory_to_check = Path(directory_to_check)
|
|
272
|
+
return (directory_to_check / "pyvenv.cfg").exists()
|
reflex/utils/exec.py
CHANGED
|
@@ -497,6 +497,7 @@ HOTRELOAD_IGNORE_EXTENSIONS = (
|
|
|
497
497
|
"sh",
|
|
498
498
|
"bash",
|
|
499
499
|
"log",
|
|
500
|
+
"db",
|
|
500
501
|
)
|
|
501
502
|
|
|
502
503
|
HOTRELOAD_IGNORE_PATTERNS = (
|
|
@@ -742,6 +743,17 @@ def is_prod_mode() -> bool:
|
|
|
742
743
|
return current_mode == constants.Env.PROD
|
|
743
744
|
|
|
744
745
|
|
|
746
|
+
def should_prerender_routes() -> bool:
|
|
747
|
+
"""Check if the app should prerender routes.
|
|
748
|
+
|
|
749
|
+
Returns:
|
|
750
|
+
True if the app should prerender routes.
|
|
751
|
+
"""
|
|
752
|
+
if not environment.REFLEX_SSR.is_set():
|
|
753
|
+
return is_prod_mode()
|
|
754
|
+
return environment.REFLEX_SSR.get()
|
|
755
|
+
|
|
756
|
+
|
|
745
757
|
def get_compile_context() -> constants.CompileContext:
|
|
746
758
|
"""Check if the app is compiled for deploy.
|
|
747
759
|
|
reflex/utils/export.py
CHANGED
|
@@ -18,6 +18,8 @@ def export(
|
|
|
18
18
|
deploy_url: str | None = None,
|
|
19
19
|
env: constants.Env = constants.Env.PROD,
|
|
20
20
|
loglevel: constants.LogLevel = console._LOG_LEVEL,
|
|
21
|
+
backend_excluded_dirs: tuple[Path, ...] = (),
|
|
22
|
+
prerender_routes: bool = True,
|
|
21
23
|
):
|
|
22
24
|
"""Export the app to a zip file.
|
|
23
25
|
|
|
@@ -31,6 +33,8 @@ def export(
|
|
|
31
33
|
deploy_url: The deploy URL to use. Defaults to None.
|
|
32
34
|
env: The environment to use. Defaults to constants.Env.PROD.
|
|
33
35
|
loglevel: The log level to use. Defaults to console._LOG_LEVEL.
|
|
36
|
+
backend_excluded_dirs: A tuple of files or directories to exclude from the backend zip. Defaults to ().
|
|
37
|
+
prerender_routes: Whether to prerender the routes. Defaults to True.
|
|
34
38
|
"""
|
|
35
39
|
config = get_config()
|
|
36
40
|
|
|
@@ -56,7 +60,7 @@ def export(
|
|
|
56
60
|
|
|
57
61
|
if frontend:
|
|
58
62
|
# Ensure module can be imported and app.compile() is called.
|
|
59
|
-
prerequisites.get_compiled_app(prerender_routes=
|
|
63
|
+
prerequisites.get_compiled_app(prerender_routes=prerender_routes)
|
|
60
64
|
# Set up .web directory and install frontend dependencies.
|
|
61
65
|
build.setup_frontend(Path.cwd())
|
|
62
66
|
|
|
@@ -70,7 +74,8 @@ def export(
|
|
|
70
74
|
frontend=frontend,
|
|
71
75
|
backend=backend,
|
|
72
76
|
zip_dest_dir=zip_dest_dir,
|
|
73
|
-
|
|
77
|
+
include_db_file=upload_db_file,
|
|
78
|
+
backend_excluded_dirs=backend_excluded_dirs,
|
|
74
79
|
)
|
|
75
80
|
|
|
76
81
|
# Post a telemetry event.
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -269,25 +269,100 @@ def get_compiled_app(
|
|
|
269
269
|
return app_module
|
|
270
270
|
|
|
271
271
|
|
|
272
|
+
def _can_colorize() -> bool:
|
|
273
|
+
"""Check if the output can be colorized.
|
|
274
|
+
|
|
275
|
+
Copied from _colorize.can_colorize.
|
|
276
|
+
|
|
277
|
+
https://raw.githubusercontent.com/python/cpython/refs/heads/main/Lib/_colorize.py
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
If the output can be colorized
|
|
281
|
+
"""
|
|
282
|
+
import io
|
|
283
|
+
import os
|
|
284
|
+
|
|
285
|
+
def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
|
|
286
|
+
"""Exception-safe environment retrieval. See gh-128636.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
k: The environment variable key.
|
|
290
|
+
fallback: The fallback value if the environment variable is not set.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
The value of the environment variable or the fallback value.
|
|
294
|
+
"""
|
|
295
|
+
try:
|
|
296
|
+
return os.environ.get(k, fallback)
|
|
297
|
+
except Exception:
|
|
298
|
+
return fallback
|
|
299
|
+
|
|
300
|
+
file = sys.stdout
|
|
301
|
+
|
|
302
|
+
if not sys.flags.ignore_environment:
|
|
303
|
+
if _safe_getenv("PYTHON_COLORS") == "0":
|
|
304
|
+
return False
|
|
305
|
+
if _safe_getenv("PYTHON_COLORS") == "1":
|
|
306
|
+
return True
|
|
307
|
+
if _safe_getenv("NO_COLOR"):
|
|
308
|
+
return False
|
|
309
|
+
if _safe_getenv("FORCE_COLOR"):
|
|
310
|
+
return True
|
|
311
|
+
if _safe_getenv("TERM") == "dumb":
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
if not hasattr(file, "fileno"):
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
if sys.platform == "win32":
|
|
318
|
+
try:
|
|
319
|
+
import nt
|
|
320
|
+
|
|
321
|
+
if not nt._supports_virtual_terminal():
|
|
322
|
+
return False
|
|
323
|
+
except (ImportError, AttributeError):
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
return os.isatty(file.fileno())
|
|
328
|
+
except io.UnsupportedOperation:
|
|
329
|
+
return hasattr(file, "isatty") and file.isatty()
|
|
330
|
+
|
|
331
|
+
|
|
272
332
|
def compile_or_validate_app(
|
|
273
333
|
compile: bool = False,
|
|
274
334
|
check_if_schema_up_to_date: bool = False,
|
|
275
335
|
prerender_routes: bool = False,
|
|
276
|
-
):
|
|
336
|
+
) -> bool:
|
|
277
337
|
"""Compile or validate the app module based on the default config.
|
|
278
338
|
|
|
279
339
|
Args:
|
|
280
340
|
compile: Whether to compile the app.
|
|
281
341
|
check_if_schema_up_to_date: If True, check if the schema is up to date.
|
|
282
342
|
prerender_routes: Whether to prerender routes.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
True if the app was successfully compiled or validated, False otherwise.
|
|
283
346
|
"""
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
347
|
+
try:
|
|
348
|
+
if compile:
|
|
349
|
+
get_compiled_app(
|
|
350
|
+
check_if_schema_up_to_date=check_if_schema_up_to_date,
|
|
351
|
+
prerender_routes=prerender_routes,
|
|
352
|
+
)
|
|
353
|
+
else:
|
|
354
|
+
get_and_validate_app(check_if_schema_up_to_date=check_if_schema_up_to_date)
|
|
355
|
+
except Exception as e:
|
|
356
|
+
import traceback
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
colorize = _can_colorize()
|
|
360
|
+
traceback.print_exception(e, colorize=colorize) # pyright: ignore[reportCallIssue]
|
|
361
|
+
except Exception:
|
|
362
|
+
traceback.print_exception(e)
|
|
363
|
+
return False
|
|
289
364
|
else:
|
|
290
|
-
|
|
365
|
+
return True
|
|
291
366
|
|
|
292
367
|
|
|
293
368
|
def get_redis() -> Redis | None:
|
reflex/utils/serializers.py
CHANGED
|
@@ -20,7 +20,7 @@ from pydantic import BaseModel as BaseModelV2
|
|
|
20
20
|
from pydantic.v1 import BaseModel as BaseModelV1
|
|
21
21
|
|
|
22
22
|
from reflex.base import Base
|
|
23
|
-
from reflex.constants.colors import Color
|
|
23
|
+
from reflex.constants.colors import Color
|
|
24
24
|
from reflex.utils import console, types
|
|
25
25
|
|
|
26
26
|
# Mapping from type to a serializer.
|
|
@@ -413,8 +413,8 @@ def serialize_decimal(value: decimal.Decimal) -> float:
|
|
|
413
413
|
return float(value)
|
|
414
414
|
|
|
415
415
|
|
|
416
|
-
@serializer(to=
|
|
417
|
-
def serialize_color(color: Color) ->
|
|
416
|
+
@serializer(to=dict)
|
|
417
|
+
def serialize_color(color: Color) -> dict:
|
|
418
418
|
"""Serialize a color.
|
|
419
419
|
|
|
420
420
|
Args:
|
|
@@ -423,7 +423,11 @@ def serialize_color(color: Color) -> str:
|
|
|
423
423
|
Returns:
|
|
424
424
|
The serialized color.
|
|
425
425
|
"""
|
|
426
|
-
return
|
|
426
|
+
return {
|
|
427
|
+
"color": color.color,
|
|
428
|
+
"shade": color.shade,
|
|
429
|
+
"alpha": color.alpha,
|
|
430
|
+
}
|
|
427
431
|
|
|
428
432
|
|
|
429
433
|
with contextlib.suppress(ImportError):
|
reflex/utils/token_manager.py
CHANGED
|
@@ -66,6 +66,16 @@ class TokenManager(ABC):
|
|
|
66
66
|
|
|
67
67
|
return LocalTokenManager()
|
|
68
68
|
|
|
69
|
+
async def disconnect_all(self):
|
|
70
|
+
"""Disconnect all tracked tokens when the server is going down."""
|
|
71
|
+
token_sid_pairs: set[tuple[str, str]] = set(self.token_to_sid.items())
|
|
72
|
+
token_sid_pairs.update(
|
|
73
|
+
((token, sid) for sid, token in self.sid_to_token.items())
|
|
74
|
+
)
|
|
75
|
+
# Perform the disconnection logic here
|
|
76
|
+
for token, sid in token_sid_pairs:
|
|
77
|
+
await self.disconnect_token(token, sid)
|
|
78
|
+
|
|
69
79
|
|
|
70
80
|
class LocalTokenManager(TokenManager):
|
|
71
81
|
"""Token manager using local in-memory dictionaries (single worker)."""
|
reflex/utils/types.py
CHANGED
|
@@ -6,6 +6,7 @@ import dataclasses
|
|
|
6
6
|
import sys
|
|
7
7
|
import types
|
|
8
8
|
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
9
|
+
from enum import Enum
|
|
9
10
|
from functools import cached_property, lru_cache
|
|
10
11
|
from types import GenericAlias
|
|
11
12
|
from typing import ( # noqa: UP035
|
|
@@ -1241,6 +1242,7 @@ IMMUTABLE_TYPES = (
|
|
|
1241
1242
|
frozenset,
|
|
1242
1243
|
tuple,
|
|
1243
1244
|
type(None),
|
|
1245
|
+
Enum,
|
|
1244
1246
|
)
|
|
1245
1247
|
|
|
1246
1248
|
|