reflex 0.5.6a2__py3-none-any.whl → 0.5.7a1__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/jinja/web/utils/context.js.jinja2 +2 -0
- reflex/.templates/web/utils/state.js +31 -2
- reflex/app.py +190 -4
- reflex/compiler/templates.py +1 -0
- reflex/components/base/__init__.py +4 -0
- reflex/components/base/__init__.pyi +2 -0
- reflex/components/base/error_boundary.py +78 -0
- reflex/components/base/error_boundary.pyi +98 -0
- reflex/components/radix/primitives/form.py +3 -2
- reflex/components/recharts/cartesian.py +31 -16
- reflex/components/recharts/cartesian.pyi +46 -22
- reflex/components/recharts/charts.py +6 -6
- reflex/components/recharts/charts.pyi +10 -10
- reflex/components/recharts/general.py +7 -0
- reflex/components/recharts/general.pyi +5 -0
- reflex/components/recharts/recharts.py +16 -1
- reflex/components/recharts/recharts.pyi +1 -0
- reflex/config.py +2 -1
- reflex/constants/compiler.py +20 -2
- reflex/constants/config.py +1 -1
- reflex/event.py +9 -0
- reflex/experimental/__init__.py +2 -0
- reflex/experimental/vars/__init__.py +3 -0
- reflex/experimental/vars/base.py +210 -0
- reflex/state.py +85 -18
- reflex/testing.py +28 -0
- reflex/utils/compat.py +7 -5
- reflex/utils/console.py +10 -1
- reflex/utils/prerequisites.py +11 -5
- reflex/utils/processes.py +7 -2
- reflex/utils/pyi_generator.py +4 -1
- reflex/utils/types.py +35 -30
- reflex/vars.py +58 -23
- reflex/vars.pyi +4 -1
- {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/METADATA +1 -1
- {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/RECORD +39 -35
- {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/LICENSE +0 -0
- {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/WHEEL +0 -0
- {reflex-0.5.6a2.dist-info → reflex-0.5.7a1.dist-info}/entry_points.txt +0 -0
reflex/constants/compiler.py
CHANGED
|
@@ -62,9 +62,17 @@ class CompileVars(SimpleNamespace):
|
|
|
62
62
|
# The name of the function for converting a dict to an event.
|
|
63
63
|
TO_EVENT = "Event"
|
|
64
64
|
# The name of the internal on_load event.
|
|
65
|
-
ON_LOAD_INTERNAL = "
|
|
65
|
+
ON_LOAD_INTERNAL = "reflex___state____on_load_internal_state.on_load_internal"
|
|
66
66
|
# The name of the internal event to update generic state vars.
|
|
67
|
-
UPDATE_VARS_INTERNAL =
|
|
67
|
+
UPDATE_VARS_INTERNAL = (
|
|
68
|
+
"reflex___state____update_vars_internal_state.update_vars_internal"
|
|
69
|
+
)
|
|
70
|
+
# The name of the frontend event exception state
|
|
71
|
+
FRONTEND_EXCEPTION_STATE = "reflex___state____frontend_event_exception_state"
|
|
72
|
+
# The full name of the frontend exception state
|
|
73
|
+
FRONTEND_EXCEPTION_STATE_FULL = (
|
|
74
|
+
f"reflex___state____state.{FRONTEND_EXCEPTION_STATE}"
|
|
75
|
+
)
|
|
68
76
|
|
|
69
77
|
|
|
70
78
|
class PageNames(SimpleNamespace):
|
|
@@ -124,6 +132,16 @@ class Hooks(SimpleNamespace):
|
|
|
124
132
|
}
|
|
125
133
|
})"""
|
|
126
134
|
|
|
135
|
+
FRONTEND_ERRORS = f"""
|
|
136
|
+
const logFrontendError = (error, info) => {{
|
|
137
|
+
if (process.env.NODE_ENV === "production") {{
|
|
138
|
+
addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE_FULL}.handle_frontend_exception", {{
|
|
139
|
+
stack: error.stack,
|
|
140
|
+
}})])
|
|
141
|
+
}}
|
|
142
|
+
}}
|
|
143
|
+
"""
|
|
144
|
+
|
|
127
145
|
|
|
128
146
|
class MemoizationDisposition(enum.Enum):
|
|
129
147
|
"""The conditions under which a component should be memoized."""
|
reflex/constants/config.py
CHANGED
|
@@ -39,7 +39,7 @@ class GitIgnore(SimpleNamespace):
|
|
|
39
39
|
# The gitignore file.
|
|
40
40
|
FILE = ".gitignore"
|
|
41
41
|
# Files to gitignore.
|
|
42
|
-
DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]"}
|
|
42
|
+
DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]", "assets/external/"}
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class RequirementsTxt(SimpleNamespace):
|
reflex/event.py
CHANGED
|
@@ -509,6 +509,15 @@ def console_log(message: str | Var[str]) -> EventSpec:
|
|
|
509
509
|
return server_side("_console", get_fn_signature(console_log), message=message)
|
|
510
510
|
|
|
511
511
|
|
|
512
|
+
def back() -> EventSpec:
|
|
513
|
+
"""Do a history.back on the browser.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
An event to go back one page.
|
|
517
|
+
"""
|
|
518
|
+
return call_script("window.history.back()")
|
|
519
|
+
|
|
520
|
+
|
|
512
521
|
def window_alert(message: str | Var[str]) -> EventSpec:
|
|
513
522
|
"""Create a window alert on the browser.
|
|
514
523
|
|
reflex/experimental/__init__.py
CHANGED
|
@@ -8,6 +8,7 @@ from reflex.components.sonner.toast import toast as toast
|
|
|
8
8
|
|
|
9
9
|
from ..utils.console import warn
|
|
10
10
|
from . import hooks as hooks
|
|
11
|
+
from . import vars as vars
|
|
11
12
|
from .assets import asset as asset
|
|
12
13
|
from .client_state import ClientStateVar as ClientStateVar
|
|
13
14
|
from .layout import layout as layout
|
|
@@ -42,6 +43,7 @@ _x = ExperimentalNamespace(
|
|
|
42
43
|
asset=asset,
|
|
43
44
|
client_state=ClientStateVar.create,
|
|
44
45
|
hooks=hooks,
|
|
46
|
+
vars=vars,
|
|
45
47
|
layout=layout,
|
|
46
48
|
progress=progress,
|
|
47
49
|
PropsBase=PropsBase,
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Collection of base classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any, Optional, Type
|
|
8
|
+
|
|
9
|
+
from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
|
|
10
|
+
from reflex.utils import serializers, types
|
|
11
|
+
from reflex.utils.exceptions import VarTypeError
|
|
12
|
+
from reflex.vars import Var, VarData, _decode_var, _extract_var_data, _global_vars
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclasses.dataclass(
|
|
16
|
+
eq=False,
|
|
17
|
+
frozen=True,
|
|
18
|
+
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
|
19
|
+
)
|
|
20
|
+
class ImmutableVar(Var):
|
|
21
|
+
"""Base class for immutable vars."""
|
|
22
|
+
|
|
23
|
+
# The name of the var.
|
|
24
|
+
_var_name: str = dataclasses.field()
|
|
25
|
+
|
|
26
|
+
# The type of the var.
|
|
27
|
+
_var_type: Type = dataclasses.field(default=Any)
|
|
28
|
+
|
|
29
|
+
# Extra metadata associated with the Var
|
|
30
|
+
_var_data: Optional[VarData] = dataclasses.field(default=None)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def _var_is_local(self) -> bool:
|
|
34
|
+
"""Whether this is a local javascript variable.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
False
|
|
38
|
+
"""
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def _var_is_string(self) -> bool:
|
|
43
|
+
"""Whether the var is a string literal.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
False
|
|
47
|
+
"""
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def _var_full_name_needs_state_prefix(self) -> bool:
|
|
52
|
+
"""Whether the full name of the var needs a _var_state prefix.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
False
|
|
56
|
+
"""
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def __post_init__(self):
|
|
60
|
+
"""Post-initialize the var."""
|
|
61
|
+
# Decode any inline Var markup and apply it to the instance
|
|
62
|
+
_var_data, _var_name = _decode_var(self._var_name)
|
|
63
|
+
if _var_data:
|
|
64
|
+
self.__init__(
|
|
65
|
+
_var_name, self._var_type, VarData.merge(self._var_data, _var_data)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _replace(self, merge_var_data=None, **kwargs: Any):
|
|
69
|
+
"""Make a copy of this Var with updated fields.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
merge_var_data: VarData to merge into the existing VarData.
|
|
73
|
+
**kwargs: Var fields to update.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
A new ImmutableVar with the updated fields overwriting the corresponding fields in this Var.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
|
|
80
|
+
"""
|
|
81
|
+
if kwargs.get("_var_is_local", False) is not False:
|
|
82
|
+
raise TypeError(
|
|
83
|
+
"The _var_is_local argument is not supported for ImmutableVar."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if kwargs.get("_var_is_string", False) is not False:
|
|
87
|
+
raise TypeError(
|
|
88
|
+
"The _var_is_string argument is not supported for ImmutableVar."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
|
|
92
|
+
raise TypeError(
|
|
93
|
+
"The _var_full_name_needs_state_prefix argument is not supported for ImmutableVar."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
field_values = dict(
|
|
97
|
+
_var_name=kwargs.pop("_var_name", self._var_name),
|
|
98
|
+
_var_type=kwargs.pop("_var_type", self._var_type),
|
|
99
|
+
_var_data=VarData.merge(
|
|
100
|
+
kwargs.get("_var_data", self._var_data), merge_var_data
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
return ImmutableVar(**field_values)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def create(
|
|
107
|
+
cls,
|
|
108
|
+
value: Any,
|
|
109
|
+
_var_is_local: bool | None = None,
|
|
110
|
+
_var_is_string: bool | None = None,
|
|
111
|
+
_var_data: VarData | None = None,
|
|
112
|
+
) -> Var | None:
|
|
113
|
+
"""Create a var from a value.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
value: The value to create the var from.
|
|
117
|
+
_var_is_local: Whether the var is local. Deprecated.
|
|
118
|
+
_var_is_string: Whether the var is a string literal. Deprecated.
|
|
119
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The var.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
VarTypeError: If the value is JSON-unserializable.
|
|
126
|
+
TypeError: If _var_is_local or _var_is_string is not None.
|
|
127
|
+
"""
|
|
128
|
+
if _var_is_local is not None:
|
|
129
|
+
raise TypeError(
|
|
130
|
+
"The _var_is_local argument is not supported for ImmutableVar."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if _var_is_string is not None:
|
|
134
|
+
raise TypeError(
|
|
135
|
+
"The _var_is_string argument is not supported for ImmutableVar."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
from reflex.utils import format
|
|
139
|
+
|
|
140
|
+
# Check for none values.
|
|
141
|
+
if value is None:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
# If the value is already a var, do nothing.
|
|
145
|
+
if isinstance(value, Var):
|
|
146
|
+
return value
|
|
147
|
+
|
|
148
|
+
# Try to pull the imports and hooks from contained values.
|
|
149
|
+
if not isinstance(value, str):
|
|
150
|
+
_var_data = VarData.merge(*_extract_var_data(value), _var_data)
|
|
151
|
+
|
|
152
|
+
# Try to serialize the value.
|
|
153
|
+
type_ = type(value)
|
|
154
|
+
if type_ in types.JSONType:
|
|
155
|
+
name = value
|
|
156
|
+
else:
|
|
157
|
+
name, _serialized_type = serializers.serialize(value, get_type=True)
|
|
158
|
+
if name is None:
|
|
159
|
+
raise VarTypeError(
|
|
160
|
+
f"No JSON serializer found for var {value} of type {type_}."
|
|
161
|
+
)
|
|
162
|
+
name = name if isinstance(name, str) else format.json_dumps(name)
|
|
163
|
+
|
|
164
|
+
return ImmutableVar(
|
|
165
|
+
_var_name=name,
|
|
166
|
+
_var_type=type_,
|
|
167
|
+
_var_data=_var_data,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def create_safe(
|
|
172
|
+
cls,
|
|
173
|
+
value: Any,
|
|
174
|
+
_var_is_local: bool | None = None,
|
|
175
|
+
_var_is_string: bool | None = None,
|
|
176
|
+
_var_data: VarData | None = None,
|
|
177
|
+
) -> Var:
|
|
178
|
+
"""Create a var from a value, asserting that it is not None.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
value: The value to create the var from.
|
|
182
|
+
_var_is_local: Whether the var is local. Deprecated.
|
|
183
|
+
_var_is_string: Whether the var is a string literal. Deprecated.
|
|
184
|
+
_var_data: Additional hooks and imports associated with the Var.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
The var.
|
|
188
|
+
"""
|
|
189
|
+
var = cls.create(
|
|
190
|
+
value,
|
|
191
|
+
_var_is_local=_var_is_local,
|
|
192
|
+
_var_is_string=_var_is_string,
|
|
193
|
+
_var_data=_var_data,
|
|
194
|
+
)
|
|
195
|
+
assert var is not None
|
|
196
|
+
return var
|
|
197
|
+
|
|
198
|
+
def __format__(self, format_spec: str) -> str:
|
|
199
|
+
"""Format the var into a Javascript equivalent to an f-string.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
format_spec: The format specifier (Ignored for now).
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
The formatted var.
|
|
206
|
+
"""
|
|
207
|
+
_global_vars[hash(self)] = self
|
|
208
|
+
|
|
209
|
+
# Encode the _var_data into the formatted output for tracking purposes.
|
|
210
|
+
return f"{REFLEX_VAR_OPENING_TAG}{hash(self)}{REFLEX_VAR_CLOSING_TAG}{self._var_name}"
|
reflex/state.py
CHANGED
|
@@ -8,7 +8,6 @@ import copy
|
|
|
8
8
|
import functools
|
|
9
9
|
import inspect
|
|
10
10
|
import os
|
|
11
|
-
import traceback
|
|
12
11
|
import uuid
|
|
13
12
|
from abc import ABC, abstractmethod
|
|
14
13
|
from collections import defaultdict
|
|
@@ -25,9 +24,12 @@ from typing import (
|
|
|
25
24
|
Sequence,
|
|
26
25
|
Set,
|
|
27
26
|
Type,
|
|
27
|
+
Union,
|
|
28
|
+
cast,
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
import dill
|
|
32
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
31
33
|
|
|
32
34
|
try:
|
|
33
35
|
import pydantic.v1 as pydantic
|
|
@@ -47,7 +49,6 @@ from reflex.event import (
|
|
|
47
49
|
EventHandler,
|
|
48
50
|
EventSpec,
|
|
49
51
|
fix_events,
|
|
50
|
-
window_alert,
|
|
51
52
|
)
|
|
52
53
|
from reflex.utils import console, format, prerequisites, types
|
|
53
54
|
from reflex.utils.exceptions import ImmutableStateError, LockExpiredError
|
|
@@ -425,6 +426,21 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
425
426
|
if isinstance(v, ComputedVar)
|
|
426
427
|
]
|
|
427
428
|
|
|
429
|
+
@classmethod
|
|
430
|
+
def _validate_module_name(cls) -> None:
|
|
431
|
+
"""Check if the module name is valid.
|
|
432
|
+
|
|
433
|
+
Reflex uses ___ as state name module separator.
|
|
434
|
+
|
|
435
|
+
Raises:
|
|
436
|
+
NameError: If the module name is invalid.
|
|
437
|
+
"""
|
|
438
|
+
if "___" in cls.__module__:
|
|
439
|
+
raise NameError(
|
|
440
|
+
"The module name of a State class cannot contain '___'. "
|
|
441
|
+
"Please rename the module."
|
|
442
|
+
)
|
|
443
|
+
|
|
428
444
|
@classmethod
|
|
429
445
|
def __init_subclass__(cls, mixin: bool = False, **kwargs):
|
|
430
446
|
"""Do some magic for the subclass initialization.
|
|
@@ -444,8 +460,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
444
460
|
if mixin:
|
|
445
461
|
return
|
|
446
462
|
|
|
463
|
+
# Validate the module name.
|
|
464
|
+
cls._validate_module_name()
|
|
465
|
+
|
|
447
466
|
# Event handlers should not shadow builtin state methods.
|
|
448
467
|
cls._check_overridden_methods()
|
|
468
|
+
|
|
449
469
|
# Computed vars should not shadow builtin state props.
|
|
450
470
|
cls._check_overriden_basevars()
|
|
451
471
|
|
|
@@ -462,20 +482,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
462
482
|
cls.inherited_backend_vars = parent_state.backend_vars
|
|
463
483
|
|
|
464
484
|
# Check if another substate class with the same name has already been defined.
|
|
465
|
-
if cls.
|
|
485
|
+
if cls.get_name() in set(
|
|
486
|
+
c.get_name() for c in parent_state.class_subclasses
|
|
487
|
+
):
|
|
466
488
|
if is_testing_env():
|
|
467
489
|
# Clear existing subclass with same name when app is reloaded via
|
|
468
490
|
# utils.prerequisites.get_app(reload=True)
|
|
469
491
|
parent_state.class_subclasses = set(
|
|
470
492
|
c
|
|
471
493
|
for c in parent_state.class_subclasses
|
|
472
|
-
if c.
|
|
494
|
+
if c.get_name() != cls.get_name()
|
|
473
495
|
)
|
|
474
496
|
else:
|
|
475
497
|
# During normal operation, subclasses cannot have the same name, even if they are
|
|
476
498
|
# defined in different modules.
|
|
477
499
|
raise StateValueError(
|
|
478
|
-
f"The substate class '{cls.
|
|
500
|
+
f"The substate class '{cls.get_name()}' has been defined multiple times. "
|
|
479
501
|
"Shadowing substate classes is not allowed."
|
|
480
502
|
)
|
|
481
503
|
# Track this new subclass in the parent state's subclasses set.
|
|
@@ -758,7 +780,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
758
780
|
Returns:
|
|
759
781
|
The name of the state.
|
|
760
782
|
"""
|
|
761
|
-
|
|
783
|
+
module = cls.__module__.replace(".", "___")
|
|
784
|
+
return format.to_snake_case(f"{module}___{cls.__name__}")
|
|
762
785
|
|
|
763
786
|
@classmethod
|
|
764
787
|
@functools.lru_cache()
|
|
@@ -1430,15 +1453,39 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1430
1453
|
# Convert valid EventHandler and EventSpec into Event
|
|
1431
1454
|
fixed_events = fix_events(self._check_valid(handler, events), token)
|
|
1432
1455
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1456
|
+
try:
|
|
1457
|
+
# Get the delta after processing the event.
|
|
1458
|
+
delta = state.get_delta()
|
|
1459
|
+
state._clean()
|
|
1460
|
+
|
|
1461
|
+
return StateUpdate(
|
|
1462
|
+
delta=delta,
|
|
1463
|
+
events=fixed_events,
|
|
1464
|
+
final=final if not handler.is_background else True,
|
|
1465
|
+
)
|
|
1466
|
+
except Exception as ex:
|
|
1467
|
+
state._clean()
|
|
1468
|
+
|
|
1469
|
+
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
|
1436
1470
|
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1471
|
+
event_specs = app_instance.backend_exception_handler(ex)
|
|
1472
|
+
|
|
1473
|
+
if event_specs is None:
|
|
1474
|
+
return StateUpdate()
|
|
1475
|
+
|
|
1476
|
+
event_specs_correct_type = cast(
|
|
1477
|
+
Union[List[Union[EventSpec, EventHandler]], None],
|
|
1478
|
+
[event_specs] if isinstance(event_specs, EventSpec) else event_specs,
|
|
1479
|
+
)
|
|
1480
|
+
fixed_events = fix_events(
|
|
1481
|
+
event_specs_correct_type,
|
|
1482
|
+
token,
|
|
1483
|
+
router_data=state.router_data,
|
|
1484
|
+
)
|
|
1485
|
+
return StateUpdate(
|
|
1486
|
+
events=fixed_events,
|
|
1487
|
+
final=True,
|
|
1488
|
+
)
|
|
1442
1489
|
|
|
1443
1490
|
async def _process_event(
|
|
1444
1491
|
self, handler: EventHandler, state: BaseState | StateProxy, payload: Dict
|
|
@@ -1491,12 +1538,15 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1491
1538
|
|
|
1492
1539
|
# If an error occurs, throw a window alert.
|
|
1493
1540
|
except Exception as ex:
|
|
1494
|
-
error = traceback.format_exc()
|
|
1495
|
-
print(error)
|
|
1496
1541
|
telemetry.send_error(ex, context="backend")
|
|
1542
|
+
|
|
1543
|
+
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
|
1544
|
+
|
|
1545
|
+
event_specs = app_instance.backend_exception_handler(ex)
|
|
1546
|
+
|
|
1497
1547
|
yield state._as_state_update(
|
|
1498
1548
|
handler,
|
|
1499
|
-
|
|
1549
|
+
event_specs,
|
|
1500
1550
|
final=True,
|
|
1501
1551
|
)
|
|
1502
1552
|
|
|
@@ -1798,6 +1848,23 @@ class State(BaseState):
|
|
|
1798
1848
|
is_hydrated: bool = False
|
|
1799
1849
|
|
|
1800
1850
|
|
|
1851
|
+
class FrontendEventExceptionState(State):
|
|
1852
|
+
"""Substate for handling frontend exceptions."""
|
|
1853
|
+
|
|
1854
|
+
def handle_frontend_exception(self, stack: str) -> None:
|
|
1855
|
+
"""Handle frontend exceptions.
|
|
1856
|
+
|
|
1857
|
+
If a frontend exception handler is provided, it will be called.
|
|
1858
|
+
Otherwise, the default frontend exception handler will be called.
|
|
1859
|
+
|
|
1860
|
+
Args:
|
|
1861
|
+
stack: The stack trace of the exception.
|
|
1862
|
+
|
|
1863
|
+
"""
|
|
1864
|
+
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
|
1865
|
+
app_instance.frontend_exception_handler(Exception(stack))
|
|
1866
|
+
|
|
1867
|
+
|
|
1801
1868
|
class UpdateVarsInternalState(State):
|
|
1802
1869
|
"""Substate for handling internal state var updates."""
|
|
1803
1870
|
|
|
@@ -2919,7 +2986,7 @@ class MutableProxy(wrapt.ObjectProxy):
|
|
|
2919
2986
|
pydantic.BaseModel.__dict__
|
|
2920
2987
|
)
|
|
2921
2988
|
|
|
2922
|
-
__mutable_types__ = (list, dict, set, Base)
|
|
2989
|
+
__mutable_types__ = (list, dict, set, Base, DeclarativeBase)
|
|
2923
2990
|
|
|
2924
2991
|
def __init__(self, wrapped: Any, state: BaseState, field_name: str):
|
|
2925
2992
|
"""Create a proxy for a mutable object that tracks changes.
|
reflex/testing.py
CHANGED
|
@@ -40,6 +40,7 @@ import reflex
|
|
|
40
40
|
import reflex.reflex
|
|
41
41
|
import reflex.utils.build
|
|
42
42
|
import reflex.utils.exec
|
|
43
|
+
import reflex.utils.format
|
|
43
44
|
import reflex.utils.prerequisites
|
|
44
45
|
import reflex.utils.processes
|
|
45
46
|
from reflex.state import (
|
|
@@ -177,6 +178,33 @@ class AppHarness:
|
|
|
177
178
|
app_module_path=root / app_name / f"{app_name}.py",
|
|
178
179
|
)
|
|
179
180
|
|
|
181
|
+
def get_state_name(self, state_cls_name: str) -> str:
|
|
182
|
+
"""Get the state name for the given state class name.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
state_cls_name: The state class name
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The state name
|
|
189
|
+
"""
|
|
190
|
+
return reflex.utils.format.to_snake_case(
|
|
191
|
+
f"{self.app_name}___{self.app_name}___" + state_cls_name
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def get_full_state_name(self, path: List[str]) -> str:
|
|
195
|
+
"""Get the full state name for the given state class name.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
path: A list of state class names
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
The full state name
|
|
202
|
+
"""
|
|
203
|
+
# NOTE: using State.get_name() somehow causes trouble here
|
|
204
|
+
# path = [State.get_name()] + [self.get_state_name(p) for p in path]
|
|
205
|
+
path = ["reflex___state____state"] + [self.get_state_name(p) for p in path]
|
|
206
|
+
return ".".join(path)
|
|
207
|
+
|
|
180
208
|
def _get_globals_from_signature(self, func: Any) -> dict[str, Any]:
|
|
181
209
|
"""Get the globals from a function or module object.
|
|
182
210
|
|
reflex/utils/compat.py
CHANGED
|
@@ -32,6 +32,13 @@ def pydantic_v1_patch():
|
|
|
32
32
|
Yields:
|
|
33
33
|
None when the Pydantic module is patched.
|
|
34
34
|
"""
|
|
35
|
+
import pydantic
|
|
36
|
+
|
|
37
|
+
if pydantic.__version__.startswith("1."):
|
|
38
|
+
# pydantic v1 is already installed
|
|
39
|
+
yield
|
|
40
|
+
return
|
|
41
|
+
|
|
35
42
|
patched_modules = [
|
|
36
43
|
"pydantic",
|
|
37
44
|
"pydantic.fields",
|
|
@@ -42,11 +49,6 @@ def pydantic_v1_patch():
|
|
|
42
49
|
try:
|
|
43
50
|
import pydantic.v1 # type: ignore
|
|
44
51
|
|
|
45
|
-
if pydantic.__version__.startswith("1."):
|
|
46
|
-
# pydantic v1 is already installed
|
|
47
|
-
yield
|
|
48
|
-
return
|
|
49
|
-
|
|
50
52
|
sys.modules["pydantic.fields"] = pydantic.v1.fields # type: ignore
|
|
51
53
|
sys.modules["pydantic.main"] = pydantic.v1.main # type: ignore
|
|
52
54
|
sys.modules["pydantic.errors"] = pydantic.v1.errors # type: ignore
|
reflex/utils/console.py
CHANGED
|
@@ -17,6 +17,9 @@ _LOG_LEVEL = LogLevel.INFO
|
|
|
17
17
|
# Deprecated features who's warning has been printed.
|
|
18
18
|
_EMITTED_DEPRECATION_WARNINGS = set()
|
|
19
19
|
|
|
20
|
+
# Info messages which have been printed.
|
|
21
|
+
_EMITTED_INFO = set()
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
def set_log_level(log_level: LogLevel):
|
|
22
25
|
"""Set the log level.
|
|
@@ -62,14 +65,20 @@ def debug(msg: str, **kwargs):
|
|
|
62
65
|
print(msg_, **kwargs)
|
|
63
66
|
|
|
64
67
|
|
|
65
|
-
def info(msg: str, **kwargs):
|
|
68
|
+
def info(msg: str, dedupe: bool = False, **kwargs):
|
|
66
69
|
"""Print an info message.
|
|
67
70
|
|
|
68
71
|
Args:
|
|
69
72
|
msg: The info message.
|
|
73
|
+
dedupe: If True, suppress multiple console logs of info message.
|
|
70
74
|
kwargs: Keyword arguments to pass to the print function.
|
|
71
75
|
"""
|
|
72
76
|
if _LOG_LEVEL <= LogLevel.INFO:
|
|
77
|
+
if dedupe:
|
|
78
|
+
if msg in _EMITTED_INFO:
|
|
79
|
+
return
|
|
80
|
+
else:
|
|
81
|
+
_EMITTED_INFO.add(msg)
|
|
73
82
|
print(f"[cyan]Info: {msg}[/cyan]", **kwargs)
|
|
74
83
|
|
|
75
84
|
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import functools
|
|
6
6
|
import glob
|
|
7
7
|
import importlib
|
|
8
|
+
import importlib.metadata
|
|
8
9
|
import inspect
|
|
9
10
|
import json
|
|
10
11
|
import os
|
|
@@ -23,7 +24,6 @@ from types import ModuleType
|
|
|
23
24
|
from typing import Callable, List, Optional
|
|
24
25
|
|
|
25
26
|
import httpx
|
|
26
|
-
import pkg_resources
|
|
27
27
|
import typer
|
|
28
28
|
from alembic.util.exc import CommandError
|
|
29
29
|
from packaging import version
|
|
@@ -61,7 +61,7 @@ class CpuInfo(Base):
|
|
|
61
61
|
def get_web_dir() -> Path:
|
|
62
62
|
"""Get the working directory for the next.js commands.
|
|
63
63
|
|
|
64
|
-
Can be
|
|
64
|
+
Can be overridden with REFLEX_WEB_WORKDIR.
|
|
65
65
|
|
|
66
66
|
Returns:
|
|
67
67
|
The working directory.
|
|
@@ -78,7 +78,7 @@ def check_latest_package_version(package_name: str):
|
|
|
78
78
|
"""
|
|
79
79
|
try:
|
|
80
80
|
# Get the latest version from PyPI
|
|
81
|
-
current_version =
|
|
81
|
+
current_version = importlib.metadata.version(package_name)
|
|
82
82
|
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
83
83
|
response = httpx.get(url)
|
|
84
84
|
latest_version = response.json()["info"]["version"]
|
|
@@ -404,9 +404,15 @@ def initialize_gitignore(
|
|
|
404
404
|
files_to_ignore: The files to add to the .gitignore file.
|
|
405
405
|
"""
|
|
406
406
|
# Combine with the current ignored files.
|
|
407
|
+
current_ignore: set[str] = set()
|
|
407
408
|
if os.path.exists(gitignore_file):
|
|
408
409
|
with open(gitignore_file, "r") as f:
|
|
409
|
-
|
|
410
|
+
current_ignore |= set([line.strip() for line in f.readlines()])
|
|
411
|
+
|
|
412
|
+
if files_to_ignore == current_ignore:
|
|
413
|
+
console.debug(f"{gitignore_file} already up to date.")
|
|
414
|
+
return
|
|
415
|
+
files_to_ignore |= current_ignore
|
|
410
416
|
|
|
411
417
|
# Write files to the .gitignore file.
|
|
412
418
|
with open(gitignore_file, "w", newline="\n") as f:
|
|
@@ -970,7 +976,7 @@ def is_latest_template() -> bool:
|
|
|
970
976
|
json_file = get_web_dir() / constants.Reflex.JSON
|
|
971
977
|
if not json_file.exists():
|
|
972
978
|
return False
|
|
973
|
-
app_version = json.
|
|
979
|
+
app_version = json.loads(json_file.read_text()).get("version")
|
|
974
980
|
return app_version == constants.Reflex.VERSION
|
|
975
981
|
|
|
976
982
|
|
reflex/utils/processes.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import collections
|
|
6
6
|
import contextlib
|
|
7
|
+
import importlib.metadata
|
|
7
8
|
import os
|
|
8
9
|
import signal
|
|
9
10
|
import subprocess
|
|
@@ -58,8 +59,12 @@ def get_process_on_port(port) -> Optional[psutil.Process]:
|
|
|
58
59
|
"""
|
|
59
60
|
for proc in psutil.process_iter(["pid", "name", "cmdline"]):
|
|
60
61
|
try:
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
if importlib.metadata.version("psutil") >= "6.0.0":
|
|
63
|
+
conns = proc.net_connections(kind="inet") # type: ignore
|
|
64
|
+
else:
|
|
65
|
+
conns = proc.connections(kind="inet")
|
|
66
|
+
for conn in conns:
|
|
67
|
+
if conn.laddr.port == int(port):
|
|
63
68
|
return proc
|
|
64
69
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
65
70
|
pass
|
reflex/utils/pyi_generator.py
CHANGED
|
@@ -159,7 +159,10 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
|
|
|
159
159
|
]
|
|
160
160
|
)
|
|
161
161
|
|
|
162
|
-
if
|
|
162
|
+
if (
|
|
163
|
+
value.__module__ not in ["builtins", "__builtins__"]
|
|
164
|
+
and value.__name__ not in type_hint_globals
|
|
165
|
+
):
|
|
163
166
|
raise TypeError(
|
|
164
167
|
f"{value.__module__ + '.' + value.__name__} is not a default import, "
|
|
165
168
|
"add it to DEFAULT_IMPORTS in pyi_generator.py"
|