reflex 0.5.2a1__py3-none-any.whl → 0.5.3a1__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/postcss.config.js +1 -0
- reflex/.templates/web/styles/tailwind.css +4 -1
- reflex/__init__.py +298 -204
- reflex/__init__.pyi +196 -157
- reflex/app.py +13 -2
- reflex/components/__init__.py +31 -17
- reflex/components/__init__.pyi +26 -0
- reflex/components/base/__init__.py +25 -9
- reflex/components/base/__init__.pyi +26 -0
- reflex/components/base/fragment.py +3 -0
- reflex/components/base/fragment.pyi +2 -0
- reflex/components/base/head.py +3 -0
- reflex/components/base/head.pyi +2 -0
- reflex/components/base/script.py +3 -0
- reflex/components/base/script.pyi +2 -0
- reflex/components/core/__init__.py +51 -37
- reflex/components/core/__init__.pyi +39 -0
- reflex/components/core/banner.py +7 -1
- reflex/components/core/banner.pyi +6 -1
- reflex/components/core/debounce.py +3 -0
- reflex/components/core/debounce.pyi +2 -0
- reflex/components/core/foreach.py +3 -0
- reflex/components/core/html.py +3 -0
- reflex/components/core/html.pyi +2 -0
- reflex/components/core/match.py +3 -0
- reflex/components/core/responsive.py +1 -1
- reflex/components/core/upload.py +5 -2
- reflex/components/core/upload.pyi +4 -2
- reflex/components/datadisplay/__init__.py +17 -8
- reflex/components/datadisplay/__init__.pyi +14 -0
- reflex/components/datadisplay/code.py +3 -0
- reflex/components/datadisplay/code.pyi +2 -0
- reflex/components/datadisplay/dataeditor.py +4 -0
- reflex/components/datadisplay/dataeditor.pyi +3 -0
- reflex/components/el/__init__.py +15 -1
- reflex/components/el/__init__.pyi +227 -0
- reflex/components/el/elements/__init__.py +129 -220
- reflex/components/el/elements/__init__.pyi +341 -0
- reflex/components/el/elements/forms.py +15 -0
- reflex/components/el/elements/forms.pyi +14 -0
- reflex/components/el/elements/inline.py +30 -0
- reflex/components/el/elements/inline.pyi +29 -0
- reflex/components/el/elements/media.py +16 -0
- reflex/components/el/elements/media.pyi +15 -0
- reflex/components/el/elements/metadata.py +7 -0
- reflex/components/el/elements/metadata.pyi +6 -0
- reflex/components/el/elements/other.py +9 -0
- reflex/components/el/elements/other.pyi +8 -0
- reflex/components/el/elements/scripts.py +5 -0
- reflex/components/el/elements/scripts.pyi +4 -0
- reflex/components/el/elements/sectioning.py +17 -0
- reflex/components/el/elements/sectioning.pyi +16 -0
- reflex/components/el/elements/tables.py +12 -0
- reflex/components/el/elements/tables.pyi +11 -0
- reflex/components/el/elements/typography.py +16 -0
- reflex/components/el/elements/typography.pyi +15 -0
- reflex/components/moment/__init__.py +1 -1
- reflex/components/plotly/plotly.py +184 -6
- reflex/components/plotly/plotly.pyi +62 -4
- reflex/components/radix/__init__.py +14 -2
- reflex/components/radix/__init__.pyi +73 -0
- reflex/components/radix/primitives/__init__.py +13 -5
- reflex/components/radix/primitives/__init__.pyi +11 -0
- reflex/components/radix/themes/__init__.py +20 -6
- reflex/components/radix/themes/__init__.pyi +13 -0
- reflex/components/radix/themes/base.py +26 -20
- reflex/components/radix/themes/base.pyi +4 -1
- reflex/components/radix/themes/color_mode.py +3 -1
- reflex/components/radix/themes/color_mode.pyi +3 -1
- reflex/components/radix/themes/components/__init__.py +11 -79
- reflex/components/radix/themes/components/__init__.pyi +44 -0
- reflex/components/radix/themes/components/alert_dialog.py +2 -2
- reflex/components/radix/themes/components/alert_dialog.pyi +2 -2
- reflex/components/radix/themes/components/badge.py +2 -2
- reflex/components/radix/themes/components/badge.pyi +2 -2
- reflex/components/radix/themes/components/button.py +2 -2
- reflex/components/radix/themes/components/button.pyi +2 -2
- reflex/components/radix/themes/components/callout.py +4 -4
- reflex/components/radix/themes/components/callout.pyi +4 -4
- reflex/components/radix/themes/components/card.py +2 -2
- reflex/components/radix/themes/components/card.pyi +2 -2
- reflex/components/radix/themes/components/dialog.py +2 -2
- reflex/components/radix/themes/components/dialog.pyi +2 -2
- reflex/components/radix/themes/components/hover_card.py +2 -2
- reflex/components/radix/themes/components/hover_card.pyi +2 -2
- reflex/components/radix/themes/components/icon_button.py +2 -2
- reflex/components/radix/themes/components/icon_button.pyi +2 -2
- reflex/components/radix/themes/components/inset.py +2 -2
- reflex/components/radix/themes/components/inset.pyi +2 -2
- reflex/components/radix/themes/components/popover.py +2 -2
- reflex/components/radix/themes/components/popover.pyi +2 -2
- reflex/components/radix/themes/components/table.py +8 -8
- reflex/components/radix/themes/components/table.pyi +8 -8
- reflex/components/radix/themes/components/text_area.py +11 -2
- reflex/components/radix/themes/components/text_area.pyi +18 -3
- reflex/components/radix/themes/components/text_field.py +3 -3
- reflex/components/radix/themes/components/text_field.pyi +3 -3
- reflex/components/radix/themes/layout/__init__.py +12 -38
- reflex/components/radix/themes/layout/__init__.pyi +21 -0
- reflex/components/radix/themes/layout/box.py +5 -2
- reflex/components/radix/themes/layout/box.pyi +4 -2
- reflex/components/radix/themes/layout/center.py +3 -0
- reflex/components/radix/themes/layout/center.pyi +2 -0
- reflex/components/radix/themes/layout/container.py +5 -2
- reflex/components/radix/themes/layout/container.pyi +4 -2
- reflex/components/radix/themes/layout/flex.py +5 -2
- reflex/components/radix/themes/layout/flex.pyi +4 -2
- reflex/components/radix/themes/layout/grid.py +5 -2
- reflex/components/radix/themes/layout/grid.pyi +4 -2
- reflex/components/radix/themes/layout/list.py +14 -0
- reflex/components/radix/themes/layout/list.pyi +3 -0
- reflex/components/radix/themes/layout/section.py +7 -4
- reflex/components/radix/themes/layout/section.pyi +5 -3
- reflex/components/radix/themes/layout/spacer.py +3 -0
- reflex/components/radix/themes/layout/spacer.pyi +2 -0
- reflex/components/radix/themes/layout/stack.py +5 -0
- reflex/components/radix/themes/layout/stack.pyi +4 -0
- reflex/components/radix/themes/typography/__init__.py +11 -16
- reflex/components/radix/themes/typography/__init__.pyi +12 -0
- reflex/components/radix/themes/typography/blockquote.py +5 -2
- reflex/components/radix/themes/typography/blockquote.pyi +4 -2
- reflex/components/radix/themes/typography/code.py +5 -2
- reflex/components/radix/themes/typography/code.pyi +4 -2
- reflex/components/radix/themes/typography/heading.py +5 -2
- reflex/components/radix/themes/typography/heading.pyi +4 -2
- reflex/components/radix/themes/typography/link.py +3 -0
- reflex/components/radix/themes/typography/link.pyi +2 -0
- reflex/components/radix/themes/typography/text.py +6 -6
- reflex/components/radix/themes/typography/text.pyi +6 -6
- reflex/components/recharts/__init__.py +114 -104
- reflex/components/recharts/__init__.pyi +106 -0
- reflex/components/recharts/cartesian.py +17 -0
- reflex/components/recharts/cartesian.pyi +16 -0
- reflex/components/recharts/charts.py +12 -0
- reflex/components/recharts/charts.pyi +11 -0
- reflex/components/recharts/general.py +7 -0
- reflex/components/recharts/general.pyi +6 -0
- reflex/components/recharts/polar.py +11 -0
- reflex/components/recharts/polar.pyi +9 -0
- reflex/config.py +3 -0
- reflex/constants/__init__.py +0 -2
- reflex/constants/base.py +2 -0
- reflex/constants/base.pyi +5 -0
- reflex/constants/installer.py +2 -1
- reflex/experimental/__init__.py +2 -0
- reflex/experimental/assets.py +56 -0
- reflex/experimental/client_state.py +4 -2
- reflex/experimental/hooks.py +8 -6
- reflex/experimental/layout.py +3 -1
- reflex/state.py +54 -4
- reflex/utils/exec.py +8 -0
- reflex/utils/lazy_loader.py +33 -0
- reflex/utils/prerequisites.py +1 -14
- reflex/utils/pyi_generator.py +71 -20
- reflex/utils/serializers.py +3 -3
- reflex/vars.py +79 -5
- reflex/vars.pyi +16 -0
- {reflex-0.5.2a1.dist-info → reflex-0.5.3a1.dist-info}/METADATA +2 -1
- {reflex-0.5.2a1.dist-info → reflex-0.5.3a1.dist-info}/RECORD +162 -148
- reflex/config.pyi +0 -112
- {reflex-0.5.2a1.dist-info → reflex-0.5.3a1.dist-info}/LICENSE +0 -0
- {reflex-0.5.2a1.dist-info → reflex-0.5.3a1.dist-info}/WHEEL +0 -0
- {reflex-0.5.2a1.dist-info → reflex-0.5.3a1.dist-info}/entry_points.txt +0 -0
reflex/experimental/hooks.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Add standard Hooks wrapper for React."""
|
|
2
2
|
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
3
5
|
from reflex.utils.imports import ImportVar
|
|
4
6
|
from reflex.vars import Var, VarData
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
def _add_react_import(v: Var
|
|
9
|
+
def _add_react_import(v: Optional[Var], tags: Union[str, list]):
|
|
8
10
|
if v is None:
|
|
9
11
|
return
|
|
10
12
|
|
|
@@ -16,7 +18,7 @@ def _add_react_import(v: Var | None, tags: str | list):
|
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
def const(name, value) -> Var
|
|
21
|
+
def const(name, value) -> Optional[Var]:
|
|
20
22
|
"""Create a constant Var.
|
|
21
23
|
|
|
22
24
|
Args:
|
|
@@ -31,7 +33,7 @@ def const(name, value) -> Var | None:
|
|
|
31
33
|
return Var.create(f"const {name} = {value}")
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
def useCallback(func, deps) -> Var
|
|
36
|
+
def useCallback(func, deps) -> Optional[Var]:
|
|
35
37
|
"""Create a useCallback hook with a function and dependencies.
|
|
36
38
|
|
|
37
39
|
Args:
|
|
@@ -49,7 +51,7 @@ def useCallback(func, deps) -> Var | None:
|
|
|
49
51
|
return v
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
def useContext(context) -> Var
|
|
54
|
+
def useContext(context) -> Optional[Var]:
|
|
53
55
|
"""Create a useContext hook with a context.
|
|
54
56
|
|
|
55
57
|
Args:
|
|
@@ -63,7 +65,7 @@ def useContext(context) -> Var | None:
|
|
|
63
65
|
return v
|
|
64
66
|
|
|
65
67
|
|
|
66
|
-
def useRef(default) -> Var
|
|
68
|
+
def useRef(default) -> Optional[Var]:
|
|
67
69
|
"""Create a useRef hook with a default value.
|
|
68
70
|
|
|
69
71
|
Args:
|
|
@@ -77,7 +79,7 @@ def useRef(default) -> Var | None:
|
|
|
77
79
|
return v
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
def useState(var_name, default=None) -> Var
|
|
82
|
+
def useState(var_name, default=None) -> Optional[Var]:
|
|
81
83
|
"""Create a useState hook with a variable name and setter name.
|
|
82
84
|
|
|
83
85
|
Args:
|
reflex/experimental/layout.py
CHANGED
|
@@ -9,7 +9,9 @@ from reflex.components.base.fragment import Fragment
|
|
|
9
9
|
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
|
|
10
10
|
from reflex.components.radix.primitives.drawer import DrawerRoot, drawer
|
|
11
11
|
from reflex.components.radix.themes.components.icon_button import IconButton
|
|
12
|
-
from reflex.components.radix.themes.layout import Box
|
|
12
|
+
from reflex.components.radix.themes.layout.box import Box
|
|
13
|
+
from reflex.components.radix.themes.layout.container import Container
|
|
14
|
+
from reflex.components.radix.themes.layout.stack import HStack
|
|
13
15
|
from reflex.event import call_script
|
|
14
16
|
from reflex.experimental import hooks
|
|
15
17
|
from reflex.state import ComponentState
|
reflex/state.py
CHANGED
|
@@ -7,6 +7,7 @@ import contextlib
|
|
|
7
7
|
import copy
|
|
8
8
|
import functools
|
|
9
9
|
import inspect
|
|
10
|
+
import os
|
|
10
11
|
import traceback
|
|
11
12
|
import uuid
|
|
12
13
|
from abc import ABC, abstractmethod
|
|
@@ -35,6 +36,7 @@ except ModuleNotFoundError:
|
|
|
35
36
|
|
|
36
37
|
import wrapt
|
|
37
38
|
from redis.asyncio import Redis
|
|
39
|
+
from redis.exceptions import ResponseError
|
|
38
40
|
|
|
39
41
|
from reflex import constants
|
|
40
42
|
from reflex.base import Base
|
|
@@ -1536,6 +1538,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1536
1538
|
if actual_var is not None:
|
|
1537
1539
|
actual_var.mark_dirty(instance=self)
|
|
1538
1540
|
|
|
1541
|
+
def _expired_computed_vars(self) -> set[str]:
|
|
1542
|
+
"""Determine ComputedVars that need to be recalculated based on the expiration time.
|
|
1543
|
+
|
|
1544
|
+
Returns:
|
|
1545
|
+
Set of computed vars to include in the delta.
|
|
1546
|
+
"""
|
|
1547
|
+
return set(
|
|
1548
|
+
cvar
|
|
1549
|
+
for cvar in self.computed_vars
|
|
1550
|
+
if self.computed_vars[cvar].needs_update(instance=self)
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1539
1553
|
def _dirty_computed_vars(self, from_vars: set[str] | None = None) -> set[str]:
|
|
1540
1554
|
"""Determine ComputedVars that need to be recalculated based on the given vars.
|
|
1541
1555
|
|
|
@@ -1588,6 +1602,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1588
1602
|
# and always dirty computed vars (cache=False)
|
|
1589
1603
|
delta_vars = (
|
|
1590
1604
|
self.dirty_vars.intersection(self.base_vars)
|
|
1605
|
+
.union(self.dirty_vars.intersection(self.computed_vars))
|
|
1591
1606
|
.union(self._dirty_computed_vars())
|
|
1592
1607
|
.union(self._always_dirty_computed_vars)
|
|
1593
1608
|
)
|
|
@@ -1621,6 +1636,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1621
1636
|
self.parent_state.dirty_substates.add(self.get_name())
|
|
1622
1637
|
self.parent_state._mark_dirty()
|
|
1623
1638
|
|
|
1639
|
+
# Append expired computed vars to dirty_vars to trigger recalculation
|
|
1640
|
+
self.dirty_vars.update(self._expired_computed_vars())
|
|
1641
|
+
|
|
1624
1642
|
# have to mark computed vars dirty to allow access to newly computed
|
|
1625
1643
|
# values within the same ComputedVar function
|
|
1626
1644
|
self._mark_dirty_computed_vars()
|
|
@@ -2622,13 +2640,26 @@ class StateManagerRedis(StateManager):
|
|
|
2622
2640
|
Args:
|
|
2623
2641
|
lock_key: The redis key for the lock.
|
|
2624
2642
|
lock_id: The ID of the lock.
|
|
2643
|
+
|
|
2644
|
+
Raises:
|
|
2645
|
+
ResponseError: when the keyspace config cannot be set.
|
|
2625
2646
|
"""
|
|
2626
2647
|
state_is_locked = False
|
|
2627
2648
|
lock_key_channel = f"__keyspace@0__:{lock_key.decode()}"
|
|
2628
2649
|
# Enable keyspace notifications for the lock key, so we know when it is available.
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2650
|
+
try:
|
|
2651
|
+
await self.redis.config_set(
|
|
2652
|
+
"notify-keyspace-events",
|
|
2653
|
+
self._redis_notify_keyspace_events,
|
|
2654
|
+
)
|
|
2655
|
+
except ResponseError:
|
|
2656
|
+
# Some redis servers only allow out-of-band configuration, so ignore errors here.
|
|
2657
|
+
ignore_config_error = os.environ.get(
|
|
2658
|
+
"REFLEX_IGNORE_REDIS_CONFIG_ERROR",
|
|
2659
|
+
None,
|
|
2660
|
+
)
|
|
2661
|
+
if not ignore_config_error:
|
|
2662
|
+
raise
|
|
2632
2663
|
async with self.redis.pubsub() as pubsub:
|
|
2633
2664
|
await pubsub.psubscribe(lock_key_channel)
|
|
2634
2665
|
while not state_is_locked:
|
|
@@ -2836,6 +2867,11 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
2836
2867
|
]
|
|
2837
2868
|
)
|
|
2838
2869
|
|
|
2870
|
+
# These internal attributes on rx.Base should NOT be wrapped in a MutableProxy.
|
|
2871
|
+
__never_wrap_base_attrs__ = set(Base.__dict__) - {"set"} | set(
|
|
2872
|
+
pydantic.BaseModel.__dict__
|
|
2873
|
+
)
|
|
2874
|
+
|
|
2839
2875
|
__mutable_types__ = (list, dict, set, Base)
|
|
2840
2876
|
|
|
2841
2877
|
def __init__(self, wrapped: Any, state: BaseState, field_name: str):
|
|
@@ -2885,7 +2921,10 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
2885
2921
|
Returns:
|
|
2886
2922
|
The wrapped value.
|
|
2887
2923
|
"""
|
|
2888
|
-
|
|
2924
|
+
# Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
|
|
2925
|
+
if isinstance(value, self.__mutable_types__) and not isinstance(
|
|
2926
|
+
value, MutableProxy
|
|
2927
|
+
):
|
|
2889
2928
|
return type(self)(
|
|
2890
2929
|
wrapped=value,
|
|
2891
2930
|
state=self._self_state,
|
|
@@ -2932,6 +2971,17 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
2932
2971
|
self._wrap_recursive_decorator,
|
|
2933
2972
|
)
|
|
2934
2973
|
|
|
2974
|
+
if (
|
|
2975
|
+
isinstance(self.__wrapped__, Base)
|
|
2976
|
+
and __name not in self.__never_wrap_base_attrs__
|
|
2977
|
+
and hasattr(value, "__func__")
|
|
2978
|
+
):
|
|
2979
|
+
# Wrap methods called on Base subclasses, which might do _anything_
|
|
2980
|
+
return wrapt.FunctionWrapper(
|
|
2981
|
+
functools.partial(value.__func__, self),
|
|
2982
|
+
self._wrap_recursive_decorator,
|
|
2983
|
+
)
|
|
2984
|
+
|
|
2935
2985
|
if isinstance(value, self.__mutable_types__) and __name not in (
|
|
2936
2986
|
"__wrapped__",
|
|
2937
2987
|
"_self_state",
|
reflex/utils/exec.py
CHANGED
|
@@ -113,6 +113,14 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True):
|
|
|
113
113
|
else:
|
|
114
114
|
console.print("New packages detected: Updating app...")
|
|
115
115
|
else:
|
|
116
|
+
if any(
|
|
117
|
+
[x in line for x in ("bin executable does not exist on disk",)]
|
|
118
|
+
):
|
|
119
|
+
console.error(
|
|
120
|
+
"Try setting `REFLEX_USE_NPM=1` and re-running `reflex init` and `reflex run` to use npm instead of bun:\n"
|
|
121
|
+
"`REFLEX_USE_NPM=1 reflex init`\n"
|
|
122
|
+
"`REFLEX_USE_NPM=1 reflex run`"
|
|
123
|
+
)
|
|
116
124
|
new_hash = detect_package_change(json_file_path)
|
|
117
125
|
if new_hash != last_hash:
|
|
118
126
|
last_hash = new_hash
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Module to implement lazy loading in reflex."""
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
import lazy_loader as lazy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def attach(package_name, submodules=None, submod_attrs=None):
|
|
8
|
+
"""Replaces a package's __getattr__, __dir__, and __all__ attributes using lazy.attach.
|
|
9
|
+
The lazy loader __getattr__ doesn't support tuples as list values. We needed to add
|
|
10
|
+
this functionality (tuples) in Reflex to support 'import as _' statements. This function
|
|
11
|
+
reformats the submod_attrs dictionary to flatten the module list before passing it to
|
|
12
|
+
lazy_loader.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
package_name: name of the package.
|
|
16
|
+
submodules : List of submodules to attach.
|
|
17
|
+
submod_attrs : Dictionary of submodule -> list of attributes / functions.
|
|
18
|
+
These attributes are imported as they are used.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
__getattr__, __dir__, __all__
|
|
22
|
+
"""
|
|
23
|
+
_submod_attrs = copy.deepcopy(submod_attrs)
|
|
24
|
+
if _submod_attrs:
|
|
25
|
+
for k, v in _submod_attrs.items():
|
|
26
|
+
# when flattening the list, only keep the alias in the tuple(mod[1])
|
|
27
|
+
_submod_attrs[k] = [
|
|
28
|
+
mod if not isinstance(mod, tuple) else mod[1] for mod in v
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
return lazy.attach(
|
|
32
|
+
package_name=package_name, submodules=submodules, submod_attrs=_submod_attrs
|
|
33
|
+
)
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -332,19 +332,6 @@ def parse_redis_url() -> str | dict | None:
|
|
|
332
332
|
return dict(host=redis_url, port=int(redis_port), db=0)
|
|
333
333
|
|
|
334
334
|
|
|
335
|
-
def get_production_backend_url() -> str:
|
|
336
|
-
"""Get the production backend URL.
|
|
337
|
-
|
|
338
|
-
Returns:
|
|
339
|
-
The production backend URL.
|
|
340
|
-
"""
|
|
341
|
-
config = get_config()
|
|
342
|
-
return constants.PRODUCTION_BACKEND_URL.format(
|
|
343
|
-
username=config.username,
|
|
344
|
-
app_name=config.app_name,
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
|
|
348
335
|
def validate_app_name(app_name: str | None = None) -> str:
|
|
349
336
|
"""Validate the app name.
|
|
350
337
|
|
|
@@ -625,7 +612,7 @@ def _update_next_config(
|
|
|
625
612
|
next_config = {
|
|
626
613
|
"basePath": config.frontend_path or "",
|
|
627
614
|
"compress": config.next_compression,
|
|
628
|
-
"reactStrictMode":
|
|
615
|
+
"reactStrictMode": config.react_strict_mode,
|
|
629
616
|
"trailingSlash": True,
|
|
630
617
|
}
|
|
631
618
|
if transpile_packages:
|
reflex/utils/pyi_generator.py
CHANGED
|
@@ -29,11 +29,9 @@ from reflex.vars import Var
|
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger("pyi_generator")
|
|
31
31
|
|
|
32
|
-
INIT_FILE = Path("reflex/__init__.pyi").resolve()
|
|
33
32
|
PWD = Path(".").resolve()
|
|
34
33
|
|
|
35
34
|
EXCLUDED_FILES = [
|
|
36
|
-
"__init__.py",
|
|
37
35
|
# "app.py",
|
|
38
36
|
"component.py",
|
|
39
37
|
"bare.py",
|
|
@@ -322,6 +320,7 @@ def _extract_class_props_as_ast_nodes(
|
|
|
322
320
|
all_props = []
|
|
323
321
|
kwargs = []
|
|
324
322
|
for target_class in clzs:
|
|
323
|
+
event_triggers = target_class().get_event_triggers()
|
|
325
324
|
# Import from the target class to ensure type hints are resolvable.
|
|
326
325
|
exec(f"from {target_class.__module__} import *", type_hint_globals)
|
|
327
326
|
for name, value in target_class.__annotations__.items():
|
|
@@ -329,6 +328,7 @@ def _extract_class_props_as_ast_nodes(
|
|
|
329
328
|
name in spec.kwonlyargs
|
|
330
329
|
or name in EXCLUDED_PROPS
|
|
331
330
|
or name in all_props
|
|
331
|
+
or name in event_triggers
|
|
332
332
|
or (isinstance(value, str) and "ClassVar" in value)
|
|
333
333
|
):
|
|
334
334
|
continue
|
|
@@ -775,6 +775,13 @@ class StubGenerator(ast.NodeTransformer):
|
|
|
775
775
|
# Remove annotated assignments in Component classes (props)
|
|
776
776
|
return None
|
|
777
777
|
|
|
778
|
+
# remove dunder method assignments for lazy_loader.attach
|
|
779
|
+
for target in node.targets:
|
|
780
|
+
if isinstance(target, ast.Tuple):
|
|
781
|
+
for name in target.elts:
|
|
782
|
+
if isinstance(name, ast.Name) and name.id.startswith("_"):
|
|
783
|
+
return
|
|
784
|
+
|
|
778
785
|
return node
|
|
779
786
|
|
|
780
787
|
def visit_AnnAssign(self, node: ast.AnnAssign) -> ast.AnnAssign | None:
|
|
@@ -805,6 +812,23 @@ class StubGenerator(ast.NodeTransformer):
|
|
|
805
812
|
return node
|
|
806
813
|
|
|
807
814
|
|
|
815
|
+
class InitStubGenerator(StubGenerator):
|
|
816
|
+
"""A node transformer that will generate the stubs for a given init file."""
|
|
817
|
+
|
|
818
|
+
def visit_Import(
|
|
819
|
+
self, node: ast.Import | ast.ImportFrom
|
|
820
|
+
) -> ast.Import | ast.ImportFrom | list[ast.Import | ast.ImportFrom]:
|
|
821
|
+
"""Collect import statements from the init module.
|
|
822
|
+
|
|
823
|
+
Args:
|
|
824
|
+
node: The import node to visit.
|
|
825
|
+
|
|
826
|
+
Returns:
|
|
827
|
+
The modified import node(s).
|
|
828
|
+
"""
|
|
829
|
+
return [node]
|
|
830
|
+
|
|
831
|
+
|
|
808
832
|
class PyiGenerator:
|
|
809
833
|
"""A .pyi file generator that will scan all defined Component in Reflex and
|
|
810
834
|
generate the approriate stub.
|
|
@@ -842,6 +866,37 @@ class PyiGenerator:
|
|
|
842
866
|
pyi_path.write_text("\n".join(pyi_content))
|
|
843
867
|
logger.info(f"Wrote {relpath}")
|
|
844
868
|
|
|
869
|
+
def _get_init_lazy_imports(self, mod, new_tree):
|
|
870
|
+
# retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present.
|
|
871
|
+
sub_mods = getattr(mod, "_SUBMODULES", None)
|
|
872
|
+
sub_mod_attrs = getattr(mod, "_SUBMOD_ATTRS", None)
|
|
873
|
+
|
|
874
|
+
if not sub_mods and not sub_mod_attrs:
|
|
875
|
+
return
|
|
876
|
+
sub_mods_imports = []
|
|
877
|
+
sub_mod_attrs_imports = []
|
|
878
|
+
|
|
879
|
+
if sub_mods:
|
|
880
|
+
sub_mods_imports = [
|
|
881
|
+
f"from . import {mod} as {mod}" for mod in sorted(sub_mods)
|
|
882
|
+
]
|
|
883
|
+
sub_mods_imports.append("")
|
|
884
|
+
|
|
885
|
+
if sub_mod_attrs:
|
|
886
|
+
sub_mod_attrs = {
|
|
887
|
+
attr: mod for mod, attrs in sub_mod_attrs.items() for attr in attrs
|
|
888
|
+
}
|
|
889
|
+
# construct the import statement and handle special cases for aliases
|
|
890
|
+
sub_mod_attrs_imports = [
|
|
891
|
+
f"from .{path} import {mod if not isinstance(mod, tuple) else mod[0]} as {mod if not isinstance(mod, tuple) else mod[1]}"
|
|
892
|
+
for mod, path in sub_mod_attrs.items()
|
|
893
|
+
]
|
|
894
|
+
sub_mod_attrs_imports.append("")
|
|
895
|
+
|
|
896
|
+
text = "\n" + "\n".join([*sub_mods_imports, *sub_mod_attrs_imports])
|
|
897
|
+
text += ast.unparse(new_tree) + "\n"
|
|
898
|
+
return text
|
|
899
|
+
|
|
845
900
|
def _scan_file(self, module_path: Path):
|
|
846
901
|
module_import = (
|
|
847
902
|
_relative_to_pwd(module_path)
|
|
@@ -860,13 +915,22 @@ class PyiGenerator:
|
|
|
860
915
|
and obj != Component
|
|
861
916
|
and inspect.getmodule(obj) == module
|
|
862
917
|
}
|
|
863
|
-
|
|
918
|
+
is_init_file = _relative_to_pwd(module_path).name == "__init__.py"
|
|
919
|
+
if not class_names and not is_init_file:
|
|
864
920
|
return
|
|
865
921
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
922
|
+
if is_init_file:
|
|
923
|
+
new_tree = InitStubGenerator(module, class_names).visit(
|
|
924
|
+
ast.parse(inspect.getsource(module))
|
|
925
|
+
)
|
|
926
|
+
init_imports = self._get_init_lazy_imports(module, new_tree)
|
|
927
|
+
if init_imports:
|
|
928
|
+
self._write_pyi_file(module_path, init_imports)
|
|
929
|
+
else:
|
|
930
|
+
new_tree = StubGenerator(module, class_names).visit(
|
|
931
|
+
ast.parse(inspect.getsource(module))
|
|
932
|
+
)
|
|
933
|
+
self._write_pyi_file(module_path, ast.unparse(new_tree))
|
|
870
934
|
|
|
871
935
|
def _scan_files_multiprocess(self, files: list[Path]):
|
|
872
936
|
with Pool(processes=cpu_count()) as pool:
|
|
@@ -922,16 +986,3 @@ class PyiGenerator:
|
|
|
922
986
|
self._scan_files(file_targets)
|
|
923
987
|
else:
|
|
924
988
|
self._scan_files_multiprocess(file_targets)
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
def generate_init():
|
|
928
|
-
"""Generate a pyi file for the main __init__.py."""
|
|
929
|
-
from reflex import _MAPPING # type: ignore
|
|
930
|
-
|
|
931
|
-
imports = [
|
|
932
|
-
f"from {path if mod != path.rsplit('.')[-1] or mod == 'page' else '.'.join(path.rsplit('.')[:-1])} import {mod} as {mod}"
|
|
933
|
-
for mod, path in _MAPPING.items()
|
|
934
|
-
]
|
|
935
|
-
imports.append("")
|
|
936
|
-
with contextlib.suppress(Exception):
|
|
937
|
-
INIT_FILE.write_text("\n".join(imports))
|
reflex/utils/serializers.py
CHANGED
|
@@ -255,7 +255,7 @@ def serialize_enum(en: Enum) -> str:
|
|
|
255
255
|
en: The enum to serialize.
|
|
256
256
|
|
|
257
257
|
Returns:
|
|
258
|
-
|
|
258
|
+
The serialized enum.
|
|
259
259
|
"""
|
|
260
260
|
return en.value
|
|
261
261
|
|
|
@@ -313,7 +313,7 @@ try:
|
|
|
313
313
|
from plotly.io import to_json
|
|
314
314
|
|
|
315
315
|
@serializer
|
|
316
|
-
def serialize_figure(figure: Figure) ->
|
|
316
|
+
def serialize_figure(figure: Figure) -> dict:
|
|
317
317
|
"""Serialize a plotly figure.
|
|
318
318
|
|
|
319
319
|
Args:
|
|
@@ -322,7 +322,7 @@ try:
|
|
|
322
322
|
Returns:
|
|
323
323
|
The serialized figure.
|
|
324
324
|
"""
|
|
325
|
-
return json.loads(str(to_json(figure)))
|
|
325
|
+
return json.loads(str(to_json(figure)))
|
|
326
326
|
|
|
327
327
|
except ImportError:
|
|
328
328
|
pass
|
reflex/vars.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import dataclasses
|
|
7
|
+
import datetime
|
|
7
8
|
import dis
|
|
8
9
|
import functools
|
|
9
10
|
import inspect
|
|
@@ -1873,13 +1874,26 @@ class ComputedVar(Var, property):
|
|
|
1873
1874
|
# Whether to track dependencies and cache computed values
|
|
1874
1875
|
_cache: bool = dataclasses.field(default=False)
|
|
1875
1876
|
|
|
1876
|
-
|
|
1877
|
+
# The initial value of the computed var
|
|
1878
|
+
_initial_value: Any | types.Unset = dataclasses.field(default=types.Unset())
|
|
1879
|
+
|
|
1880
|
+
# Explicit var dependencies to track
|
|
1881
|
+
_static_deps: set[str] = dataclasses.field(default_factory=set)
|
|
1882
|
+
|
|
1883
|
+
# Whether var dependencies should be auto-determined
|
|
1884
|
+
_auto_deps: bool = dataclasses.field(default=True)
|
|
1885
|
+
|
|
1886
|
+
# Interval at which the computed var should be updated
|
|
1887
|
+
_update_interval: Optional[datetime.timedelta] = dataclasses.field(default=None)
|
|
1877
1888
|
|
|
1878
1889
|
def __init__(
|
|
1879
1890
|
self,
|
|
1880
1891
|
fget: Callable[[BaseState], Any],
|
|
1881
1892
|
initial_value: Any | types.Unset = types.Unset(),
|
|
1882
1893
|
cache: bool = False,
|
|
1894
|
+
deps: Optional[List[Union[str, Var]]] = None,
|
|
1895
|
+
auto_deps: bool = True,
|
|
1896
|
+
interval: Optional[Union[int, datetime.timedelta]] = None,
|
|
1883
1897
|
**kwargs,
|
|
1884
1898
|
):
|
|
1885
1899
|
"""Initialize a ComputedVar.
|
|
@@ -1888,10 +1902,22 @@ class ComputedVar(Var, property):
|
|
|
1888
1902
|
fget: The getter function.
|
|
1889
1903
|
initial_value: The initial value of the computed var.
|
|
1890
1904
|
cache: Whether to cache the computed value.
|
|
1905
|
+
deps: Explicit var dependencies to track.
|
|
1906
|
+
auto_deps: Whether var dependencies should be auto-determined.
|
|
1907
|
+
interval: Interval at which the computed var should be updated.
|
|
1891
1908
|
**kwargs: additional attributes to set on the instance
|
|
1892
1909
|
"""
|
|
1893
1910
|
self._initial_value = initial_value
|
|
1894
1911
|
self._cache = cache
|
|
1912
|
+
if isinstance(interval, int):
|
|
1913
|
+
interval = datetime.timedelta(seconds=interval)
|
|
1914
|
+
self._update_interval = interval
|
|
1915
|
+
if deps is None:
|
|
1916
|
+
deps = []
|
|
1917
|
+
self._static_deps = {
|
|
1918
|
+
dep._var_name if isinstance(dep, Var) else dep for dep in deps
|
|
1919
|
+
}
|
|
1920
|
+
self._auto_deps = auto_deps
|
|
1895
1921
|
property.__init__(self, fget)
|
|
1896
1922
|
kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__)
|
|
1897
1923
|
kwargs["_var_type"] = kwargs.pop("_var_type", self._determine_var_type())
|
|
@@ -1912,6 +1938,9 @@ class ComputedVar(Var, property):
|
|
|
1912
1938
|
fget=kwargs.get("fget", self.fget),
|
|
1913
1939
|
initial_value=kwargs.get("initial_value", self._initial_value),
|
|
1914
1940
|
cache=kwargs.get("cache", self._cache),
|
|
1941
|
+
deps=kwargs.get("deps", self._static_deps),
|
|
1942
|
+
auto_deps=kwargs.get("auto_deps", self._auto_deps),
|
|
1943
|
+
interval=kwargs.get("interval", self._update_interval),
|
|
1915
1944
|
_var_name=kwargs.get("_var_name", self._var_name),
|
|
1916
1945
|
_var_type=kwargs.get("_var_type", self._var_type),
|
|
1917
1946
|
_var_is_local=kwargs.get("_var_is_local", self._var_is_local),
|
|
@@ -1932,7 +1961,32 @@ class ComputedVar(Var, property):
|
|
|
1932
1961
|
"""
|
|
1933
1962
|
return f"__cached_{self._var_name}"
|
|
1934
1963
|
|
|
1935
|
-
|
|
1964
|
+
@property
|
|
1965
|
+
def _last_updated_attr(self) -> str:
|
|
1966
|
+
"""Get the attribute used to store the last updated timestamp.
|
|
1967
|
+
|
|
1968
|
+
Returns:
|
|
1969
|
+
An attribute name.
|
|
1970
|
+
"""
|
|
1971
|
+
return f"__last_updated_{self._var_name}"
|
|
1972
|
+
|
|
1973
|
+
def needs_update(self, instance: BaseState) -> bool:
|
|
1974
|
+
"""Check if the computed var needs to be updated.
|
|
1975
|
+
|
|
1976
|
+
Args:
|
|
1977
|
+
instance: The state instance that the computed var is attached to.
|
|
1978
|
+
|
|
1979
|
+
Returns:
|
|
1980
|
+
True if the computed var needs to be updated, False otherwise.
|
|
1981
|
+
"""
|
|
1982
|
+
if self._update_interval is None:
|
|
1983
|
+
return False
|
|
1984
|
+
last_updated = getattr(instance, self._last_updated_attr, None)
|
|
1985
|
+
if last_updated is None:
|
|
1986
|
+
return True
|
|
1987
|
+
return datetime.datetime.now() - last_updated > self._update_interval
|
|
1988
|
+
|
|
1989
|
+
def __get__(self, instance: BaseState | None, owner):
|
|
1936
1990
|
"""Get the ComputedVar value.
|
|
1937
1991
|
|
|
1938
1992
|
If the value is already cached on the instance, return the cached value.
|
|
@@ -1948,10 +2002,13 @@ class ComputedVar(Var, property):
|
|
|
1948
2002
|
return super().__get__(instance, owner)
|
|
1949
2003
|
|
|
1950
2004
|
# handle caching
|
|
1951
|
-
if not hasattr(instance, self._cache_attr):
|
|
2005
|
+
if not hasattr(instance, self._cache_attr) or self.needs_update(instance):
|
|
2006
|
+
# Set cache attr on state instance.
|
|
1952
2007
|
setattr(instance, self._cache_attr, super().__get__(instance, owner))
|
|
1953
2008
|
# Ensure the computed var gets serialized to redis.
|
|
1954
2009
|
instance._was_touched = True
|
|
2010
|
+
# Set the last updated timestamp on the state instance.
|
|
2011
|
+
setattr(instance, self._last_updated_attr, datetime.datetime.now())
|
|
1955
2012
|
return getattr(instance, self._cache_attr)
|
|
1956
2013
|
|
|
1957
2014
|
def _deps(
|
|
@@ -1978,7 +2035,9 @@ class ComputedVar(Var, property):
|
|
|
1978
2035
|
VarValueError: if the function references the get_state, parent_state, or substates attributes
|
|
1979
2036
|
(cannot track deps in a related state, only implicitly via parent state).
|
|
1980
2037
|
"""
|
|
1981
|
-
|
|
2038
|
+
if not self._auto_deps:
|
|
2039
|
+
return self._static_deps
|
|
2040
|
+
d = self._static_deps.copy()
|
|
1982
2041
|
if obj is None:
|
|
1983
2042
|
fget = property.__getattribute__(self, "fget")
|
|
1984
2043
|
if fget is not None:
|
|
@@ -2076,6 +2135,9 @@ def computed_var(
|
|
|
2076
2135
|
fget: Callable[[BaseState], Any] | None = None,
|
|
2077
2136
|
initial_value: Any | None = None,
|
|
2078
2137
|
cache: bool = False,
|
|
2138
|
+
deps: Optional[List[Union[str, Var]]] = None,
|
|
2139
|
+
auto_deps: bool = True,
|
|
2140
|
+
interval: Optional[Union[datetime.timedelta, int]] = None,
|
|
2079
2141
|
**kwargs,
|
|
2080
2142
|
) -> ComputedVar | Callable[[Callable[[BaseState], Any]], ComputedVar]:
|
|
2081
2143
|
"""A ComputedVar decorator with or without kwargs.
|
|
@@ -2084,19 +2146,31 @@ def computed_var(
|
|
|
2084
2146
|
fget: The getter function.
|
|
2085
2147
|
initial_value: The initial value of the computed var.
|
|
2086
2148
|
cache: Whether to cache the computed value.
|
|
2149
|
+
deps: Explicit var dependencies to track.
|
|
2150
|
+
auto_deps: Whether var dependencies should be auto-determined.
|
|
2151
|
+
interval: Interval at which the computed var should be updated.
|
|
2087
2152
|
**kwargs: additional attributes to set on the instance
|
|
2088
2153
|
|
|
2089
2154
|
Returns:
|
|
2090
2155
|
A ComputedVar instance.
|
|
2156
|
+
|
|
2157
|
+
Raises:
|
|
2158
|
+
ValueError: If caching is disabled and an update interval is set.
|
|
2091
2159
|
"""
|
|
2160
|
+
if cache is False and interval is not None:
|
|
2161
|
+
raise ValueError("Cannot set update interval without caching.")
|
|
2162
|
+
|
|
2092
2163
|
if fget is not None:
|
|
2093
2164
|
return ComputedVar(fget=fget, cache=cache)
|
|
2094
2165
|
|
|
2095
|
-
def wrapper(fget):
|
|
2166
|
+
def wrapper(fget: Callable[[BaseState], Any]) -> ComputedVar:
|
|
2096
2167
|
return ComputedVar(
|
|
2097
2168
|
fget=fget,
|
|
2098
2169
|
initial_value=initial_value,
|
|
2099
2170
|
cache=cache,
|
|
2171
|
+
deps=deps,
|
|
2172
|
+
auto_deps=auto_deps,
|
|
2173
|
+
interval=interval,
|
|
2100
2174
|
**kwargs,
|
|
2101
2175
|
)
|
|
2102
2176
|
|
reflex/vars.pyi
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import datetime
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from _typeshed import Incomplete
|
|
7
8
|
from reflex import constants as constants
|
|
@@ -141,6 +142,7 @@ class ComputedVar(Var):
|
|
|
141
142
|
def _deps(self, objclass: Type, obj: Optional[FunctionType] = ...) -> Set[str]: ...
|
|
142
143
|
def _replace(self, merge_var_data=None, **kwargs: Any) -> ComputedVar: ...
|
|
143
144
|
def mark_dirty(self, instance) -> None: ...
|
|
145
|
+
def needs_update(self, instance) -> bool: ...
|
|
144
146
|
def _determine_var_type(self) -> Type: ...
|
|
145
147
|
@overload
|
|
146
148
|
def __init__(
|
|
@@ -155,10 +157,24 @@ class ComputedVar(Var):
|
|
|
155
157
|
def computed_var(
|
|
156
158
|
fget: Callable[[BaseState], Any] | None = None,
|
|
157
159
|
initial_value: Any | None = None,
|
|
160
|
+
cache: bool = False,
|
|
161
|
+
deps: Optional[List[Union[str, Var]]] = None,
|
|
162
|
+
auto_deps: bool = True,
|
|
163
|
+
interval: Optional[Union[datetime.timedelta, int]] = None,
|
|
158
164
|
**kwargs,
|
|
159
165
|
) -> Callable[[Callable[[Any], Any]], ComputedVar]: ...
|
|
160
166
|
@overload
|
|
161
167
|
def computed_var(fget: Callable[[Any], Any]) -> ComputedVar: ...
|
|
168
|
+
@overload
|
|
169
|
+
def cached_var(
|
|
170
|
+
fget: Callable[[BaseState], Any] | None = None,
|
|
171
|
+
initial_value: Any | None = None,
|
|
172
|
+
deps: Optional[List[Union[str, Var]]] = None,
|
|
173
|
+
auto_deps: bool = True,
|
|
174
|
+
interval: Optional[Union[datetime.timedelta, int]] = None,
|
|
175
|
+
**kwargs,
|
|
176
|
+
) -> Callable[[Callable[[Any], Any]], ComputedVar]: ...
|
|
177
|
+
@overload
|
|
162
178
|
def cached_var(fget: Callable[[Any], Any]) -> ComputedVar: ...
|
|
163
179
|
|
|
164
180
|
class CallableVar(BaseVar):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: reflex
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3a1
|
|
4
4
|
Summary: Web apps in pure Python.
|
|
5
5
|
Home-page: https://reflex.dev
|
|
6
6
|
License: Apache-2.0
|
|
@@ -25,6 +25,7 @@ Requires-Dist: fastapi (>=0.96.0,<1.0)
|
|
|
25
25
|
Requires-Dist: gunicorn (>=20.1.0,<23.0)
|
|
26
26
|
Requires-Dist: httpx (>=0.25.1,<1.0)
|
|
27
27
|
Requires-Dist: jinja2 (>=3.1.2,<4.0)
|
|
28
|
+
Requires-Dist: lazy_loader (>=0.4)
|
|
28
29
|
Requires-Dist: packaging (>=23.1,<25.0)
|
|
29
30
|
Requires-Dist: platformdirs (>=3.10.0,<5.0)
|
|
30
31
|
Requires-Dist: psutil (>=5.9.4,<6.0)
|