reflex 0.6.3a4__py3-none-any.whl → 0.6.4__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/pages/_app.js.jinja2 +2 -2
- reflex/.templates/jinja/web/utils/context.js.jinja2 +3 -1
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -3
- reflex/.templates/web/components/shiki/code.js +29 -0
- reflex/.templates/web/jsconfig.json +2 -1
- reflex/.templates/web/utils/state.js +6 -4
- reflex/__init__.py +6 -3
- reflex/__init__.pyi +4 -3
- reflex/app.py +6 -5
- reflex/compiler/compiler.py +6 -7
- reflex/compiler/utils.py +8 -1
- reflex/components/base/error_boundary.py +37 -24
- reflex/components/base/error_boundary.pyi +8 -7
- reflex/components/component.py +9 -4
- reflex/components/core/banner.py +2 -2
- reflex/components/core/client_side_routing.py +1 -1
- reflex/components/core/clipboard.py +1 -1
- reflex/components/core/clipboard.pyi +1 -1
- reflex/components/core/cond.py +1 -1
- reflex/components/core/debounce.py +5 -1
- reflex/components/core/upload.py +7 -9
- reflex/components/core/upload.pyi +2 -2
- reflex/components/datadisplay/code.py +1 -1
- reflex/components/datadisplay/dataeditor.py +83 -18
- reflex/components/datadisplay/dataeditor.pyi +67 -15
- reflex/components/datadisplay/shiki_code_block.py +813 -0
- reflex/components/datadisplay/shiki_code_block.pyi +2211 -0
- reflex/components/dynamic.py +3 -3
- reflex/components/el/elements/forms.py +37 -23
- reflex/components/el/elements/forms.pyi +7 -4
- reflex/components/markdown/markdown.py +12 -1
- reflex/components/moment/moment.pyi +1 -1
- reflex/components/radix/primitives/drawer.pyi +2 -2
- reflex/components/radix/themes/base.py +2 -2
- reflex/components/radix/themes/color_mode.pyi +1 -1
- reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
- reflex/components/radix/themes/components/checkbox.pyi +3 -3
- reflex/components/radix/themes/components/context_menu.pyi +1 -1
- reflex/components/radix/themes/components/dialog.pyi +2 -2
- reflex/components/radix/themes/components/dropdown_menu.pyi +2 -2
- reflex/components/radix/themes/components/hover_card.pyi +2 -2
- reflex/components/radix/themes/components/popover.pyi +1 -1
- reflex/components/radix/themes/components/radio_cards.pyi +1 -1
- reflex/components/radix/themes/components/radio_group.pyi +1 -1
- reflex/components/radix/themes/components/select.pyi +6 -6
- reflex/components/radix/themes/components/switch.pyi +1 -1
- reflex/components/radix/themes/components/tabs.pyi +2 -2
- reflex/components/radix/themes/components/tooltip.pyi +4 -2
- reflex/components/react_player/__init__.py +1 -0
- reflex/components/react_player/audio.pyi +6 -3
- reflex/components/react_player/react_player.py +12 -1
- reflex/components/react_player/react_player.pyi +11 -3
- reflex/components/react_player/video.pyi +6 -3
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/sonner/toast.py +1 -1
- reflex/components/suneditor/editor.py +40 -16
- reflex/components/suneditor/editor.pyi +15 -11
- reflex/config.py +284 -20
- reflex/constants/__init__.py +2 -0
- reflex/constants/base.py +53 -31
- reflex/constants/compiler.py +2 -12
- reflex/constants/config.py +1 -2
- reflex/constants/installer.py +88 -32
- reflex/constants/style.py +1 -1
- reflex/constants/utils.py +32 -0
- reflex/custom_components/custom_components.py +3 -3
- reflex/event.py +152 -84
- reflex/experimental/__init__.py +2 -0
- reflex/experimental/client_state.py +1 -1
- reflex/experimental/layout.pyi +1 -1
- reflex/istate/storage.py +144 -0
- reflex/model.py +8 -11
- reflex/reflex.py +18 -17
- reflex/state.py +89 -151
- reflex/style.py +1 -1
- reflex/testing.py +2 -1
- reflex/utils/build.py +0 -12
- reflex/utils/exceptions.py +8 -0
- reflex/utils/exec.py +22 -4
- reflex/utils/imports.py +6 -0
- reflex/utils/net.py +2 -4
- reflex/utils/path_ops.py +7 -21
- reflex/utils/prerequisites.py +11 -17
- reflex/utils/pyi_generator.py +91 -3
- reflex/utils/registry.py +2 -6
- reflex/utils/types.py +14 -0
- reflex/vars/base.py +453 -424
- reflex/vars/function.py +6 -16
- reflex/vars/number.py +46 -67
- reflex/vars/object.py +1 -31
- reflex/vars/sequence.py +177 -47
- {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/METADATA +1 -1
- {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/RECORD +96 -91
- {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/LICENSE +0 -0
- {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/WHEEL +0 -0
- {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/entry_points.txt +0 -0
reflex/istate/storage.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Client-side storage classes for reflex state variables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from reflex.utils import format
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ClientStorageBase:
|
|
11
|
+
"""Base class for client-side storage."""
|
|
12
|
+
|
|
13
|
+
def options(self) -> dict[str, Any]:
|
|
14
|
+
"""Get the options for the storage.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
All set options for the storage (not None).
|
|
18
|
+
"""
|
|
19
|
+
return {
|
|
20
|
+
format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Cookie(ClientStorageBase, str):
|
|
25
|
+
"""Represents a state Var that is stored as a cookie in the browser."""
|
|
26
|
+
|
|
27
|
+
name: str | None
|
|
28
|
+
path: str
|
|
29
|
+
max_age: int | None
|
|
30
|
+
domain: str | None
|
|
31
|
+
secure: bool | None
|
|
32
|
+
same_site: str
|
|
33
|
+
|
|
34
|
+
def __new__(
|
|
35
|
+
cls,
|
|
36
|
+
object: Any = "",
|
|
37
|
+
encoding: str | None = None,
|
|
38
|
+
errors: str | None = None,
|
|
39
|
+
/,
|
|
40
|
+
name: str | None = None,
|
|
41
|
+
path: str = "/",
|
|
42
|
+
max_age: int | None = None,
|
|
43
|
+
domain: str | None = None,
|
|
44
|
+
secure: bool | None = None,
|
|
45
|
+
same_site: str = "lax",
|
|
46
|
+
):
|
|
47
|
+
"""Create a client-side Cookie (str).
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
object: The initial object.
|
|
51
|
+
encoding: The encoding to use.
|
|
52
|
+
errors: The error handling scheme to use.
|
|
53
|
+
name: The name of the cookie on the client side.
|
|
54
|
+
path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
|
|
55
|
+
max_age: Relative max age of the cookie in seconds from when the client receives it.
|
|
56
|
+
domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
|
|
57
|
+
secure: Is the cookie only accessible through HTTPS?
|
|
58
|
+
same_site: Whether the cookie is sent with third party requests.
|
|
59
|
+
One of (true|false|none|lax|strict)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The client-side Cookie object.
|
|
63
|
+
|
|
64
|
+
Note: expires (absolute Date) is not supported at this time.
|
|
65
|
+
"""
|
|
66
|
+
if encoding or errors:
|
|
67
|
+
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
|
68
|
+
else:
|
|
69
|
+
inst = super().__new__(cls, object)
|
|
70
|
+
inst.name = name
|
|
71
|
+
inst.path = path
|
|
72
|
+
inst.max_age = max_age
|
|
73
|
+
inst.domain = domain
|
|
74
|
+
inst.secure = secure
|
|
75
|
+
inst.same_site = same_site
|
|
76
|
+
return inst
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class LocalStorage(ClientStorageBase, str):
|
|
80
|
+
"""Represents a state Var that is stored in localStorage in the browser."""
|
|
81
|
+
|
|
82
|
+
name: str | None
|
|
83
|
+
sync: bool = False
|
|
84
|
+
|
|
85
|
+
def __new__(
|
|
86
|
+
cls,
|
|
87
|
+
object: Any = "",
|
|
88
|
+
encoding: str | None = None,
|
|
89
|
+
errors: str | None = None,
|
|
90
|
+
/,
|
|
91
|
+
name: str | None = None,
|
|
92
|
+
sync: bool = False,
|
|
93
|
+
) -> "LocalStorage":
|
|
94
|
+
"""Create a client-side localStorage (str).
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
object: The initial object.
|
|
98
|
+
encoding: The encoding to use.
|
|
99
|
+
errors: The error handling scheme to use.
|
|
100
|
+
name: The name of the storage key on the client side.
|
|
101
|
+
sync: Whether changes should be propagated to other tabs.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
The client-side localStorage object.
|
|
105
|
+
"""
|
|
106
|
+
if encoding or errors:
|
|
107
|
+
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
|
108
|
+
else:
|
|
109
|
+
inst = super().__new__(cls, object)
|
|
110
|
+
inst.name = name
|
|
111
|
+
inst.sync = sync
|
|
112
|
+
return inst
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class SessionStorage(ClientStorageBase, str):
|
|
116
|
+
"""Represents a state Var that is stored in sessionStorage in the browser."""
|
|
117
|
+
|
|
118
|
+
name: str | None
|
|
119
|
+
|
|
120
|
+
def __new__(
|
|
121
|
+
cls,
|
|
122
|
+
object: Any = "",
|
|
123
|
+
encoding: str | None = None,
|
|
124
|
+
errors: str | None = None,
|
|
125
|
+
/,
|
|
126
|
+
name: str | None = None,
|
|
127
|
+
) -> "SessionStorage":
|
|
128
|
+
"""Create a client-side sessionStorage (str).
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
object: The initial object.
|
|
132
|
+
encoding: The encoding to use.
|
|
133
|
+
errors: The error handling scheme to use
|
|
134
|
+
name: The name of the storage on the client side
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The client-side sessionStorage object.
|
|
138
|
+
"""
|
|
139
|
+
if encoding or errors:
|
|
140
|
+
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
|
141
|
+
else:
|
|
142
|
+
inst = super().__new__(cls, object)
|
|
143
|
+
inst.name = name
|
|
144
|
+
return inst
|
reflex/model.py
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import os
|
|
6
5
|
from collections import defaultdict
|
|
7
|
-
from pathlib import Path
|
|
8
6
|
from typing import Any, ClassVar, Optional, Type, Union
|
|
9
7
|
|
|
10
8
|
import alembic.autogenerate
|
|
@@ -18,9 +16,8 @@ import sqlalchemy
|
|
|
18
16
|
import sqlalchemy.exc
|
|
19
17
|
import sqlalchemy.orm
|
|
20
18
|
|
|
21
|
-
from reflex import constants
|
|
22
19
|
from reflex.base import Base
|
|
23
|
-
from reflex.config import get_config
|
|
20
|
+
from reflex.config import environment, get_config
|
|
24
21
|
from reflex.utils import console
|
|
25
22
|
from reflex.utils.compat import sqlmodel, sqlmodel_field_has_primary_key
|
|
26
23
|
|
|
@@ -41,12 +38,12 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine:
|
|
|
41
38
|
url = url or conf.db_url
|
|
42
39
|
if url is None:
|
|
43
40
|
raise ValueError("No database url configured")
|
|
44
|
-
if not
|
|
41
|
+
if not environment.ALEMBIC_CONFIG.exists():
|
|
45
42
|
console.warn(
|
|
46
43
|
"Database is not initialized, run [bold]reflex db init[/bold] first."
|
|
47
44
|
)
|
|
48
45
|
# Print the SQL queries if the log level is INFO or lower.
|
|
49
|
-
echo_db_query =
|
|
46
|
+
echo_db_query = environment.SQLALCHEMY_ECHO
|
|
50
47
|
# Needed for the admin dash on sqlite.
|
|
51
48
|
connect_args = {"check_same_thread": False} if url.startswith("sqlite") else {}
|
|
52
49
|
return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
|
|
@@ -234,7 +231,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
|
|
234
231
|
Returns:
|
|
235
232
|
tuple of (config, script_directory)
|
|
236
233
|
"""
|
|
237
|
-
config = alembic.config.Config(
|
|
234
|
+
config = alembic.config.Config(environment.ALEMBIC_CONFIG)
|
|
238
235
|
return config, alembic.script.ScriptDirectory(
|
|
239
236
|
config.get_main_option("script_location", default="version"),
|
|
240
237
|
)
|
|
@@ -269,8 +266,8 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
|
|
269
266
|
def alembic_init(cls):
|
|
270
267
|
"""Initialize alembic for the project."""
|
|
271
268
|
alembic.command.init(
|
|
272
|
-
config=alembic.config.Config(
|
|
273
|
-
directory=str(
|
|
269
|
+
config=alembic.config.Config(environment.ALEMBIC_CONFIG),
|
|
270
|
+
directory=str(environment.ALEMBIC_CONFIG.parent / "alembic"),
|
|
274
271
|
)
|
|
275
272
|
|
|
276
273
|
@classmethod
|
|
@@ -290,7 +287,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
|
|
290
287
|
Returns:
|
|
291
288
|
True when changes have been detected.
|
|
292
289
|
"""
|
|
293
|
-
if not
|
|
290
|
+
if not environment.ALEMBIC_CONFIG.exists():
|
|
294
291
|
return False
|
|
295
292
|
|
|
296
293
|
config, script_directory = cls._alembic_config()
|
|
@@ -391,7 +388,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
|
|
|
391
388
|
True - indicating the process was successful.
|
|
392
389
|
None - indicating the process was skipped.
|
|
393
390
|
"""
|
|
394
|
-
if not
|
|
391
|
+
if not environment.ALEMBIC_CONFIG.exists():
|
|
395
392
|
return
|
|
396
393
|
|
|
397
394
|
with cls.get_db_engine().connect() as connection:
|
reflex/reflex.py
CHANGED
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import atexit
|
|
6
6
|
import os
|
|
7
|
-
import webbrowser
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
from typing import List, Optional
|
|
10
9
|
|
|
@@ -14,7 +13,7 @@ from reflex_cli.deployments import deployments_cli
|
|
|
14
13
|
from reflex_cli.utils import dependency
|
|
15
14
|
|
|
16
15
|
from reflex import constants
|
|
17
|
-
from reflex.config import get_config
|
|
16
|
+
from reflex.config import environment, get_config
|
|
18
17
|
from reflex.custom_components.custom_components import custom_components_cli
|
|
19
18
|
from reflex.state import reset_disk_state_manager
|
|
20
19
|
from reflex.utils import console, redir, telemetry
|
|
@@ -275,9 +274,17 @@ def run(
|
|
|
275
274
|
constants.Env.DEV, help="The environment to run the app in."
|
|
276
275
|
),
|
|
277
276
|
frontend: bool = typer.Option(
|
|
278
|
-
False,
|
|
277
|
+
False,
|
|
278
|
+
"--frontend-only",
|
|
279
|
+
help="Execute only frontend.",
|
|
280
|
+
envvar=constants.ENV_FRONTEND_ONLY_ENV_VAR,
|
|
281
|
+
),
|
|
282
|
+
backend: bool = typer.Option(
|
|
283
|
+
False,
|
|
284
|
+
"--backend-only",
|
|
285
|
+
help="Execute only backend.",
|
|
286
|
+
envvar=constants.ENV_BACKEND_ONLY_ENV_VAR,
|
|
279
287
|
),
|
|
280
|
-
backend: bool = typer.Option(False, "--backend-only", help="Execute only backend."),
|
|
281
288
|
frontend_port: str = typer.Option(
|
|
282
289
|
config.frontend_port, help="Specify a different frontend port."
|
|
283
290
|
),
|
|
@@ -292,6 +299,12 @@ def run(
|
|
|
292
299
|
),
|
|
293
300
|
):
|
|
294
301
|
"""Run the app in the current directory."""
|
|
302
|
+
if frontend and backend:
|
|
303
|
+
console.error("Cannot use both --frontend-only and --backend-only options.")
|
|
304
|
+
raise typer.Exit(1)
|
|
305
|
+
os.environ[constants.ENV_BACKEND_ONLY_ENV_VAR] = str(backend).lower()
|
|
306
|
+
os.environ[constants.ENV_FRONTEND_ONLY_ENV_VAR] = str(frontend).lower()
|
|
307
|
+
|
|
295
308
|
_run(env, frontend, backend, frontend_port, backend_port, backend_host, loglevel)
|
|
296
309
|
|
|
297
310
|
|
|
@@ -407,7 +420,7 @@ def db_init():
|
|
|
407
420
|
return
|
|
408
421
|
|
|
409
422
|
# Check the alembic config.
|
|
410
|
-
if
|
|
423
|
+
if environment.ALEMBIC_CONFIG.exists():
|
|
411
424
|
console.error(
|
|
412
425
|
"Database is already initialized. Use "
|
|
413
426
|
"[bold]reflex db makemigrations[/bold] to create schema change "
|
|
@@ -586,18 +599,6 @@ def deploy(
|
|
|
586
599
|
)
|
|
587
600
|
|
|
588
601
|
|
|
589
|
-
@cli.command()
|
|
590
|
-
def demo(
|
|
591
|
-
frontend_port: str = typer.Option(
|
|
592
|
-
"3001", help="Specify a different frontend port."
|
|
593
|
-
),
|
|
594
|
-
backend_port: str = typer.Option("8001", help="Specify a different backend port."),
|
|
595
|
-
):
|
|
596
|
-
"""Run the demo app."""
|
|
597
|
-
# Open the demo app in a terminal.
|
|
598
|
-
webbrowser.open("https://demo.reflex.run")
|
|
599
|
-
|
|
600
|
-
|
|
601
602
|
cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
|
|
602
603
|
cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")
|
|
603
604
|
cli.add_typer(
|
reflex/state.py
CHANGED
|
@@ -8,7 +8,6 @@ import copy
|
|
|
8
8
|
import dataclasses
|
|
9
9
|
import functools
|
|
10
10
|
import inspect
|
|
11
|
-
import os
|
|
12
11
|
import pickle
|
|
13
12
|
import sys
|
|
14
13
|
import uuid
|
|
@@ -31,6 +30,7 @@ from typing import (
|
|
|
31
30
|
Set,
|
|
32
31
|
Tuple,
|
|
33
32
|
Type,
|
|
33
|
+
TypeVar,
|
|
34
34
|
Union,
|
|
35
35
|
cast,
|
|
36
36
|
get_args,
|
|
@@ -40,8 +40,12 @@ from typing import (
|
|
|
40
40
|
from sqlalchemy.orm import DeclarativeBase
|
|
41
41
|
from typing_extensions import Self
|
|
42
42
|
|
|
43
|
+
from reflex import event
|
|
43
44
|
from reflex.config import get_config
|
|
44
45
|
from reflex.istate.data import RouterData
|
|
46
|
+
from reflex.istate.storage import (
|
|
47
|
+
ClientStorageBase,
|
|
48
|
+
)
|
|
45
49
|
from reflex.vars.base import (
|
|
46
50
|
ComputedVar,
|
|
47
51
|
DynamicRouteVar,
|
|
@@ -64,6 +68,7 @@ from redis.exceptions import ResponseError
|
|
|
64
68
|
import reflex.istate.dynamic
|
|
65
69
|
from reflex import constants
|
|
66
70
|
from reflex.base import Base
|
|
71
|
+
from reflex.config import environment
|
|
67
72
|
from reflex.event import (
|
|
68
73
|
BACKGROUND_TASK_MARKER,
|
|
69
74
|
Event,
|
|
@@ -75,6 +80,7 @@ from reflex.utils import console, format, path_ops, prerequisites, types
|
|
|
75
80
|
from reflex.utils.exceptions import (
|
|
76
81
|
ComputedVarShadowsBaseVars,
|
|
77
82
|
ComputedVarShadowsStateVar,
|
|
83
|
+
DynamicComponentInvalidSignature,
|
|
78
84
|
DynamicRouteArgShadowsStateVar,
|
|
79
85
|
EventHandlerShadowsBuiltInStateMethod,
|
|
80
86
|
ImmutableStateError,
|
|
@@ -214,6 +220,7 @@ class EventHandlerSetVar(EventHandler):
|
|
|
214
220
|
Raises:
|
|
215
221
|
AttributeError: If the given Var name does not exist on the state.
|
|
216
222
|
EventHandlerValueError: If the given Var name is not a str
|
|
223
|
+
NotImplementedError: If the setter for the given Var is async
|
|
217
224
|
"""
|
|
218
225
|
from reflex.utils.exceptions import EventHandlerValueError
|
|
219
226
|
|
|
@@ -222,11 +229,20 @@ class EventHandlerSetVar(EventHandler):
|
|
|
222
229
|
raise EventHandlerValueError(
|
|
223
230
|
f"Var name must be passed as a string, got {args[0]!r}"
|
|
224
231
|
)
|
|
232
|
+
|
|
233
|
+
handler = getattr(self.state_cls, constants.SETTER_PREFIX + args[0], None)
|
|
234
|
+
|
|
225
235
|
# Check that the requested Var setter exists on the State at compile time.
|
|
226
|
-
if
|
|
236
|
+
if handler is None:
|
|
227
237
|
raise AttributeError(
|
|
228
238
|
f"Variable `{args[0]}` cannot be set on `{self.state_cls.get_full_name()}`"
|
|
229
239
|
)
|
|
240
|
+
|
|
241
|
+
if asyncio.iscoroutinefunction(handler.fn):
|
|
242
|
+
raise NotImplementedError(
|
|
243
|
+
f"Setter for {args[0]} is async, which is not supported."
|
|
244
|
+
)
|
|
245
|
+
|
|
230
246
|
return super().__call__(*args)
|
|
231
247
|
|
|
232
248
|
|
|
@@ -2047,12 +2063,24 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
2047
2063
|
"""
|
|
2048
2064
|
try:
|
|
2049
2065
|
return pickle.dumps((self._to_schema(), self))
|
|
2050
|
-
except pickle.PicklingError:
|
|
2051
|
-
|
|
2066
|
+
except (pickle.PicklingError, AttributeError) as og_pickle_error:
|
|
2067
|
+
error = (
|
|
2052
2068
|
f"Failed to serialize state {self.get_full_name()} due to unpicklable object. "
|
|
2053
|
-
"This state will not be persisted."
|
|
2069
|
+
"This state will not be persisted. "
|
|
2054
2070
|
)
|
|
2055
|
-
|
|
2071
|
+
try:
|
|
2072
|
+
import dill
|
|
2073
|
+
|
|
2074
|
+
return dill.dumps((self._to_schema(), self))
|
|
2075
|
+
except ImportError:
|
|
2076
|
+
error += (
|
|
2077
|
+
f"Pickle error: {og_pickle_error}. "
|
|
2078
|
+
"Consider `pip install 'dill>=0.3.8'` for more exotic serialization support."
|
|
2079
|
+
)
|
|
2080
|
+
except (pickle.PicklingError, TypeError, ValueError) as ex:
|
|
2081
|
+
error += f"Dill was also unable to pickle the state: {ex}"
|
|
2082
|
+
console.warn(error)
|
|
2083
|
+
return b""
|
|
2056
2084
|
|
|
2057
2085
|
@classmethod
|
|
2058
2086
|
def _deserialize(
|
|
@@ -2091,10 +2119,56 @@ class State(BaseState):
|
|
|
2091
2119
|
is_hydrated: bool = False
|
|
2092
2120
|
|
|
2093
2121
|
|
|
2122
|
+
T = TypeVar("T", bound=BaseState)
|
|
2123
|
+
|
|
2124
|
+
|
|
2125
|
+
def dynamic(func: Callable[[T], Component]):
|
|
2126
|
+
"""Create a dynamically generated components from a state class.
|
|
2127
|
+
|
|
2128
|
+
Args:
|
|
2129
|
+
func: The function to generate the component.
|
|
2130
|
+
|
|
2131
|
+
Returns:
|
|
2132
|
+
The dynamically generated component.
|
|
2133
|
+
|
|
2134
|
+
Raises:
|
|
2135
|
+
DynamicComponentInvalidSignature: If the function does not have exactly one parameter.
|
|
2136
|
+
DynamicComponentInvalidSignature: If the function does not have a type hint for the state class.
|
|
2137
|
+
"""
|
|
2138
|
+
number_of_parameters = len(inspect.signature(func).parameters)
|
|
2139
|
+
|
|
2140
|
+
func_signature = get_type_hints(func)
|
|
2141
|
+
|
|
2142
|
+
if "return" in func_signature:
|
|
2143
|
+
func_signature.pop("return")
|
|
2144
|
+
|
|
2145
|
+
values = list(func_signature.values())
|
|
2146
|
+
|
|
2147
|
+
if number_of_parameters != 1:
|
|
2148
|
+
raise DynamicComponentInvalidSignature(
|
|
2149
|
+
"The function must have exactly one parameter, which is the state class."
|
|
2150
|
+
)
|
|
2151
|
+
|
|
2152
|
+
if len(values) != 1:
|
|
2153
|
+
raise DynamicComponentInvalidSignature(
|
|
2154
|
+
"You must provide a type hint for the state class in the function."
|
|
2155
|
+
)
|
|
2156
|
+
|
|
2157
|
+
state_class: Type[T] = values[0]
|
|
2158
|
+
|
|
2159
|
+
def wrapper() -> Component:
|
|
2160
|
+
from reflex.components.base.fragment import fragment
|
|
2161
|
+
|
|
2162
|
+
return fragment(state_class._evaluate(lambda state: func(state)))
|
|
2163
|
+
|
|
2164
|
+
return wrapper
|
|
2165
|
+
|
|
2166
|
+
|
|
2094
2167
|
class FrontendEventExceptionState(State):
|
|
2095
2168
|
"""Substate for handling frontend exceptions."""
|
|
2096
2169
|
|
|
2097
|
-
|
|
2170
|
+
@event
|
|
2171
|
+
def handle_frontend_exception(self, stack: str, component_stack: str) -> None:
|
|
2098
2172
|
"""Handle frontend exceptions.
|
|
2099
2173
|
|
|
2100
2174
|
If a frontend exception handler is provided, it will be called.
|
|
@@ -2102,6 +2176,7 @@ class FrontendEventExceptionState(State):
|
|
|
2102
2176
|
|
|
2103
2177
|
Args:
|
|
2104
2178
|
stack: The stack trace of the exception.
|
|
2179
|
+
component_stack: The stack trace of the component where the exception occurred.
|
|
2105
2180
|
|
|
2106
2181
|
"""
|
|
2107
2182
|
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
|
@@ -2842,9 +2917,13 @@ class StateManagerDisk(StateManager):
|
|
|
2842
2917
|
for substate in state.get_substates():
|
|
2843
2918
|
substate_token = _substate_key(client_token, substate)
|
|
2844
2919
|
|
|
2920
|
+
fresh_instance = await root_state.get_state(substate)
|
|
2845
2921
|
instance = await self.load_state(substate_token)
|
|
2846
|
-
if instance is None:
|
|
2847
|
-
|
|
2922
|
+
if instance is not None:
|
|
2923
|
+
# Ensure all substates exist, even if they weren't serialized previously.
|
|
2924
|
+
instance.substates = fresh_instance.substates
|
|
2925
|
+
else:
|
|
2926
|
+
instance = fresh_instance
|
|
2848
2927
|
state.substates[substate.get_name()] = instance
|
|
2849
2928
|
instance.parent_state = state
|
|
2850
2929
|
|
|
@@ -3274,11 +3353,7 @@ class StateManagerRedis(StateManager):
|
|
|
3274
3353
|
)
|
|
3275
3354
|
except ResponseError:
|
|
3276
3355
|
# Some redis servers only allow out-of-band configuration, so ignore errors here.
|
|
3277
|
-
|
|
3278
|
-
"REFLEX_IGNORE_REDIS_CONFIG_ERROR",
|
|
3279
|
-
None,
|
|
3280
|
-
)
|
|
3281
|
-
if not ignore_config_error:
|
|
3356
|
+
if not environment.REFLEX_IGNORE_REDIS_CONFIG_ERROR:
|
|
3282
3357
|
raise
|
|
3283
3358
|
async with self.redis.pubsub() as pubsub:
|
|
3284
3359
|
await pubsub.psubscribe(lock_key_channel)
|
|
@@ -3350,143 +3425,6 @@ def get_state_manager() -> StateManager:
|
|
|
3350
3425
|
return app.state_manager
|
|
3351
3426
|
|
|
3352
3427
|
|
|
3353
|
-
class ClientStorageBase:
|
|
3354
|
-
"""Base class for client-side storage."""
|
|
3355
|
-
|
|
3356
|
-
def options(self) -> dict[str, Any]:
|
|
3357
|
-
"""Get the options for the storage.
|
|
3358
|
-
|
|
3359
|
-
Returns:
|
|
3360
|
-
All set options for the storage (not None).
|
|
3361
|
-
"""
|
|
3362
|
-
return {
|
|
3363
|
-
format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
|
|
3364
|
-
}
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
class Cookie(ClientStorageBase, str):
|
|
3368
|
-
"""Represents a state Var that is stored as a cookie in the browser."""
|
|
3369
|
-
|
|
3370
|
-
name: str | None
|
|
3371
|
-
path: str
|
|
3372
|
-
max_age: int | None
|
|
3373
|
-
domain: str | None
|
|
3374
|
-
secure: bool | None
|
|
3375
|
-
same_site: str
|
|
3376
|
-
|
|
3377
|
-
def __new__(
|
|
3378
|
-
cls,
|
|
3379
|
-
object: Any = "",
|
|
3380
|
-
encoding: str | None = None,
|
|
3381
|
-
errors: str | None = None,
|
|
3382
|
-
/,
|
|
3383
|
-
name: str | None = None,
|
|
3384
|
-
path: str = "/",
|
|
3385
|
-
max_age: int | None = None,
|
|
3386
|
-
domain: str | None = None,
|
|
3387
|
-
secure: bool | None = None,
|
|
3388
|
-
same_site: str = "lax",
|
|
3389
|
-
):
|
|
3390
|
-
"""Create a client-side Cookie (str).
|
|
3391
|
-
|
|
3392
|
-
Args:
|
|
3393
|
-
object: The initial object.
|
|
3394
|
-
encoding: The encoding to use.
|
|
3395
|
-
errors: The error handling scheme to use.
|
|
3396
|
-
name: The name of the cookie on the client side.
|
|
3397
|
-
path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
|
|
3398
|
-
max_age: Relative max age of the cookie in seconds from when the client receives it.
|
|
3399
|
-
domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
|
|
3400
|
-
secure: Is the cookie only accessible through HTTPS?
|
|
3401
|
-
same_site: Whether the cookie is sent with third party requests.
|
|
3402
|
-
One of (true|false|none|lax|strict)
|
|
3403
|
-
|
|
3404
|
-
Returns:
|
|
3405
|
-
The client-side Cookie object.
|
|
3406
|
-
|
|
3407
|
-
Note: expires (absolute Date) is not supported at this time.
|
|
3408
|
-
"""
|
|
3409
|
-
if encoding or errors:
|
|
3410
|
-
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
|
3411
|
-
else:
|
|
3412
|
-
inst = super().__new__(cls, object)
|
|
3413
|
-
inst.name = name
|
|
3414
|
-
inst.path = path
|
|
3415
|
-
inst.max_age = max_age
|
|
3416
|
-
inst.domain = domain
|
|
3417
|
-
inst.secure = secure
|
|
3418
|
-
inst.same_site = same_site
|
|
3419
|
-
return inst
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
class LocalStorage(ClientStorageBase, str):
|
|
3423
|
-
"""Represents a state Var that is stored in localStorage in the browser."""
|
|
3424
|
-
|
|
3425
|
-
name: str | None
|
|
3426
|
-
sync: bool = False
|
|
3427
|
-
|
|
3428
|
-
def __new__(
|
|
3429
|
-
cls,
|
|
3430
|
-
object: Any = "",
|
|
3431
|
-
encoding: str | None = None,
|
|
3432
|
-
errors: str | None = None,
|
|
3433
|
-
/,
|
|
3434
|
-
name: str | None = None,
|
|
3435
|
-
sync: bool = False,
|
|
3436
|
-
) -> "LocalStorage":
|
|
3437
|
-
"""Create a client-side localStorage (str).
|
|
3438
|
-
|
|
3439
|
-
Args:
|
|
3440
|
-
object: The initial object.
|
|
3441
|
-
encoding: The encoding to use.
|
|
3442
|
-
errors: The error handling scheme to use.
|
|
3443
|
-
name: The name of the storage key on the client side.
|
|
3444
|
-
sync: Whether changes should be propagated to other tabs.
|
|
3445
|
-
|
|
3446
|
-
Returns:
|
|
3447
|
-
The client-side localStorage object.
|
|
3448
|
-
"""
|
|
3449
|
-
if encoding or errors:
|
|
3450
|
-
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
|
3451
|
-
else:
|
|
3452
|
-
inst = super().__new__(cls, object)
|
|
3453
|
-
inst.name = name
|
|
3454
|
-
inst.sync = sync
|
|
3455
|
-
return inst
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
class SessionStorage(ClientStorageBase, str):
|
|
3459
|
-
"""Represents a state Var that is stored in sessionStorage in the browser."""
|
|
3460
|
-
|
|
3461
|
-
name: str | None
|
|
3462
|
-
|
|
3463
|
-
def __new__(
|
|
3464
|
-
cls,
|
|
3465
|
-
object: Any = "",
|
|
3466
|
-
encoding: str | None = None,
|
|
3467
|
-
errors: str | None = None,
|
|
3468
|
-
/,
|
|
3469
|
-
name: str | None = None,
|
|
3470
|
-
) -> "SessionStorage":
|
|
3471
|
-
"""Create a client-side sessionStorage (str).
|
|
3472
|
-
|
|
3473
|
-
Args:
|
|
3474
|
-
object: The initial object.
|
|
3475
|
-
encoding: The encoding to use.
|
|
3476
|
-
errors: The error handling scheme to use
|
|
3477
|
-
name: The name of the storage on the client side
|
|
3478
|
-
|
|
3479
|
-
Returns:
|
|
3480
|
-
The client-side sessionStorage object.
|
|
3481
|
-
"""
|
|
3482
|
-
if encoding or errors:
|
|
3483
|
-
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
|
3484
|
-
else:
|
|
3485
|
-
inst = super().__new__(cls, object)
|
|
3486
|
-
inst.name = name
|
|
3487
|
-
return inst
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
3428
|
class MutableProxy(wrapt.ObjectProxy):
|
|
3491
3429
|
"""A proxy for a mutable object that tracks changes."""
|
|
3492
3430
|
|
reflex/style.py
CHANGED
|
@@ -23,7 +23,7 @@ LiteralColorMode = Literal["system", "light", "dark"]
|
|
|
23
23
|
|
|
24
24
|
# Reference the global ColorModeContext
|
|
25
25
|
color_mode_imports = {
|
|
26
|
-
f"
|
|
26
|
+
f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
|
|
27
27
|
"react": [ImportVar(tag="useContext")],
|
|
28
28
|
}
|
|
29
29
|
|
reflex/testing.py
CHANGED
|
@@ -249,7 +249,8 @@ class AppHarness:
|
|
|
249
249
|
return textwrap.dedent(source)
|
|
250
250
|
|
|
251
251
|
def _initialize_app(self):
|
|
252
|
-
|
|
252
|
+
# disable telemetry reporting for tests
|
|
253
|
+
os.environ["TELEMETRY_ENABLED"] = "false"
|
|
253
254
|
self.app_path.mkdir(parents=True, exist_ok=True)
|
|
254
255
|
if self.app_source is not None:
|
|
255
256
|
app_globals = self._get_globals_from_signature(self.app_source)
|
reflex/utils/build.py
CHANGED
|
@@ -23,18 +23,6 @@ def set_env_json():
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def set_os_env(**kwargs):
|
|
27
|
-
"""Set os environment variables.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
kwargs: env key word args.
|
|
31
|
-
"""
|
|
32
|
-
for key, value in kwargs.items():
|
|
33
|
-
if not value:
|
|
34
|
-
continue
|
|
35
|
-
os.environ[key.upper()] = value
|
|
36
|
-
|
|
37
|
-
|
|
38
26
|
def generate_sitemap_config(deploy_url: str, export=False):
|
|
39
27
|
"""Generate the sitemap config file.
|
|
40
28
|
|
reflex/utils/exceptions.py
CHANGED
|
@@ -135,3 +135,11 @@ class SetUndefinedStateVarError(ReflexError, AttributeError):
|
|
|
135
135
|
|
|
136
136
|
class StateSchemaMismatchError(ReflexError, TypeError):
|
|
137
137
|
"""Raised when the serialized schema of a state class does not match the current schema."""
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class EnvironmentVarValueError(ReflexError, ValueError):
|
|
141
|
+
"""Raised when an environment variable is set to an invalid value."""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class DynamicComponentInvalidSignature(ReflexError, TypeError):
|
|
145
|
+
"""Raised when a dynamic component has an invalid signature."""
|