reflex 0.8.11__py3-none-any.whl → 0.8.12__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 +56 -21
- reflex/app.py +17 -10
- reflex/app_mixins/lifespan.py +11 -0
- reflex/compiler/compiler.py +4 -1
- reflex/compiler/templates.py +1 -1
- reflex/components/core/upload.py +53 -14
- reflex/components/core/upload.pyi +8 -0
- reflex/components/lucide/icon.py +2 -1
- reflex/components/lucide/icon.pyi +2 -1
- reflex/constants/installer.py +4 -4
- reflex/event.py +1 -1
- reflex/istate/manager.py +2 -2
- reflex/istate/proxy.py +36 -36
- reflex/reflex.py +18 -0
- reflex/state.py +2 -2
- reflex/style.py +4 -2
- reflex/testing.py +14 -0
- reflex/utils/build.py +67 -53
- reflex/utils/console.py +17 -1
- reflex/utils/exec.py +4 -2
- reflex/utils/export.py +4 -1
- reflex/utils/token_manager.py +10 -0
- reflex/utils/types.py +2 -0
- reflex/vars/base.py +2 -2
- reflex/vars/object.py +6 -21
- {reflex-0.8.11.dist-info → reflex-0.8.12.dist-info}/METADATA +1 -1
- {reflex-0.8.11.dist-info → reflex-0.8.12.dist-info}/RECORD +30 -30
- {reflex-0.8.11.dist-info → reflex-0.8.12.dist-info}/WHEEL +0 -0
- {reflex-0.8.11.dist-info → reflex-0.8.12.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.11.dist-info → reflex-0.8.12.dist-info}/licenses/LICENSE +0 -0
reflex/reflex.py
CHANGED
|
@@ -422,6 +422,13 @@ def compile(dry: bool, rich: bool):
|
|
|
422
422
|
default=constants.Env.PROD.value,
|
|
423
423
|
help="The environment to export the app in.",
|
|
424
424
|
)
|
|
425
|
+
@click.option(
|
|
426
|
+
"--exclude-from-backend",
|
|
427
|
+
"backend_excluded_dirs",
|
|
428
|
+
multiple=True,
|
|
429
|
+
type=click.Path(exists=True, path_type=Path, resolve_path=True),
|
|
430
|
+
help="Files or directories to exclude from the backend zip. Can be used multiple times.",
|
|
431
|
+
)
|
|
425
432
|
def export(
|
|
426
433
|
zip: bool,
|
|
427
434
|
frontend_only: bool,
|
|
@@ -429,6 +436,7 @@ def export(
|
|
|
429
436
|
zip_dest_dir: str,
|
|
430
437
|
upload_db_file: bool,
|
|
431
438
|
env: LITERAL_ENV,
|
|
439
|
+
backend_excluded_dirs: tuple[Path, ...] = (),
|
|
432
440
|
):
|
|
433
441
|
"""Export the app to a zip file."""
|
|
434
442
|
from reflex.utils import export as export_utils
|
|
@@ -455,6 +463,7 @@ def export(
|
|
|
455
463
|
upload_db_file=upload_db_file,
|
|
456
464
|
env=constants.Env.DEV if env == constants.Env.DEV else constants.Env.PROD,
|
|
457
465
|
loglevel=config.loglevel.subprocess_level(),
|
|
466
|
+
backend_excluded_dirs=backend_excluded_dirs,
|
|
458
467
|
)
|
|
459
468
|
|
|
460
469
|
|
|
@@ -660,6 +669,13 @@ def makemigrations(message: str | None):
|
|
|
660
669
|
"--config",
|
|
661
670
|
help="path to the config file",
|
|
662
671
|
)
|
|
672
|
+
@click.option(
|
|
673
|
+
"--exclude-from-backend",
|
|
674
|
+
"backend_excluded_dirs",
|
|
675
|
+
multiple=True,
|
|
676
|
+
type=click.Path(exists=True, path_type=Path, resolve_path=True),
|
|
677
|
+
help="Files or directories to exclude from the backend zip. Can be used multiple times.",
|
|
678
|
+
)
|
|
663
679
|
def deploy(
|
|
664
680
|
app_name: str | None,
|
|
665
681
|
app_id: str | None,
|
|
@@ -673,6 +689,7 @@ def deploy(
|
|
|
673
689
|
project_name: str | None,
|
|
674
690
|
token: str | None,
|
|
675
691
|
config_path: str | None,
|
|
692
|
+
backend_excluded_dirs: tuple[Path, ...] = (),
|
|
676
693
|
):
|
|
677
694
|
"""Deploy the app to the Reflex hosting service."""
|
|
678
695
|
from reflex_cli.utils import dependency
|
|
@@ -721,6 +738,7 @@ def deploy(
|
|
|
721
738
|
zipping=zipping,
|
|
722
739
|
loglevel=config.loglevel.subprocess_level(),
|
|
723
740
|
upload_db_file=upload_db,
|
|
741
|
+
backend_excluded_dirs=backend_excluded_dirs,
|
|
724
742
|
)
|
|
725
743
|
),
|
|
726
744
|
regions=list(region),
|
reflex/state.py
CHANGED
|
@@ -41,7 +41,7 @@ from reflex.event import (
|
|
|
41
41
|
from reflex.istate import HANDLED_PICKLE_ERRORS, debug_failed_pickles
|
|
42
42
|
from reflex.istate.data import RouterData
|
|
43
43
|
from reflex.istate.proxy import ImmutableMutableProxy as ImmutableMutableProxy
|
|
44
|
-
from reflex.istate.proxy import MutableProxy, StateProxy
|
|
44
|
+
from reflex.istate.proxy import MutableProxy, StateProxy, is_mutable_type
|
|
45
45
|
from reflex.istate.storage import ClientStorageBase
|
|
46
46
|
from reflex.model import Model
|
|
47
47
|
from reflex.utils import console, format, prerequisites, types
|
|
@@ -1359,7 +1359,7 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
1359
1359
|
if parent_state is not None:
|
|
1360
1360
|
return getattr(parent_state, name)
|
|
1361
1361
|
|
|
1362
|
-
if
|
|
1362
|
+
if is_mutable_type(type(value)) and (
|
|
1363
1363
|
name in super().__getattribute__("base_vars") or name in backend_vars
|
|
1364
1364
|
):
|
|
1365
1365
|
# track changes in mutable containers (list, dict, set, etc)
|
reflex/style.py
CHANGED
|
@@ -120,9 +120,11 @@ def convert_item(
|
|
|
120
120
|
Raises:
|
|
121
121
|
ReflexError: If an EventHandler is used as a style value
|
|
122
122
|
"""
|
|
123
|
-
|
|
123
|
+
from reflex.components.component import BaseComponent
|
|
124
|
+
|
|
125
|
+
if isinstance(style_item, (EventHandler, BaseComponent)):
|
|
124
126
|
msg = (
|
|
125
|
-
"
|
|
127
|
+
f"{type(style_item)} cannot be used as style values. "
|
|
126
128
|
"Please use a Var or a literal value."
|
|
127
129
|
)
|
|
128
130
|
raise ReflexError(msg)
|
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,7 +214,7 @@ def build():
|
|
|
200
214
|
},
|
|
201
215
|
)
|
|
202
216
|
processes.show_progress("Creating Production Build", process, checkpoints)
|
|
203
|
-
|
|
217
|
+
_duplicate_index_html_to_parent_directory(wdir / constants.Dirs.STATIC)
|
|
204
218
|
path_ops.cp(
|
|
205
219
|
wdir / constants.Dirs.STATIC / constants.ReactRouter.SPA_FALLBACK,
|
|
206
220
|
wdir / constants.Dirs.STATIC / "404.html",
|
|
@@ -247,6 +261,6 @@ def setup_frontend_prod(
|
|
|
247
261
|
build()
|
|
248
262
|
|
|
249
263
|
|
|
250
|
-
def
|
|
251
|
-
|
|
252
|
-
return (
|
|
264
|
+
def _looks_like_venv_directory(directory_to_check: str | Path) -> bool:
|
|
265
|
+
directory_to_check = Path(directory_to_check)
|
|
266
|
+
return (directory_to_check / "pyvenv.cfg").exists()
|
reflex/utils/console.py
CHANGED
|
@@ -22,6 +22,7 @@ from reflex.utils.decorator import once
|
|
|
22
22
|
|
|
23
23
|
# Console for pretty printing.
|
|
24
24
|
_console = Console()
|
|
25
|
+
_console_stderr = Console(stderr=True)
|
|
25
26
|
|
|
26
27
|
# The current log level.
|
|
27
28
|
_LOG_LEVEL = LogLevel.INFO
|
|
@@ -96,6 +97,21 @@ def print(msg: str, *, dedupe: bool = False, **kwargs):
|
|
|
96
97
|
_console.print(msg, **kwargs)
|
|
97
98
|
|
|
98
99
|
|
|
100
|
+
def _print_stderr(msg: str, *, dedupe: bool = False, **kwargs):
|
|
101
|
+
"""Print a message to stderr.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
msg: The message to print.
|
|
105
|
+
dedupe: If True, suppress multiple console logs of print message.
|
|
106
|
+
kwargs: Keyword arguments to pass to the print function.
|
|
107
|
+
"""
|
|
108
|
+
if dedupe:
|
|
109
|
+
if msg in _EMITTED_PRINTS:
|
|
110
|
+
return
|
|
111
|
+
_EMITTED_PRINTS.add(msg)
|
|
112
|
+
_console_stderr.print(msg, **kwargs)
|
|
113
|
+
|
|
114
|
+
|
|
99
115
|
@once
|
|
100
116
|
def log_file_console():
|
|
101
117
|
"""Create a console that logs to a file.
|
|
@@ -342,7 +358,7 @@ def error(msg: str, *, dedupe: bool = False, **kwargs):
|
|
|
342
358
|
if msg in _EMITTED_ERRORS:
|
|
343
359
|
return
|
|
344
360
|
_EMITTED_ERRORS.add(msg)
|
|
345
|
-
|
|
361
|
+
_print_stderr(f"[red]{msg}[/red]", **kwargs)
|
|
346
362
|
if should_use_log_file_console():
|
|
347
363
|
print_to_log_file(f"[red]{msg}[/red]", **kwargs)
|
|
348
364
|
|
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 = (
|
|
@@ -524,7 +525,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
524
525
|
from granian.log import LogLevels
|
|
525
526
|
from granian.server import Server as Granian
|
|
526
527
|
|
|
527
|
-
from reflex.environment import
|
|
528
|
+
from reflex.environment import _load_dotenv_from_env
|
|
528
529
|
|
|
529
530
|
granian_app = Granian(
|
|
530
531
|
target=get_app_instance_from_file(),
|
|
@@ -538,10 +539,11 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
538
539
|
reload_ignore_worker_failure=True,
|
|
539
540
|
reload_ignore_patterns=HOTRELOAD_IGNORE_PATTERNS,
|
|
540
541
|
reload_tick=100,
|
|
541
|
-
env_files=_paths_from_environment() or None,
|
|
542
542
|
workers_kill_timeout=2,
|
|
543
543
|
)
|
|
544
544
|
|
|
545
|
+
granian_app.on_reload(_load_dotenv_from_env)
|
|
546
|
+
|
|
545
547
|
granian_app.serve()
|
|
546
548
|
|
|
547
549
|
|
reflex/utils/export.py
CHANGED
|
@@ -18,6 +18,7 @@ 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, ...] = (),
|
|
21
22
|
):
|
|
22
23
|
"""Export the app to a zip file.
|
|
23
24
|
|
|
@@ -31,6 +32,7 @@ def export(
|
|
|
31
32
|
deploy_url: The deploy URL to use. Defaults to None.
|
|
32
33
|
env: The environment to use. Defaults to constants.Env.PROD.
|
|
33
34
|
loglevel: The log level to use. Defaults to console._LOG_LEVEL.
|
|
35
|
+
backend_excluded_dirs: A tuple of files or directories to exclude from the backend zip. Defaults to ().
|
|
34
36
|
"""
|
|
35
37
|
config = get_config()
|
|
36
38
|
|
|
@@ -70,7 +72,8 @@ def export(
|
|
|
70
72
|
frontend=frontend,
|
|
71
73
|
backend=backend,
|
|
72
74
|
zip_dest_dir=zip_dest_dir,
|
|
73
|
-
|
|
75
|
+
include_db_file=upload_db_file,
|
|
76
|
+
backend_excluded_dirs=backend_excluded_dirs,
|
|
74
77
|
)
|
|
75
78
|
|
|
76
79
|
# Post a telemetry event.
|
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
|
|
reflex/vars/base.py
CHANGED
|
@@ -2345,7 +2345,7 @@ class ComputedVar(Var[RETURN_TYPE]):
|
|
|
2345
2345
|
def _check_deprecated_return_type(self, instance: BaseState, value: Any) -> None:
|
|
2346
2346
|
if not _isinstance(value, self._var_type, nested=1, treat_var_as_type=False):
|
|
2347
2347
|
console.error(
|
|
2348
|
-
f"Computed var '{type(instance).__name__}.{self.
|
|
2348
|
+
f"Computed var '{type(instance).__name__}.{self._name}' must return"
|
|
2349
2349
|
f" a value of type '{escape(str(self._var_type))}', got '{value!s}' of type {type(value)}."
|
|
2350
2350
|
)
|
|
2351
2351
|
|
|
@@ -2395,7 +2395,7 @@ class ComputedVar(Var[RETURN_TYPE]):
|
|
|
2395
2395
|
except Exception as e:
|
|
2396
2396
|
console.warn(
|
|
2397
2397
|
"Failed to automatically determine dependencies for computed var "
|
|
2398
|
-
f"{objclass.__name__}.{self.
|
|
2398
|
+
f"{objclass.__name__}.{self._name}: {e}. "
|
|
2399
2399
|
"Provide static_deps and set auto_deps=False to suppress this warning."
|
|
2400
2400
|
)
|
|
2401
2401
|
return d
|
reflex/vars/object.py
CHANGED
|
@@ -479,14 +479,9 @@ def object_keys_operation(value: ObjectVar):
|
|
|
479
479
|
Returns:
|
|
480
480
|
The keys of the object.
|
|
481
481
|
"""
|
|
482
|
-
if not types.is_optional(value._var_type):
|
|
483
|
-
return var_operation_return(
|
|
484
|
-
js_expression=f"Object.keys({value})",
|
|
485
|
-
var_type=list[str],
|
|
486
|
-
)
|
|
487
482
|
return var_operation_return(
|
|
488
|
-
js_expression=f"
|
|
489
|
-
var_type=
|
|
483
|
+
js_expression=f"Object.keys({value} ?? {{}})",
|
|
484
|
+
var_type=list[str],
|
|
490
485
|
)
|
|
491
486
|
|
|
492
487
|
|
|
@@ -500,14 +495,9 @@ def object_values_operation(value: ObjectVar):
|
|
|
500
495
|
Returns:
|
|
501
496
|
The values of the object.
|
|
502
497
|
"""
|
|
503
|
-
if not types.is_optional(value._var_type):
|
|
504
|
-
return var_operation_return(
|
|
505
|
-
js_expression=f"Object.values({value})",
|
|
506
|
-
var_type=list[value._value_type()],
|
|
507
|
-
)
|
|
508
498
|
return var_operation_return(
|
|
509
|
-
js_expression=f"
|
|
510
|
-
var_type=
|
|
499
|
+
js_expression=f"Object.values({value} ?? {{}})",
|
|
500
|
+
var_type=list[value._value_type()],
|
|
511
501
|
)
|
|
512
502
|
|
|
513
503
|
|
|
@@ -521,14 +511,9 @@ def object_entries_operation(value: ObjectVar):
|
|
|
521
511
|
Returns:
|
|
522
512
|
The entries of the object.
|
|
523
513
|
"""
|
|
524
|
-
if not types.is_optional(value._var_type):
|
|
525
|
-
return var_operation_return(
|
|
526
|
-
js_expression=f"Object.entries({value})",
|
|
527
|
-
var_type=list[tuple[str, value._value_type()]],
|
|
528
|
-
)
|
|
529
514
|
return var_operation_return(
|
|
530
|
-
js_expression=f"
|
|
531
|
-
var_type=
|
|
515
|
+
js_expression=f"Object.entries({value} ?? {{}})",
|
|
516
|
+
var_type=list[tuple[str, value._value_type()]],
|
|
532
517
|
)
|
|
533
518
|
|
|
534
519
|
|