reflex 0.5.2__py3-none-any.whl → 0.5.3__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/compiler/compiler.py +1 -1
- 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 +4 -1
- 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 +228 -0
- reflex/components/el/elements/__init__.py +129 -220
- reflex/components/el/elements/__init__.pyi +342 -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 +185 -7
- 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 +5 -1
- 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/format.py +8 -4
- reflex/utils/lazy_loader.py +33 -0
- reflex/utils/prerequisites.py +1 -14
- reflex/utils/pyi_generator.py +71 -20
- reflex/utils/serializers.py +9 -4
- reflex/utils/types.py +3 -1
- reflex/vars.py +92 -6
- reflex/vars.pyi +16 -0
- {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/METADATA +2 -1
- {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/RECORD +164 -151
- reflex/config.pyi +0 -112
- reflex/constants/base.pyi +0 -94
- {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/LICENSE +0 -0
- {reflex-0.5.2.dist-info → reflex-0.5.3.dist-info}/WHEEL +0 -0
- {reflex-0.5.2.dist-info → reflex-0.5.3.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
|
reflex/utils/format.py
CHANGED
|
@@ -9,8 +9,7 @@ import re
|
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
|
|
10
10
|
|
|
11
11
|
from reflex import constants
|
|
12
|
-
from reflex.utils import exceptions,
|
|
13
|
-
from reflex.utils.serializers import serialize
|
|
12
|
+
from reflex.utils import exceptions, types
|
|
14
13
|
from reflex.vars import BaseVar, Var
|
|
15
14
|
|
|
16
15
|
if TYPE_CHECKING:
|
|
@@ -400,6 +399,7 @@ def format_prop(
|
|
|
400
399
|
"""
|
|
401
400
|
# import here to avoid circular import.
|
|
402
401
|
from reflex.event import EventChain
|
|
402
|
+
from reflex.utils import serializers
|
|
403
403
|
|
|
404
404
|
try:
|
|
405
405
|
# Handle var props.
|
|
@@ -687,6 +687,8 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
|
|
|
687
687
|
Raises:
|
|
688
688
|
TypeError: If the given value is not a valid state.
|
|
689
689
|
"""
|
|
690
|
+
from reflex.utils import serializers
|
|
691
|
+
|
|
690
692
|
# Handle dicts.
|
|
691
693
|
if isinstance(value, dict):
|
|
692
694
|
return {k: format_state(v, k) for k, v in value.items()}
|
|
@@ -700,7 +702,7 @@ def format_state(value: Any, key: Optional[str] = None) -> Any:
|
|
|
700
702
|
return value
|
|
701
703
|
|
|
702
704
|
# Serialize the value.
|
|
703
|
-
serialized = serialize(value)
|
|
705
|
+
serialized = serializers.serialize(value)
|
|
704
706
|
if serialized is not None:
|
|
705
707
|
return serialized
|
|
706
708
|
|
|
@@ -803,7 +805,9 @@ def json_dumps(obj: Any) -> str:
|
|
|
803
805
|
Returns:
|
|
804
806
|
A string
|
|
805
807
|
"""
|
|
806
|
-
|
|
808
|
+
from reflex.utils import serializers
|
|
809
|
+
|
|
810
|
+
return json.dumps(obj, ensure_ascii=False, default=serializers.serialize)
|
|
807
811
|
|
|
808
812
|
|
|
809
813
|
def unwrap_vars(value: str) -> str:
|
|
@@ -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
|
@@ -12,7 +12,7 @@ from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union, get_type_
|
|
|
12
12
|
|
|
13
13
|
from reflex.base import Base
|
|
14
14
|
from reflex.constants.colors import Color, format_color
|
|
15
|
-
from reflex.utils import exceptions,
|
|
15
|
+
from reflex.utils import exceptions, types
|
|
16
16
|
|
|
17
17
|
# Mapping from type to a serializer.
|
|
18
18
|
# The serializer should convert the type to a JSON object.
|
|
@@ -154,6 +154,8 @@ def serialize_primitive(value: Union[bool, int, float, None]) -> str:
|
|
|
154
154
|
Returns:
|
|
155
155
|
The serialized number/bool/None.
|
|
156
156
|
"""
|
|
157
|
+
from reflex.utils import format
|
|
158
|
+
|
|
157
159
|
return format.json_dumps(value)
|
|
158
160
|
|
|
159
161
|
|
|
@@ -180,6 +182,8 @@ def serialize_list(value: Union[List, Tuple, Set]) -> str:
|
|
|
180
182
|
Returns:
|
|
181
183
|
The serialized list.
|
|
182
184
|
"""
|
|
185
|
+
from reflex.utils import format
|
|
186
|
+
|
|
183
187
|
# Dump the list to a string.
|
|
184
188
|
fprop = format.json_dumps(list(value))
|
|
185
189
|
|
|
@@ -202,6 +206,7 @@ def serialize_dict(prop: Dict[str, Any]) -> str:
|
|
|
202
206
|
"""
|
|
203
207
|
# Import here to avoid circular imports.
|
|
204
208
|
from reflex.event import EventHandler
|
|
209
|
+
from reflex.utils import format
|
|
205
210
|
|
|
206
211
|
prop_dict = {}
|
|
207
212
|
|
|
@@ -255,7 +260,7 @@ def serialize_enum(en: Enum) -> str:
|
|
|
255
260
|
en: The enum to serialize.
|
|
256
261
|
|
|
257
262
|
Returns:
|
|
258
|
-
|
|
263
|
+
The serialized enum.
|
|
259
264
|
"""
|
|
260
265
|
return en.value
|
|
261
266
|
|
|
@@ -313,7 +318,7 @@ try:
|
|
|
313
318
|
from plotly.io import to_json
|
|
314
319
|
|
|
315
320
|
@serializer
|
|
316
|
-
def serialize_figure(figure: Figure) ->
|
|
321
|
+
def serialize_figure(figure: Figure) -> dict:
|
|
317
322
|
"""Serialize a plotly figure.
|
|
318
323
|
|
|
319
324
|
Args:
|
|
@@ -322,7 +327,7 @@ try:
|
|
|
322
327
|
Returns:
|
|
323
328
|
The serialized figure.
|
|
324
329
|
"""
|
|
325
|
-
return json.loads(str(to_json(figure)))
|
|
330
|
+
return json.loads(str(to_json(figure)))
|
|
326
331
|
|
|
327
332
|
except ImportError:
|
|
328
333
|
pass
|
reflex/utils/types.py
CHANGED
|
@@ -42,7 +42,7 @@ from sqlalchemy.orm import (
|
|
|
42
42
|
|
|
43
43
|
from reflex import constants
|
|
44
44
|
from reflex.base import Base
|
|
45
|
-
from reflex.utils import console
|
|
45
|
+
from reflex.utils import console
|
|
46
46
|
|
|
47
47
|
if sys.version_info >= (3, 12):
|
|
48
48
|
from typing import override
|
|
@@ -392,6 +392,8 @@ def is_valid_var_type(type_: Type) -> bool:
|
|
|
392
392
|
Returns:
|
|
393
393
|
Whether the type is a valid prop type.
|
|
394
394
|
"""
|
|
395
|
+
from reflex.utils import serializers
|
|
396
|
+
|
|
395
397
|
if is_union(type_):
|
|
396
398
|
return all((is_valid_var_type(arg) for arg in get_args(type_)))
|
|
397
399
|
return _issubclass(type_, StateVar) or serializers.has_serializer(type_)
|