reflex 0.8.12a1__py3-none-any.whl → 0.8.13a1__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/react_player.py +159 -19
- 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.13a1.dist-info}/METADATA +1 -1
- {reflex-0.8.12a1.dist-info → reflex-0.8.13a1.dist-info}/RECORD +33 -32
- {reflex-0.8.12a1.dist-info → reflex-0.8.13a1.dist-info}/WHEEL +0 -0
- {reflex-0.8.12a1.dist-info → reflex-0.8.13a1.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.12a1.dist-info → reflex-0.8.13a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,7 +8,7 @@ from reflex.components.component import Component, MemoizationLeaf, NoSSRCompone
|
|
|
8
8
|
class Recharts(Component):
|
|
9
9
|
"""A component that wraps a recharts lib."""
|
|
10
10
|
|
|
11
|
-
library = "recharts@3.2.
|
|
11
|
+
library = "recharts@3.2.1"
|
|
12
12
|
|
|
13
13
|
def _get_style(self) -> dict:
|
|
14
14
|
return {"wrapperStyle": self.style}
|
|
@@ -17,7 +17,7 @@ class Recharts(Component):
|
|
|
17
17
|
class RechartsCharts(NoSSRComponent, MemoizationLeaf):
|
|
18
18
|
"""A component that wraps a recharts lib."""
|
|
19
19
|
|
|
20
|
-
library = "recharts@3.2.
|
|
20
|
+
library = "recharts@3.2.1"
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]
|
reflex/constants/colors.py
CHANGED
reflex/constants/installer.py
CHANGED
|
@@ -143,11 +143,11 @@ class PackageJson(SimpleNamespace):
|
|
|
143
143
|
"postcss-import": "16.1.1",
|
|
144
144
|
"@react-router/dev": _react_router_version,
|
|
145
145
|
"@react-router/fs-routes": _react_router_version,
|
|
146
|
-
"vite": "npm:rolldown-vite@7.1.
|
|
146
|
+
"vite": "npm:rolldown-vite@7.1.12",
|
|
147
147
|
}
|
|
148
148
|
OVERRIDES = {
|
|
149
149
|
# This should always match the `react` version in DEPENDENCIES for recharts compatibility.
|
|
150
150
|
"react-is": _react_version,
|
|
151
151
|
"cookie": "1.0.2",
|
|
152
|
-
"vite": "npm:rolldown-vite@7.1.
|
|
152
|
+
"vite": "npm:rolldown-vite@7.1.12",
|
|
153
153
|
}
|
reflex/environment.py
CHANGED
|
@@ -657,6 +657,9 @@ class EnvironmentVariables:
|
|
|
657
657
|
# Whether to force a full reload on changes.
|
|
658
658
|
VITE_FORCE_FULL_RELOAD: EnvVar[bool] = env_var(False)
|
|
659
659
|
|
|
660
|
+
# Whether to enable SSR for the frontend.
|
|
661
|
+
REFLEX_SSR: EnvVar[bool] = env_var(True)
|
|
662
|
+
|
|
660
663
|
|
|
661
664
|
environment = EnvironmentVariables()
|
|
662
665
|
|
reflex/istate/proxy.py
CHANGED
|
@@ -71,10 +71,15 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
71
71
|
state_instance: The state instance to proxy.
|
|
72
72
|
parent_state_proxy: The parent state proxy, for linked mutability and context tracking.
|
|
73
73
|
"""
|
|
74
|
+
from reflex.state import _substate_key
|
|
75
|
+
|
|
74
76
|
super().__init__(state_instance)
|
|
75
|
-
# compile is not relevant to backend logic
|
|
76
77
|
self._self_app = prerequisites.get_and_validate_app().app
|
|
77
78
|
self._self_substate_path = tuple(state_instance.get_full_name().split("."))
|
|
79
|
+
self._self_substate_token = _substate_key(
|
|
80
|
+
state_instance.router.session.client_token,
|
|
81
|
+
self._self_substate_path,
|
|
82
|
+
)
|
|
78
83
|
self._self_actx = None
|
|
79
84
|
self._self_mutable = False
|
|
80
85
|
self._self_actx_lock = asyncio.Lock()
|
|
@@ -127,16 +132,9 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
127
132
|
msg = "The state is already mutable. Do not nest `async with self` blocks."
|
|
128
133
|
raise ImmutableStateError(msg)
|
|
129
134
|
|
|
130
|
-
from reflex.state import _substate_key
|
|
131
|
-
|
|
132
135
|
await self._self_actx_lock.acquire()
|
|
133
136
|
self._self_actx_lock_holder = current_task
|
|
134
|
-
self._self_actx = self._self_app.modify_state(
|
|
135
|
-
token=_substate_key(
|
|
136
|
-
self.__wrapped__.router.session.client_token,
|
|
137
|
-
self._self_substate_path,
|
|
138
|
-
)
|
|
139
|
-
)
|
|
137
|
+
self._self_actx = self._self_app.modify_state(token=self._self_substate_token)
|
|
140
138
|
mutable_state = await self._self_actx.__aenter__()
|
|
141
139
|
super().__setattr__(
|
|
142
140
|
"__wrapped__", mutable_state.get_substate(self._self_substate_path)
|
|
@@ -378,17 +376,6 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
378
376
|
pydantic.BaseModel.__dict__
|
|
379
377
|
)
|
|
380
378
|
|
|
381
|
-
# These types will be wrapped in MutableProxy
|
|
382
|
-
__mutable_types__ = (
|
|
383
|
-
list,
|
|
384
|
-
dict,
|
|
385
|
-
set,
|
|
386
|
-
Base,
|
|
387
|
-
DeclarativeBase,
|
|
388
|
-
BaseModelV2,
|
|
389
|
-
BaseModelV1,
|
|
390
|
-
)
|
|
391
|
-
|
|
392
379
|
# Dynamically generated classes for tracking dataclass mutations.
|
|
393
380
|
__dataclass_proxies__: dict[type, type] = {}
|
|
394
381
|
|
|
@@ -469,20 +456,6 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
469
456
|
return wrapped(*args, **(kwargs or {}))
|
|
470
457
|
return None
|
|
471
458
|
|
|
472
|
-
@classmethod
|
|
473
|
-
def _is_mutable_type(cls, value: Any) -> bool:
|
|
474
|
-
"""Check if a value is of a mutable type and should be wrapped.
|
|
475
|
-
|
|
476
|
-
Args:
|
|
477
|
-
value: The value to check.
|
|
478
|
-
|
|
479
|
-
Returns:
|
|
480
|
-
Whether the value is of a mutable type.
|
|
481
|
-
"""
|
|
482
|
-
return isinstance(value, cls.__mutable_types__) or (
|
|
483
|
-
dataclasses.is_dataclass(value) and not isinstance(value, Var)
|
|
484
|
-
)
|
|
485
|
-
|
|
486
459
|
@staticmethod
|
|
487
460
|
def _is_called_from_dataclasses_internal() -> bool:
|
|
488
461
|
"""Check if the current function is called from dataclasses helper.
|
|
@@ -514,7 +487,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
514
487
|
if self._is_called_from_dataclasses_internal():
|
|
515
488
|
return value
|
|
516
489
|
# Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
|
|
517
|
-
if
|
|
490
|
+
if is_mutable_type(type(value)) and not isinstance(value, MutableProxy):
|
|
518
491
|
base_cls = globals()[self.__base_proxy__]
|
|
519
492
|
return base_cls(
|
|
520
493
|
wrapped=value,
|
|
@@ -575,7 +548,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
575
548
|
self._wrap_recursive_decorator,
|
|
576
549
|
)
|
|
577
550
|
|
|
578
|
-
if
|
|
551
|
+
if is_mutable_type(type(value)) and __name not in (
|
|
579
552
|
"__wrapped__",
|
|
580
553
|
"_self_state",
|
|
581
554
|
"__dict__",
|
|
@@ -764,3 +737,30 @@ class ImmutableMutableProxy(MutableProxy):
|
|
|
764
737
|
return super()._mark_dirty(
|
|
765
738
|
wrapped=wrapped, instance=instance, args=args, kwargs=kwargs
|
|
766
739
|
)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
# These types will be wrapped in MutableProxy
|
|
743
|
+
MUTABLE_TYPES = (
|
|
744
|
+
list,
|
|
745
|
+
dict,
|
|
746
|
+
set,
|
|
747
|
+
Base,
|
|
748
|
+
DeclarativeBase,
|
|
749
|
+
BaseModelV2,
|
|
750
|
+
BaseModelV1,
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
@functools.lru_cache(maxsize=1024)
|
|
755
|
+
def is_mutable_type(type_: type) -> bool:
|
|
756
|
+
"""Check if a type is mutable and should be wrapped.
|
|
757
|
+
|
|
758
|
+
Args:
|
|
759
|
+
type_: The type to check.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
Whether the type is mutable and should be wrapped.
|
|
763
|
+
"""
|
|
764
|
+
return issubclass(type_, MUTABLE_TYPES) or (
|
|
765
|
+
dataclasses.is_dataclass(type_) and not issubclass(type_, Var)
|
|
766
|
+
)
|
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.
|