reflex 0.4.9a1__py3-none-any.whl → 0.5.0__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/apps/blank/code/blank.py +19 -16
- reflex/.templates/apps/demo/code/demo.py +1 -1
- reflex/.templates/apps/demo/code/pages/datatable.py +4 -4
- reflex/.templates/apps/demo/code/pages/forms.py +2 -2
- reflex/.templates/jinja/web/tailwind.config.js.jinja2 +12 -0
- reflex/.templates/web/utils/helpers/debounce.js +17 -0
- reflex/.templates/web/utils/helpers/throttle.js +22 -0
- reflex/.templates/web/utils/state.js +21 -3
- reflex/__init__.py +6 -1
- reflex/__init__.pyi +4 -1
- reflex/app.py +160 -140
- reflex/app_module_for_backend.py +1 -1
- reflex/base.py +13 -15
- reflex/compiler/compiler.py +10 -1
- reflex/compiler/utils.py +3 -30
- reflex/components/__init__.py +1 -0
- reflex/components/chakra/datadisplay/list.py +1 -3
- reflex/components/chakra/datadisplay/list.pyi +3 -3
- reflex/components/chakra/disclosure/accordion.py +1 -1
- reflex/components/chakra/forms/pininput.pyi +1 -1
- reflex/components/chakra/media/icon.py +2 -2
- reflex/components/component.py +275 -32
- reflex/components/core/__init__.py +2 -2
- reflex/components/core/cond.py +1 -10
- reflex/components/core/debounce.py +5 -2
- reflex/components/core/debounce.pyi +4 -2
- reflex/components/core/foreach.py +60 -49
- reflex/components/core/html.py +6 -0
- reflex/components/core/match.py +2 -17
- reflex/components/core/upload.py +42 -1
- reflex/components/core/upload.pyi +199 -1
- reflex/components/datadisplay/code.py +7 -3
- reflex/components/datadisplay/code.pyi +3 -1
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/el/elements/forms.pyi +1 -1
- reflex/components/lucide/icon.py +5 -13
- reflex/components/lucide/icon.pyi +0 -1
- reflex/components/markdown/markdown.py +5 -23
- reflex/components/markdown/markdown.pyi +1 -4
- reflex/components/radix/primitives/accordion.py +265 -410
- reflex/components/radix/primitives/accordion.pyi +390 -36
- reflex/components/radix/primitives/form.py +33 -29
- reflex/components/radix/primitives/form.pyi +7 -2
- reflex/components/radix/primitives/progress.py +17 -9
- reflex/components/radix/primitives/progress.pyi +2 -0
- reflex/components/radix/primitives/slider.py +30 -18
- reflex/components/radix/primitives/slider.pyi +4 -0
- reflex/components/radix/themes/base.py +8 -1
- reflex/components/radix/themes/base.pyi +79 -1
- reflex/components/radix/themes/color_mode.py +88 -20
- reflex/components/radix/themes/color_mode.pyi +157 -139
- reflex/components/radix/themes/components/__init__.py +17 -0
- reflex/components/radix/themes/components/badge.py +2 -1
- reflex/components/radix/themes/components/badge.pyi +3 -1
- reflex/components/radix/themes/components/button.py +3 -1
- reflex/components/radix/themes/components/button.pyi +4 -1
- reflex/components/radix/themes/components/checkbox_cards.py +48 -0
- reflex/components/radix/themes/components/checkbox_cards.pyi +264 -0
- reflex/components/radix/themes/components/checkbox_group.py +42 -0
- reflex/components/radix/themes/components/checkbox_group.pyi +253 -0
- reflex/components/radix/themes/components/data_list.py +63 -0
- reflex/components/radix/themes/components/data_list.pyi +426 -0
- reflex/components/radix/themes/components/icon_button.py +20 -17
- reflex/components/radix/themes/components/icon_button.pyi +5 -1
- reflex/components/radix/themes/components/progress.py +55 -0
- reflex/components/radix/themes/components/progress.pyi +180 -0
- reflex/components/radix/themes/components/radio.py +31 -0
- reflex/components/radix/themes/components/radio.pyi +169 -0
- reflex/components/radix/themes/components/radio_cards.py +48 -0
- reflex/components/radix/themes/components/radio_cards.pyi +264 -0
- reflex/components/radix/themes/components/radio_group.py +2 -4
- reflex/components/radix/themes/components/segmented_control.py +48 -0
- reflex/components/radix/themes/components/segmented_control.pyi +262 -0
- reflex/components/radix/themes/components/skeleton.py +32 -0
- reflex/components/radix/themes/components/skeleton.pyi +106 -0
- reflex/components/radix/themes/components/spinner.py +26 -0
- reflex/components/radix/themes/components/spinner.pyi +101 -0
- reflex/components/radix/themes/components/tabs.py +26 -1
- reflex/components/radix/themes/components/tabs.pyi +69 -9
- reflex/components/radix/themes/components/text_field.py +101 -71
- reflex/components/radix/themes/components/text_field.pyi +81 -499
- reflex/components/radix/themes/layout/base.py +2 -2
- reflex/components/radix/themes/layout/base.pyi +4 -4
- reflex/components/radix/themes/layout/center.py +8 -3
- reflex/components/radix/themes/layout/center.pyi +2 -1
- reflex/components/radix/themes/layout/container.py +30 -2
- reflex/components/radix/themes/layout/container.pyi +9 -30
- reflex/components/radix/themes/layout/list.py +10 -5
- reflex/components/radix/themes/layout/list.pyi +5 -21
- reflex/components/radix/themes/layout/spacer.py +8 -3
- reflex/components/radix/themes/layout/spacer.pyi +2 -1
- reflex/components/radix/themes/layout/stack.py +7 -1
- reflex/components/radix/themes/layout/stack.pyi +3 -3
- reflex/components/radix/themes/typography/link.py +10 -2
- reflex/components/radix/themes/typography/link.pyi +5 -4
- reflex/components/sonner/__init__.py +3 -0
- reflex/components/sonner/toast.py +267 -0
- reflex/components/sonner/toast.pyi +205 -0
- reflex/components/tags/iter_tag.py +9 -6
- reflex/config.py +30 -54
- reflex/constants/__init__.py +0 -2
- reflex/constants/base.py +0 -5
- reflex/constants/colors.py +2 -0
- reflex/constants/installer.py +6 -1
- reflex/constants/route.py +4 -0
- reflex/custom_components/custom_components.py +24 -2
- reflex/event.py +75 -30
- reflex/experimental/__init__.py +5 -0
- reflex/experimental/layout.py +24 -6
- reflex/model.py +2 -1
- reflex/page.py +7 -4
- reflex/reflex.py +8 -3
- reflex/route.py +39 -0
- reflex/state.py +128 -131
- reflex/style.py +25 -3
- reflex/testing.py +10 -6
- reflex/utils/console.py +3 -1
- reflex/utils/exec.py +20 -7
- reflex/utils/format.py +1 -1
- reflex/utils/imports.py +3 -1
- reflex/utils/prerequisites.py +141 -20
- reflex/utils/processes.py +21 -1
- reflex/utils/pyi_generator.py +100 -5
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +26 -4
- reflex/utils/types.py +62 -18
- reflex/vars.py +11 -5
- {reflex-0.4.9a1.dist-info → reflex-0.5.0.dist-info}/METADATA +16 -4
- {reflex-0.4.9a1.dist-info → reflex-0.5.0.dist-info}/RECORD +132 -110
- {reflex-0.4.9a1.dist-info → reflex-0.5.0.dist-info}/WHEEL +1 -1
- reflex/app.pyi +0 -149
- {reflex-0.4.9a1.dist-info → reflex-0.5.0.dist-info}/LICENSE +0 -0
- {reflex-0.4.9a1.dist-info → reflex-0.5.0.dist-info}/entry_points.txt +0 -0
reflex/state.py
CHANGED
|
@@ -7,9 +7,7 @@ import contextlib
|
|
|
7
7
|
import copy
|
|
8
8
|
import functools
|
|
9
9
|
import inspect
|
|
10
|
-
import json
|
|
11
10
|
import traceback
|
|
12
|
-
import urllib.parse
|
|
13
11
|
import uuid
|
|
14
12
|
from abc import ABC, abstractmethod
|
|
15
13
|
from collections import defaultdict
|
|
@@ -31,13 +29,7 @@ from typing import (
|
|
|
31
29
|
import dill
|
|
32
30
|
|
|
33
31
|
try:
|
|
34
|
-
|
|
35
|
-
# reflex-hosting-cli tools are compatible with pydantic v2
|
|
36
|
-
|
|
37
|
-
if not TYPE_CHECKING:
|
|
38
|
-
import pydantic.v1 as pydantic
|
|
39
|
-
else:
|
|
40
|
-
raise ModuleNotFoundError
|
|
32
|
+
import pydantic.v1 as pydantic
|
|
41
33
|
except ModuleNotFoundError:
|
|
42
34
|
import pydantic
|
|
43
35
|
|
|
@@ -47,6 +39,7 @@ from redis.asyncio import Redis
|
|
|
47
39
|
from reflex import constants
|
|
48
40
|
from reflex.base import Base
|
|
49
41
|
from reflex.event import (
|
|
42
|
+
BACKGROUND_TASK_MARKER,
|
|
50
43
|
Event,
|
|
51
44
|
EventHandler,
|
|
52
45
|
EventSpec,
|
|
@@ -247,6 +240,60 @@ def _split_substate_key(substate_key: str) -> tuple[str, str]:
|
|
|
247
240
|
return token, state_name
|
|
248
241
|
|
|
249
242
|
|
|
243
|
+
class EventHandlerSetVar(EventHandler):
|
|
244
|
+
"""A special event handler to wrap setvar functionality."""
|
|
245
|
+
|
|
246
|
+
state_cls: Type[BaseState]
|
|
247
|
+
|
|
248
|
+
def __init__(self, state_cls: Type[BaseState]):
|
|
249
|
+
"""Initialize the EventHandlerSetVar.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
state_cls: The state class that vars will be set on.
|
|
253
|
+
"""
|
|
254
|
+
super().__init__(
|
|
255
|
+
fn=type(self).setvar,
|
|
256
|
+
state_full_name=state_cls.get_full_name(),
|
|
257
|
+
state_cls=state_cls, # type: ignore
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def setvar(self, var_name: str, value: Any):
|
|
261
|
+
"""Set the state variable to the value of the event.
|
|
262
|
+
|
|
263
|
+
Note: `self` here will be an instance of the state, not EventHandlerSetVar.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
var_name: The name of the variable to set.
|
|
267
|
+
value: The value to set the variable to.
|
|
268
|
+
"""
|
|
269
|
+
getattr(self, constants.SETTER_PREFIX + var_name)(value)
|
|
270
|
+
|
|
271
|
+
def __call__(self, *args: Any) -> EventSpec:
|
|
272
|
+
"""Performs pre-checks and munging on the provided args that will become an EventSpec.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
*args: The event args.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
The (partial) EventSpec that will be used to create the event to setvar.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
AttributeError: If the given Var name does not exist on the state.
|
|
282
|
+
ValueError: If the given Var name is not a str
|
|
283
|
+
"""
|
|
284
|
+
if args:
|
|
285
|
+
if not isinstance(args[0], str):
|
|
286
|
+
raise ValueError(
|
|
287
|
+
f"Var name must be passed as a string, got {args[0]!r}"
|
|
288
|
+
)
|
|
289
|
+
# Check that the requested Var setter exists on the State at compile time.
|
|
290
|
+
if getattr(self.state_cls, constants.SETTER_PREFIX + args[0], None) is None:
|
|
291
|
+
raise AttributeError(
|
|
292
|
+
f"Variable `{args[0]}` cannot be set on `{self.state_cls.get_full_name()}`"
|
|
293
|
+
)
|
|
294
|
+
return super().__call__(*args)
|
|
295
|
+
|
|
296
|
+
|
|
250
297
|
class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
251
298
|
"""The state of the app."""
|
|
252
299
|
|
|
@@ -310,6 +357,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
310
357
|
# Whether the state has ever been touched since instantiation.
|
|
311
358
|
_was_touched: bool = False
|
|
312
359
|
|
|
360
|
+
# A special event handler for setting base vars.
|
|
361
|
+
setvar: ClassVar[EventHandler]
|
|
362
|
+
|
|
313
363
|
def __init__(
|
|
314
364
|
self,
|
|
315
365
|
*args,
|
|
@@ -393,6 +443,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
393
443
|
super().__init_subclass__(**kwargs)
|
|
394
444
|
# Event handlers should not shadow builtin state methods.
|
|
395
445
|
cls._check_overridden_methods()
|
|
446
|
+
# Computed vars should not shadow builtin state props.
|
|
447
|
+
cls._check_overriden_basevars()
|
|
396
448
|
|
|
397
449
|
# Reset subclass tracking for this class.
|
|
398
450
|
cls.class_subclasses = set()
|
|
@@ -500,6 +552,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
500
552
|
value.__qualname__ = f"{cls.__name__}.{name}"
|
|
501
553
|
events[name] = value
|
|
502
554
|
|
|
555
|
+
# Create the setvar event handler for this state
|
|
556
|
+
cls._create_setvar()
|
|
557
|
+
|
|
503
558
|
for name, fn in events.items():
|
|
504
559
|
handler = cls._create_event_handler(fn)
|
|
505
560
|
cls.event_handlers[name] = handler
|
|
@@ -525,6 +580,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
525
580
|
closure=fn.__closure__,
|
|
526
581
|
)
|
|
527
582
|
newfn.__annotations__ = fn.__annotations__
|
|
583
|
+
if mark := getattr(fn, BACKGROUND_TASK_MARKER, None):
|
|
584
|
+
setattr(newfn, BACKGROUND_TASK_MARKER, mark)
|
|
528
585
|
return newfn
|
|
529
586
|
|
|
530
587
|
@staticmethod
|
|
@@ -636,6 +693,19 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
636
693
|
f"The event handler name `{method_name}` shadows a builtin State method; use a different name instead"
|
|
637
694
|
)
|
|
638
695
|
|
|
696
|
+
@classmethod
|
|
697
|
+
def _check_overriden_basevars(cls):
|
|
698
|
+
"""Check for shadow base vars and raise error if any.
|
|
699
|
+
|
|
700
|
+
Raises:
|
|
701
|
+
NameError: When a computed var shadows a base var.
|
|
702
|
+
"""
|
|
703
|
+
for computed_var_ in cls._get_computed_vars():
|
|
704
|
+
if computed_var_._var_name in cls.__annotations__:
|
|
705
|
+
raise NameError(
|
|
706
|
+
f"The computed var name `{computed_var_._var_name}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
|
|
707
|
+
)
|
|
708
|
+
|
|
639
709
|
@classmethod
|
|
640
710
|
def get_skip_vars(cls) -> set[str]:
|
|
641
711
|
"""Get the vars to skip when serializing.
|
|
@@ -806,7 +876,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
806
876
|
cls.vars.update({name: var})
|
|
807
877
|
|
|
808
878
|
# let substates know about the new variable
|
|
809
|
-
for substate_class in cls.
|
|
879
|
+
for substate_class in cls.class_subclasses:
|
|
810
880
|
substate_class.vars.setdefault(name, var)
|
|
811
881
|
|
|
812
882
|
# Reinitialize dependency tracking dicts.
|
|
@@ -833,6 +903,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
833
903
|
"""
|
|
834
904
|
return EventHandler(fn=fn, state_full_name=cls.get_full_name())
|
|
835
905
|
|
|
906
|
+
@classmethod
|
|
907
|
+
def _create_setvar(cls):
|
|
908
|
+
"""Create the setvar method for the state."""
|
|
909
|
+
cls.setvar = cls.event_handlers["setvar"] = EventHandlerSetVar(state_cls=cls)
|
|
910
|
+
|
|
836
911
|
@classmethod
|
|
837
912
|
def _create_setter(cls, prop: BaseVar):
|
|
838
913
|
"""Create a setter for the var.
|
|
@@ -881,124 +956,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
881
956
|
if not func[0].startswith("__")
|
|
882
957
|
}
|
|
883
958
|
|
|
884
|
-
def get_token(self) -> str:
|
|
885
|
-
"""Return the token of the client associated with this state.
|
|
886
|
-
|
|
887
|
-
Returns:
|
|
888
|
-
The token of the client.
|
|
889
|
-
"""
|
|
890
|
-
console.deprecate(
|
|
891
|
-
feature_name="get_token",
|
|
892
|
-
reason="replaced by `State.router.session.client_token`",
|
|
893
|
-
deprecation_version="0.3.0",
|
|
894
|
-
removal_version="0.5.0",
|
|
895
|
-
)
|
|
896
|
-
return self.router_data.get(constants.RouteVar.CLIENT_TOKEN, "")
|
|
897
|
-
|
|
898
|
-
def get_sid(self) -> str:
|
|
899
|
-
"""Return the session ID of the client associated with this state.
|
|
900
|
-
|
|
901
|
-
Returns:
|
|
902
|
-
The session ID of the client.
|
|
903
|
-
"""
|
|
904
|
-
console.deprecate(
|
|
905
|
-
feature_name="get_sid",
|
|
906
|
-
reason="replaced by `State.router.session.session_id`",
|
|
907
|
-
deprecation_version="0.3.0",
|
|
908
|
-
removal_version="0.5.0",
|
|
909
|
-
)
|
|
910
|
-
return self.router_data.get(constants.RouteVar.SESSION_ID, "")
|
|
911
|
-
|
|
912
|
-
def get_headers(self) -> Dict:
|
|
913
|
-
"""Return the headers of the client associated with this state.
|
|
914
|
-
|
|
915
|
-
Returns:
|
|
916
|
-
The headers of the client.
|
|
917
|
-
"""
|
|
918
|
-
console.deprecate(
|
|
919
|
-
feature_name="get_headers",
|
|
920
|
-
reason="replaced by `State.router.headers`",
|
|
921
|
-
deprecation_version="0.3.0",
|
|
922
|
-
removal_version="0.5.0",
|
|
923
|
-
)
|
|
924
|
-
return self.router_data.get(constants.RouteVar.HEADERS, {})
|
|
925
|
-
|
|
926
|
-
def get_client_ip(self) -> str:
|
|
927
|
-
"""Return the IP of the client associated with this state.
|
|
928
|
-
|
|
929
|
-
Returns:
|
|
930
|
-
The IP of the client.
|
|
931
|
-
"""
|
|
932
|
-
console.deprecate(
|
|
933
|
-
feature_name="get_client_ip",
|
|
934
|
-
reason="replaced by `State.router.session.client_ip`",
|
|
935
|
-
deprecation_version="0.3.0",
|
|
936
|
-
removal_version="0.5.0",
|
|
937
|
-
)
|
|
938
|
-
return self.router_data.get(constants.RouteVar.CLIENT_IP, "")
|
|
939
|
-
|
|
940
|
-
def get_current_page(self, origin=False) -> str:
|
|
941
|
-
"""Obtain the path of current page from the router data.
|
|
942
|
-
|
|
943
|
-
Args:
|
|
944
|
-
origin: whether to return the base route as shown in browser
|
|
945
|
-
|
|
946
|
-
Returns:
|
|
947
|
-
The current page.
|
|
948
|
-
"""
|
|
949
|
-
console.deprecate(
|
|
950
|
-
feature_name="get_current_page",
|
|
951
|
-
reason="replaced by State.router.page / self.router.page",
|
|
952
|
-
deprecation_version="0.3.0",
|
|
953
|
-
removal_version="0.5.0",
|
|
954
|
-
)
|
|
955
|
-
|
|
956
|
-
return self.router.page.raw_path if origin else self.router.page.path
|
|
957
|
-
|
|
958
|
-
def get_query_params(self) -> dict[str, str]:
|
|
959
|
-
"""Obtain the query parameters for the queried page.
|
|
960
|
-
|
|
961
|
-
The query object contains both the URI parameters and the GET parameters.
|
|
962
|
-
|
|
963
|
-
Returns:
|
|
964
|
-
The dict of query parameters.
|
|
965
|
-
"""
|
|
966
|
-
console.deprecate(
|
|
967
|
-
feature_name="get_query_params",
|
|
968
|
-
reason="replaced by `State.router.page.params`",
|
|
969
|
-
deprecation_version="0.3.0",
|
|
970
|
-
removal_version="0.5.0",
|
|
971
|
-
)
|
|
972
|
-
return self.router_data.get(constants.RouteVar.QUERY, {})
|
|
973
|
-
|
|
974
|
-
def get_cookies(self) -> dict[str, str]:
|
|
975
|
-
"""Obtain the cookies of the client stored in the browser.
|
|
976
|
-
|
|
977
|
-
Returns:
|
|
978
|
-
The dict of cookies.
|
|
979
|
-
"""
|
|
980
|
-
console.deprecate(
|
|
981
|
-
feature_name=f"rx.get_cookies",
|
|
982
|
-
reason="and has been replaced by rx.Cookie, which can be used as a state var",
|
|
983
|
-
deprecation_version="0.3.0",
|
|
984
|
-
removal_version="0.5.0",
|
|
985
|
-
)
|
|
986
|
-
cookie_dict = {}
|
|
987
|
-
cookies = self.get_headers().get(constants.RouteVar.COOKIE, "").split(";")
|
|
988
|
-
|
|
989
|
-
cookie_pairs = [cookie.split("=") for cookie in cookies if cookie]
|
|
990
|
-
|
|
991
|
-
for pair in cookie_pairs:
|
|
992
|
-
key, value = pair[0].strip(), urllib.parse.unquote(pair[1].strip())
|
|
993
|
-
try:
|
|
994
|
-
# cast non-string values to the actual types.
|
|
995
|
-
value = json.loads(value)
|
|
996
|
-
except json.JSONDecodeError:
|
|
997
|
-
pass
|
|
998
|
-
finally:
|
|
999
|
-
cookie_dict[key] = value
|
|
1000
|
-
return cookie_dict
|
|
1001
|
-
|
|
1002
959
|
@classmethod
|
|
1003
960
|
def setup_dynamic_args(cls, args: dict[str, str]):
|
|
1004
961
|
"""Set up args for easy access in renderer.
|
|
@@ -1800,6 +1757,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1800
1757
|
return state
|
|
1801
1758
|
|
|
1802
1759
|
|
|
1760
|
+
EventHandlerSetVar.update_forward_refs()
|
|
1761
|
+
|
|
1762
|
+
|
|
1803
1763
|
class State(BaseState):
|
|
1804
1764
|
"""The app Base State."""
|
|
1805
1765
|
|
|
@@ -1841,7 +1801,7 @@ class OnLoadInternalState(State):
|
|
|
1841
1801
|
Returns:
|
|
1842
1802
|
The list of events to queue for on load handling.
|
|
1843
1803
|
"""
|
|
1844
|
-
# Do not app.
|
|
1804
|
+
# Do not app._compile()! It should be already compiled by now.
|
|
1845
1805
|
app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
|
1846
1806
|
load_events = app.get_load_events(self.router.page.path)
|
|
1847
1807
|
if not load_events:
|
|
@@ -1859,8 +1819,45 @@ class OnLoadInternalState(State):
|
|
|
1859
1819
|
|
|
1860
1820
|
|
|
1861
1821
|
class ComponentState(Base):
|
|
1862
|
-
"""
|
|
1822
|
+
"""Base class to allow for the creation of a state instance per component.
|
|
1823
|
+
|
|
1824
|
+
This allows for the bundling of UI and state logic into a single class,
|
|
1825
|
+
where each instance has a separate instance of the state.
|
|
1826
|
+
|
|
1827
|
+
Subclass this class and define vars and event handlers in the traditional way.
|
|
1828
|
+
Then define a `get_component` method that returns the UI for the component instance.
|
|
1829
|
+
|
|
1830
|
+
See the full [docs](https://reflex.dev/docs/substates/component-state/) for more.
|
|
1831
|
+
|
|
1832
|
+
Basic example:
|
|
1833
|
+
```python
|
|
1834
|
+
# Subclass ComponentState and define vars and event handlers.
|
|
1835
|
+
class Counter(rx.ComponentState):
|
|
1836
|
+
# Define vars that change.
|
|
1837
|
+
count: int = 0
|
|
1838
|
+
|
|
1839
|
+
# Define event handlers.
|
|
1840
|
+
def increment(self):
|
|
1841
|
+
self.count += 1
|
|
1842
|
+
|
|
1843
|
+
def decrement(self):
|
|
1844
|
+
self.count -= 1
|
|
1845
|
+
|
|
1846
|
+
@classmethod
|
|
1847
|
+
def get_component(cls, **props):
|
|
1848
|
+
# Access the state vars and event handlers using `cls`.
|
|
1849
|
+
return rx.hstack(
|
|
1850
|
+
rx.button("Decrement", on_click=cls.decrement),
|
|
1851
|
+
rx.text(cls.count),
|
|
1852
|
+
rx.button("Increment", on_click=cls.increment),
|
|
1853
|
+
**props,
|
|
1854
|
+
)
|
|
1855
|
+
|
|
1856
|
+
counter = Counter.create()
|
|
1857
|
+
```
|
|
1858
|
+
"""
|
|
1863
1859
|
|
|
1860
|
+
# The number of components created from this class.
|
|
1864
1861
|
_per_component_state_instance_count: ClassVar[int] = 0
|
|
1865
1862
|
|
|
1866
1863
|
@classmethod
|
|
@@ -2651,7 +2648,7 @@ class StateManagerRedis(StateManager):
|
|
|
2651
2648
|
|
|
2652
2649
|
Note: Connections will be automatically reopened when needed.
|
|
2653
2650
|
"""
|
|
2654
|
-
await self.redis.
|
|
2651
|
+
await self.redis.aclose(close_connection_pool=True)
|
|
2655
2652
|
|
|
2656
2653
|
|
|
2657
2654
|
def get_state_manager() -> StateManager:
|
reflex/style.py
CHANGED
|
@@ -159,12 +159,17 @@ def format_style_key(key: str) -> Tuple[str, ...]:
|
|
|
159
159
|
class Style(dict):
|
|
160
160
|
"""A style dictionary."""
|
|
161
161
|
|
|
162
|
-
def __init__(self, style_dict: dict | None = None):
|
|
162
|
+
def __init__(self, style_dict: dict | None = None, **kwargs):
|
|
163
163
|
"""Initialize the style.
|
|
164
164
|
|
|
165
165
|
Args:
|
|
166
166
|
style_dict: The style dictionary.
|
|
167
|
+
kwargs: Other key value pairs to apply to the dict update.
|
|
167
168
|
"""
|
|
169
|
+
if style_dict:
|
|
170
|
+
style_dict.update(kwargs)
|
|
171
|
+
else:
|
|
172
|
+
style_dict = kwargs
|
|
168
173
|
style_dict, self._var_data = convert(style_dict or {})
|
|
169
174
|
super().__init__(style_dict)
|
|
170
175
|
|
|
@@ -175,12 +180,15 @@ class Style(dict):
|
|
|
175
180
|
style_dict: The style dictionary.
|
|
176
181
|
kwargs: Other key value pairs to apply to the dict update.
|
|
177
182
|
"""
|
|
178
|
-
if kwargs:
|
|
179
|
-
style_dict = {**(style_dict or {}), **kwargs}
|
|
180
183
|
if not isinstance(style_dict, Style):
|
|
181
184
|
converted_dict = type(self)(style_dict)
|
|
182
185
|
else:
|
|
183
186
|
converted_dict = style_dict
|
|
187
|
+
if kwargs:
|
|
188
|
+
if converted_dict is None:
|
|
189
|
+
converted_dict = type(self)(kwargs)
|
|
190
|
+
else:
|
|
191
|
+
converted_dict.update(kwargs)
|
|
184
192
|
# Combine our VarData with that of any Vars in the style_dict that was passed.
|
|
185
193
|
self._var_data = VarData.merge(self._var_data, converted_dict._var_data)
|
|
186
194
|
super().update(converted_dict)
|
|
@@ -274,3 +282,17 @@ def convert_dict_to_style_and_format_emotion(
|
|
|
274
282
|
|
|
275
283
|
"""
|
|
276
284
|
return format_as_emotion(Style(raw_dict))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
STACK_CHILDREN_FULL_WIDTH = {
|
|
288
|
+
"& :where(.rx-Stack)": {
|
|
289
|
+
"width": "100%",
|
|
290
|
+
},
|
|
291
|
+
"& :where(.rx-Stack) > :where( "
|
|
292
|
+
"div:not(.rt-Box, .rx-Upload, .rx-Html),"
|
|
293
|
+
"input, select, textarea, table"
|
|
294
|
+
")": {
|
|
295
|
+
"width": "100%",
|
|
296
|
+
"flex_shrink": "1",
|
|
297
|
+
},
|
|
298
|
+
}
|
reflex/testing.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""reflex.testing - tools for testing reflex apps."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import asyncio
|
|
@@ -42,7 +43,6 @@ import reflex.utils.prerequisites
|
|
|
42
43
|
import reflex.utils.processes
|
|
43
44
|
from reflex.state import (
|
|
44
45
|
BaseState,
|
|
45
|
-
State,
|
|
46
46
|
StateManagerMemory,
|
|
47
47
|
StateManagerRedis,
|
|
48
48
|
reload_state_module,
|
|
@@ -167,6 +167,8 @@ class AppHarness:
|
|
|
167
167
|
app_name = app_source.__name__
|
|
168
168
|
|
|
169
169
|
app_name = app_name.lower()
|
|
170
|
+
while "__" in app_name:
|
|
171
|
+
app_name = app_name.replace("__", "_")
|
|
170
172
|
return cls(
|
|
171
173
|
app_name=app_name,
|
|
172
174
|
app_source=app_source,
|
|
@@ -240,13 +242,15 @@ class AppHarness:
|
|
|
240
242
|
# ensure config and app are reloaded when testing different app
|
|
241
243
|
reflex.config.get_config(reload=True)
|
|
242
244
|
# Save decorated pages before importing the test app module
|
|
243
|
-
before_decorated_pages = reflex.app.DECORATED_PAGES.copy()
|
|
245
|
+
before_decorated_pages = reflex.app.DECORATED_PAGES[self.app_name].copy()
|
|
244
246
|
# Ensure the AppHarness test does not skip State assignment due to running via pytest
|
|
245
247
|
os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
|
|
246
248
|
self.app_module = reflex.utils.prerequisites.get_compiled_app(reload=True)
|
|
247
249
|
# Save the pages that were added during testing
|
|
248
250
|
self._decorated_pages = [
|
|
249
|
-
p
|
|
251
|
+
p
|
|
252
|
+
for p in reflex.app.DECORATED_PAGES[self.app_name]
|
|
253
|
+
if p not in before_decorated_pages
|
|
250
254
|
]
|
|
251
255
|
self.app_instance = self.app_module.app
|
|
252
256
|
if isinstance(self.app_instance._state_manager, StateManagerRedis):
|
|
@@ -411,7 +415,7 @@ class AppHarness:
|
|
|
411
415
|
|
|
412
416
|
# Cleanup decorated pages added during testing
|
|
413
417
|
for page in self._decorated_pages:
|
|
414
|
-
reflex.app.DECORATED_PAGES.remove(page)
|
|
418
|
+
reflex.app.DECORATED_PAGES[self.app_name].remove(page)
|
|
415
419
|
|
|
416
420
|
def __exit__(self, *excinfo) -> None:
|
|
417
421
|
"""Contextmanager protocol for `stop()`.
|
|
@@ -536,7 +540,7 @@ class AppHarness:
|
|
|
536
540
|
if driver_clz is None:
|
|
537
541
|
requested_driver = os.environ.get("APP_HARNESS_DRIVER", "Chrome")
|
|
538
542
|
driver_clz = getattr(webdriver, requested_driver)
|
|
539
|
-
options = webdriver
|
|
543
|
+
options = getattr(webdriver, f"{requested_driver}Options")()
|
|
540
544
|
if driver_clz is webdriver.Chrome and want_headless:
|
|
541
545
|
options = webdriver.ChromeOptions()
|
|
542
546
|
options.add_argument("--headless=new")
|
|
@@ -596,7 +600,7 @@ class AppHarness:
|
|
|
596
600
|
await self.state_manager.close()
|
|
597
601
|
|
|
598
602
|
@contextlib.asynccontextmanager
|
|
599
|
-
async def modify_state(self, token: str) -> AsyncIterator[
|
|
603
|
+
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
|
600
604
|
"""Modify the state associated with the given token and send update to frontend.
|
|
601
605
|
|
|
602
606
|
Args:
|
reflex/utils/console.py
CHANGED
|
@@ -174,7 +174,9 @@ def ask(
|
|
|
174
174
|
Returns:
|
|
175
175
|
A string with the user input.
|
|
176
176
|
"""
|
|
177
|
-
return Prompt.ask(
|
|
177
|
+
return Prompt.ask(
|
|
178
|
+
question, choices=choices, default=default, show_choices=show_choices
|
|
179
|
+
) # type: ignore
|
|
178
180
|
|
|
179
181
|
|
|
180
182
|
def progress():
|
reflex/utils/exec.py
CHANGED
|
@@ -73,11 +73,12 @@ def kill(proc_pid: int):
|
|
|
73
73
|
# run_process_and_launch_url is assumed to be used
|
|
74
74
|
# only to launch the frontend
|
|
75
75
|
# If this is not the case, might have to change the logic
|
|
76
|
-
def run_process_and_launch_url(run_command: list[str]):
|
|
76
|
+
def run_process_and_launch_url(run_command: list[str], backend_present=True):
|
|
77
77
|
"""Run the process and launch the URL.
|
|
78
78
|
|
|
79
79
|
Args:
|
|
80
80
|
run_command: The command to run.
|
|
81
|
+
backend_present: Whether the backend is present.
|
|
81
82
|
"""
|
|
82
83
|
from reflex.utils import processes
|
|
83
84
|
|
|
@@ -89,7 +90,7 @@ def run_process_and_launch_url(run_command: list[str]):
|
|
|
89
90
|
while True:
|
|
90
91
|
if process is None:
|
|
91
92
|
kwargs = {}
|
|
92
|
-
if constants.IS_WINDOWS:
|
|
93
|
+
if constants.IS_WINDOWS and backend_present:
|
|
93
94
|
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
|
|
94
95
|
process = processes.new_process(
|
|
95
96
|
run_command,
|
|
@@ -122,12 +123,13 @@ def run_process_and_launch_url(run_command: list[str]):
|
|
|
122
123
|
break # while True
|
|
123
124
|
|
|
124
125
|
|
|
125
|
-
def run_frontend(root: Path, port: str):
|
|
126
|
+
def run_frontend(root: Path, port: str, backend_present=True):
|
|
126
127
|
"""Run the frontend.
|
|
127
128
|
|
|
128
129
|
Args:
|
|
129
130
|
root: The root path of the project.
|
|
130
131
|
port: The port to run the frontend on.
|
|
132
|
+
backend_present: Whether the backend is present.
|
|
131
133
|
"""
|
|
132
134
|
from reflex.utils import prerequisites
|
|
133
135
|
|
|
@@ -139,15 +141,19 @@ def run_frontend(root: Path, port: str):
|
|
|
139
141
|
# Run the frontend in development mode.
|
|
140
142
|
console.rule("[bold green]App Running")
|
|
141
143
|
os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
|
|
142
|
-
run_process_and_launch_url(
|
|
144
|
+
run_process_and_launch_url(
|
|
145
|
+
[prerequisites.get_package_manager(), "run", "dev"], # type: ignore
|
|
146
|
+
backend_present,
|
|
147
|
+
)
|
|
143
148
|
|
|
144
149
|
|
|
145
|
-
def run_frontend_prod(root: Path, port: str):
|
|
150
|
+
def run_frontend_prod(root: Path, port: str, backend_present=True):
|
|
146
151
|
"""Run the frontend.
|
|
147
152
|
|
|
148
153
|
Args:
|
|
149
154
|
root: The root path of the project (to keep same API as run_frontend).
|
|
150
155
|
port: The port to run the frontend on.
|
|
156
|
+
backend_present: Whether the backend is present.
|
|
151
157
|
"""
|
|
152
158
|
from reflex.utils import prerequisites
|
|
153
159
|
|
|
@@ -157,7 +163,10 @@ def run_frontend_prod(root: Path, port: str):
|
|
|
157
163
|
prerequisites.validate_frontend_dependencies(init=False)
|
|
158
164
|
# Run the frontend in production mode.
|
|
159
165
|
console.rule("[bold green]App Running")
|
|
160
|
-
run_process_and_launch_url(
|
|
166
|
+
run_process_and_launch_url(
|
|
167
|
+
[prerequisites.get_package_manager(), "run", "prod"], # type: ignore
|
|
168
|
+
backend_present,
|
|
169
|
+
)
|
|
161
170
|
|
|
162
171
|
|
|
163
172
|
def run_backend(
|
|
@@ -271,7 +280,11 @@ def output_system_info():
|
|
|
271
280
|
|
|
272
281
|
system = platform.system()
|
|
273
282
|
|
|
274
|
-
if
|
|
283
|
+
if (
|
|
284
|
+
system != "Windows"
|
|
285
|
+
or system == "Windows"
|
|
286
|
+
and prerequisites.is_windows_bun_supported()
|
|
287
|
+
):
|
|
275
288
|
dependencies.extend(
|
|
276
289
|
[
|
|
277
290
|
f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]",
|
reflex/utils/format.py
CHANGED
|
@@ -330,7 +330,7 @@ def format_cond(
|
|
|
330
330
|
cond = f"isTrue({cond})"
|
|
331
331
|
|
|
332
332
|
def create_var(cond_part):
|
|
333
|
-
return Var.create_safe(cond_part, _var_is_string=
|
|
333
|
+
return Var.create_safe(cond_part, _var_is_string=isinstance(cond_part, str))
|
|
334
334
|
|
|
335
335
|
# Format prop conds.
|
|
336
336
|
if is_prop:
|
reflex/utils/imports.py
CHANGED
|
@@ -66,7 +66,9 @@ class ImportVar(Base):
|
|
|
66
66
|
The name(tag name with alias) of tag.
|
|
67
67
|
"""
|
|
68
68
|
if self.alias:
|
|
69
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
self.alias if self.is_default else " as ".join([self.tag, self.alias]) # type: ignore
|
|
71
|
+
)
|
|
70
72
|
else:
|
|
71
73
|
return self.tag or ""
|
|
72
74
|
|