Merge branch 'main' into lendemor/builtins_states
This commit is contained in:
commit
703a6da3d7
36
poetry.lock
generated
36
poetry.lock
generated
@ -570,18 +570,18 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.2"
|
||||
version = "0.115.3"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.115.2-py3-none-any.whl", hash = "sha256:61704c71286579cc5a598763905928f24ee98bfcc07aabe84cfefb98812bbc86"},
|
||||
{file = "fastapi-0.115.2.tar.gz", hash = "sha256:3995739e0b09fa12f984bce8fa9ae197b35d433750d3d312422d846e283697ee"},
|
||||
{file = "fastapi-0.115.3-py3-none-any.whl", hash = "sha256:8035e8f9a2b0aa89cea03b6c77721178ed5358e1aea4cd8570d9466895c0638c"},
|
||||
{file = "fastapi-0.115.3.tar.gz", hash = "sha256:c091c6a35599c036d676fa24bd4a6e19fa30058d93d950216cdc672881f6f7db"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
starlette = ">=0.37.2,<0.41.0"
|
||||
starlette = ">=0.40.0,<0.42.0"
|
||||
typing-extensions = ">=4.8.0"
|
||||
|
||||
[package.extras]
|
||||
@ -1977,6 +1977,20 @@ files = [
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
|
||||
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-engineio"
|
||||
version = "4.10.1"
|
||||
@ -2253,13 +2267,13 @@ idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.9.2"
|
||||
version = "13.9.3"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"},
|
||||
{file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"},
|
||||
{file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"},
|
||||
{file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2525,13 +2539,13 @@ SQLAlchemy = ">=2.0.14,<2.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.40.0"
|
||||
version = "0.41.0"
|
||||
description = "The little ASGI library that shines."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "starlette-0.40.0-py3-none-any.whl", hash = "sha256:c494a22fae73805376ea6bf88439783ecfba9aac88a43911b48c653437e784c4"},
|
||||
{file = "starlette-0.40.0.tar.gz", hash = "sha256:1a3139688fb298ce5e2d661d37046a66ad996ce94be4d4983be019a23a04ea35"},
|
||||
{file = "starlette-0.41.0-py3-none-any.whl", hash = "sha256:a0193a3c413ebc9c78bff1c3546a45bb8c8bcb4a84cae8747d650a65bd37210a"},
|
||||
{file = "starlette-0.41.0.tar.gz", hash = "sha256:39cbd8768b107d68bfe1ff1672b38a2c38b49777de46d2a592841d58e3bf7c2a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3033,4 +3047,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "8090ccaeca173bd8612e17a0b8d157d7492618e49450abd1c8373e2976349db0"
|
||||
content-hash = "c5da15520cef58124f6699007c81158036840469d4f9972592d72bd456c45e7e"
|
||||
|
@ -33,6 +33,7 @@ jinja2 = ">=3.1.2,<4.0"
|
||||
psutil = ">=5.9.4,<7.0"
|
||||
pydantic = ">=1.10.2,<3.0"
|
||||
python-multipart = ">=0.0.5,<0.1"
|
||||
python-dotenv = ">=1.0.1"
|
||||
python-socketio = ">=5.7.0,<6.0"
|
||||
redis = ">=4.3.5,<6.0"
|
||||
rich = ">=13.0.0,<14.0"
|
||||
|
@ -321,14 +321,14 @@ _MAPPING: dict = {
|
||||
"window_alert",
|
||||
],
|
||||
"istate.builtins": ["ComponentState", "State"],
|
||||
"middleware": ["middleware", "Middleware"],
|
||||
"model": ["session", "Model"],
|
||||
"state": [
|
||||
"var",
|
||||
"istate.storage": [
|
||||
"Cookie",
|
||||
"LocalStorage",
|
||||
"SessionStorage",
|
||||
],
|
||||
"middleware": ["middleware", "Middleware"],
|
||||
"model": ["session", "Model"],
|
||||
"state": ["var"],
|
||||
"style": ["Style", "toggle_color_mode"],
|
||||
"utils.imports": ["ImportVar"],
|
||||
"utils.serializers": ["serializer"],
|
||||
|
@ -176,14 +176,14 @@ from .event import window_alert as window_alert
|
||||
from .experimental import _x as _x
|
||||
from .istate.builtins import ComponentState as ComponentState
|
||||
from .istate.builtins import State as State
|
||||
from .istate.storage import Cookie as Cookie
|
||||
from .istate.storage import LocalStorage as LocalStorage
|
||||
from .istate.storage import SessionStorage as SessionStorage
|
||||
from .middleware import Middleware as Middleware
|
||||
from .middleware import middleware as middleware
|
||||
from .model import Model as Model
|
||||
from .model import session as session
|
||||
from .page import page as page
|
||||
from .state import Cookie as Cookie
|
||||
from .state import LocalStorage as LocalStorage
|
||||
from .state import SessionStorage as SessionStorage
|
||||
from .state import var as var
|
||||
from .style import Style as Style
|
||||
from .style import toggle_color_mode as toggle_color_mode
|
||||
|
@ -28,7 +28,8 @@ from reflex.components.base import (
|
||||
Title,
|
||||
)
|
||||
from reflex.components.component import Component, ComponentStyle, CustomComponent
|
||||
from reflex.state import BaseState, Cookie, LocalStorage, SessionStorage
|
||||
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
|
||||
from reflex.state import BaseState
|
||||
from reflex.style import Style
|
||||
from reflex.utils import console, format, imports, path_ops
|
||||
from reflex.utils.imports import ImportVar, ParsedImportDict
|
||||
|
@ -109,19 +109,6 @@ class DataEditorTheme(Base):
|
||||
text_medium: Optional[str] = None
|
||||
|
||||
|
||||
def on_edit_spec(pos, data: dict[str, Any]):
|
||||
"""The on edit spec function.
|
||||
|
||||
Args:
|
||||
pos: The position of the edit event.
|
||||
data: The data of the edit event.
|
||||
|
||||
Returns:
|
||||
The position and data.
|
||||
"""
|
||||
return [pos, data]
|
||||
|
||||
|
||||
class Bounds(TypedDict):
|
||||
"""The bounds of the group header."""
|
||||
|
||||
@ -149,7 +136,7 @@ class Rectangle(TypedDict):
|
||||
class GridSelectionCurrent(TypedDict):
|
||||
"""The current selection."""
|
||||
|
||||
cell: list[int]
|
||||
cell: tuple[int, int]
|
||||
range: Rectangle
|
||||
rangeStack: list[Rectangle]
|
||||
|
||||
@ -167,7 +154,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
|
||||
|
||||
kind: str
|
||||
group: str
|
||||
location: list[int]
|
||||
location: tuple[int, int]
|
||||
bounds: Bounds
|
||||
isEdge: bool
|
||||
shiftKey: bool
|
||||
@ -178,7 +165,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
|
||||
localEventY: int
|
||||
button: int
|
||||
buttons: int
|
||||
scrollEdge: list[int]
|
||||
scrollEdge: tuple[int, int]
|
||||
|
||||
|
||||
class GridCell(TypedDict):
|
||||
@ -306,10 +293,10 @@ class DataEditor(NoSSRComponent):
|
||||
on_cell_context_menu: EventHandler[identity_event(Tuple[int, int])]
|
||||
|
||||
# Fired when a cell is edited.
|
||||
on_cell_edited: EventHandler[on_edit_spec]
|
||||
on_cell_edited: EventHandler[identity_event(Tuple[int, int], GridCell)]
|
||||
|
||||
# Fired when a group header is clicked.
|
||||
on_group_header_clicked: EventHandler[on_edit_spec]
|
||||
on_group_header_clicked: EventHandler[identity_event(Tuple[int, int], GridCell)]
|
||||
|
||||
# Fired when a group header is right-clicked.
|
||||
on_group_header_context_menu: EventHandler[
|
||||
@ -335,7 +322,9 @@ class DataEditor(NoSSRComponent):
|
||||
on_delete: EventHandler[identity_event(GridSelection)]
|
||||
|
||||
# Fired when editing is finished.
|
||||
on_finished_editing: EventHandler[identity_event(Union[GridCell, None], list[int])]
|
||||
on_finished_editing: EventHandler[
|
||||
identity_event(Union[GridCell, None], tuple[int, int])
|
||||
]
|
||||
|
||||
# Fired when a row is appended.
|
||||
on_row_appended: EventHandler[empty_event]
|
||||
|
@ -78,8 +78,6 @@ class DataEditorTheme(Base):
|
||||
text_light: Optional[str]
|
||||
text_medium: Optional[str]
|
||||
|
||||
def on_edit_spec(pos, data: dict[str, Any]): ...
|
||||
|
||||
class Bounds(TypedDict):
|
||||
x: int
|
||||
y: int
|
||||
@ -96,7 +94,7 @@ class Rectangle(TypedDict):
|
||||
height: int
|
||||
|
||||
class GridSelectionCurrent(TypedDict):
|
||||
cell: list[int]
|
||||
cell: tuple[int, int]
|
||||
range: Rectangle
|
||||
rangeStack: list[Rectangle]
|
||||
|
||||
@ -108,7 +106,7 @@ class GridSelection(TypedDict):
|
||||
class GroupHeaderClickedEventArgs(TypedDict):
|
||||
kind: str
|
||||
group: str
|
||||
location: list[int]
|
||||
location: tuple[int, int]
|
||||
bounds: Bounds
|
||||
isEdge: bool
|
||||
shiftKey: bool
|
||||
@ -119,7 +117,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
|
||||
localEventY: int
|
||||
button: int
|
||||
buttons: int
|
||||
scrollEdge: list[int]
|
||||
scrollEdge: tuple[int, int]
|
||||
|
||||
class GridCell(TypedDict):
|
||||
span: Optional[List[int]]
|
||||
@ -189,17 +187,17 @@ class DataEditor(NoSSRComponent):
|
||||
on_cell_activated: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_cell_clicked: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_cell_context_menu: Optional[EventType[tuple[int, int]]] = None,
|
||||
on_cell_edited: Optional[EventType] = None,
|
||||
on_cell_edited: Optional[EventType[tuple[int, int], GridCell]] = None,
|
||||
on_click: Optional[EventType[[]]] = None,
|
||||
on_column_resize: Optional[EventType[GridColumn, int]] = None,
|
||||
on_context_menu: Optional[EventType[[]]] = None,
|
||||
on_delete: Optional[EventType[GridSelection]] = None,
|
||||
on_double_click: Optional[EventType[[]]] = None,
|
||||
on_finished_editing: Optional[
|
||||
EventType[Union[GridCell, None], list[int]]
|
||||
EventType[Union[GridCell, None], tuple[int, int]]
|
||||
] = None,
|
||||
on_focus: Optional[EventType[[]]] = None,
|
||||
on_group_header_clicked: Optional[EventType] = None,
|
||||
on_group_header_clicked: Optional[EventType[tuple[int, int], GridCell]] = None,
|
||||
on_group_header_context_menu: Optional[
|
||||
EventType[int, GroupHeaderClickedEventArgs]
|
||||
] = None,
|
||||
|
@ -5,7 +5,9 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Literal, Optional, Union, overload
|
||||
|
||||
from reflex.event import EventType
|
||||
from reflex.event import (
|
||||
EventType,
|
||||
)
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""React Player component for audio and video."""
|
||||
|
||||
from . import react_player
|
||||
from .audio import Audio
|
||||
from .video import Video
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
import reflex
|
||||
from reflex.components.react_player.react_player import ReactPlayer
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
@ -58,7 +59,9 @@ class Audio(ReactPlayer):
|
||||
on_play: Optional[EventType[[]]] = None,
|
||||
on_playback_quality_change: Optional[EventType[[]]] = None,
|
||||
on_playback_rate_change: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[EventType] = None,
|
||||
on_progress: Optional[
|
||||
EventType[reflex.components.react_player.react_player.Progress]
|
||||
] = None,
|
||||
on_ready: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_seek: Optional[EventType[float]] = None,
|
||||
|
@ -2,11 +2,22 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventHandler, empty_event, identity_event
|
||||
from reflex.vars.base import Var
|
||||
|
||||
|
||||
class Progress(TypedDict):
|
||||
"""Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds."""
|
||||
|
||||
played: float
|
||||
playedSeconds: float
|
||||
loaded: float
|
||||
loadedSeconds: float
|
||||
|
||||
|
||||
class ReactPlayer(NoSSRComponent):
|
||||
"""Using react-player and not implement all props and callback yet.
|
||||
reference: https://github.com/cookpete/react-player.
|
||||
@ -55,7 +66,7 @@ class ReactPlayer(NoSSRComponent):
|
||||
on_play: EventHandler[empty_event]
|
||||
|
||||
# Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds. eg { played: 0.12, playedSeconds: 11.3, loaded: 0.34, loadedSeconds: 16.7 }
|
||||
on_progress: EventHandler[lambda progress: [progress]]
|
||||
on_progress: EventHandler[identity_event(Progress)]
|
||||
|
||||
# Callback containing duration of the media, in seconds.
|
||||
on_duration: EventHandler[identity_event(float)]
|
||||
|
@ -5,11 +5,19 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from reflex.components.component import NoSSRComponent
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
|
||||
class Progress(TypedDict):
|
||||
played: float
|
||||
playedSeconds: float
|
||||
loaded: float
|
||||
loadedSeconds: float
|
||||
|
||||
class ReactPlayer(NoSSRComponent):
|
||||
@overload
|
||||
@classmethod
|
||||
@ -56,7 +64,7 @@ class ReactPlayer(NoSSRComponent):
|
||||
on_play: Optional[EventType[[]]] = None,
|
||||
on_playback_quality_change: Optional[EventType[[]]] = None,
|
||||
on_playback_rate_change: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[EventType] = None,
|
||||
on_progress: Optional[EventType[Progress]] = None,
|
||||
on_ready: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_seek: Optional[EventType[float]] = None,
|
||||
|
@ -5,6 +5,7 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Dict, Optional, Union, overload
|
||||
|
||||
import reflex
|
||||
from reflex.components.react_player.react_player import ReactPlayer
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
@ -58,7 +59,9 @@ class Video(ReactPlayer):
|
||||
on_play: Optional[EventType[[]]] = None,
|
||||
on_playback_quality_change: Optional[EventType[[]]] = None,
|
||||
on_playback_rate_change: Optional[EventType[[]]] = None,
|
||||
on_progress: Optional[EventType] = None,
|
||||
on_progress: Optional[
|
||||
EventType[reflex.components.react_player.react_player.Progress]
|
||||
] = None,
|
||||
on_ready: Optional[EventType[[]]] = None,
|
||||
on_scroll: Optional[EventType[[]]] = None,
|
||||
on_seek: Optional[EventType[float]] = None,
|
||||
|
@ -436,6 +436,9 @@ class Config(Base):
|
||||
# Attributes that were explicitly set by the user.
|
||||
_non_default_attributes: Set[str] = pydantic.PrivateAttr(set())
|
||||
|
||||
# Path to file containing key-values pairs to override in the environment; Dotenv format.
|
||||
env_file: Optional[str] = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize the config values.
|
||||
|
||||
@ -477,6 +480,7 @@ class Config(Base):
|
||||
|
||||
def update_from_env(self) -> dict[str, Any]:
|
||||
"""Update the config values based on set environment variables.
|
||||
If there is a set env_file, it is loaded first.
|
||||
|
||||
Returns:
|
||||
The updated config values.
|
||||
@ -486,6 +490,12 @@ class Config(Base):
|
||||
"""
|
||||
from reflex.utils.exceptions import EnvVarValueError
|
||||
|
||||
if self.env_file:
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# load env file if exists
|
||||
load_dotenv(self.env_file, override=True)
|
||||
|
||||
updated_values = {}
|
||||
# Iterate over the fields.
|
||||
for key, field in self.__fields__.items():
|
||||
|
144
reflex/istate/storage.py
Normal file
144
reflex/istate/storage.py
Normal file
@ -0,0 +1,144 @@
|
||||
"""Client-side storage classes for reflex state variables."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from reflex.utils import format
|
||||
|
||||
|
||||
class ClientStorageBase:
|
||||
"""Base class for client-side storage."""
|
||||
|
||||
def options(self) -> dict[str, Any]:
|
||||
"""Get the options for the storage.
|
||||
|
||||
Returns:
|
||||
All set options for the storage (not None).
|
||||
"""
|
||||
return {
|
||||
format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
|
||||
}
|
||||
|
||||
|
||||
class Cookie(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored as a cookie in the browser."""
|
||||
|
||||
name: str | None
|
||||
path: str
|
||||
max_age: int | None
|
||||
domain: str | None
|
||||
secure: bool | None
|
||||
same_site: str
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
path: str = "/",
|
||||
max_age: int | None = None,
|
||||
domain: str | None = None,
|
||||
secure: bool | None = None,
|
||||
same_site: str = "lax",
|
||||
):
|
||||
"""Create a client-side Cookie (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the cookie on the client side.
|
||||
path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
|
||||
max_age: Relative max age of the cookie in seconds from when the client receives it.
|
||||
domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
|
||||
secure: Is the cookie only accessible through HTTPS?
|
||||
same_site: Whether the cookie is sent with third party requests.
|
||||
One of (true|false|none|lax|strict)
|
||||
|
||||
Returns:
|
||||
The client-side Cookie object.
|
||||
|
||||
Note: expires (absolute Date) is not supported at this time.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.path = path
|
||||
inst.max_age = max_age
|
||||
inst.domain = domain
|
||||
inst.secure = secure
|
||||
inst.same_site = same_site
|
||||
return inst
|
||||
|
||||
|
||||
class LocalStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in localStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
sync: bool = False
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
sync: bool = False,
|
||||
) -> "LocalStorage":
|
||||
"""Create a client-side localStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the storage key on the client side.
|
||||
sync: Whether changes should be propagated to other tabs.
|
||||
|
||||
Returns:
|
||||
The client-side localStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.sync = sync
|
||||
return inst
|
||||
|
||||
|
||||
class SessionStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in sessionStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
) -> "SessionStorage":
|
||||
"""Create a client-side sessionStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use
|
||||
name: The name of the storage on the client side
|
||||
|
||||
Returns:
|
||||
The client-side sessionStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
return inst
|
140
reflex/state.py
140
reflex/state.py
@ -41,6 +41,9 @@ from typing_extensions import Self
|
||||
|
||||
from reflex.config import get_config
|
||||
from reflex.istate.data import RouterData
|
||||
from reflex.istate.storage import (
|
||||
ClientStorageBase,
|
||||
)
|
||||
from reflex.vars.base import (
|
||||
ComputedVar,
|
||||
DynamicRouteVar,
|
||||
@ -3176,143 +3179,6 @@ def get_state_manager() -> StateManager:
|
||||
return app.state_manager
|
||||
|
||||
|
||||
class ClientStorageBase:
|
||||
"""Base class for client-side storage."""
|
||||
|
||||
def options(self) -> dict[str, Any]:
|
||||
"""Get the options for the storage.
|
||||
|
||||
Returns:
|
||||
All set options for the storage (not None).
|
||||
"""
|
||||
return {
|
||||
format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
|
||||
}
|
||||
|
||||
|
||||
class Cookie(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored as a cookie in the browser."""
|
||||
|
||||
name: str | None
|
||||
path: str
|
||||
max_age: int | None
|
||||
domain: str | None
|
||||
secure: bool | None
|
||||
same_site: str
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
path: str = "/",
|
||||
max_age: int | None = None,
|
||||
domain: str | None = None,
|
||||
secure: bool | None = None,
|
||||
same_site: str = "lax",
|
||||
):
|
||||
"""Create a client-side Cookie (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the cookie on the client side.
|
||||
path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
|
||||
max_age: Relative max age of the cookie in seconds from when the client receives it.
|
||||
domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
|
||||
secure: Is the cookie only accessible through HTTPS?
|
||||
same_site: Whether the cookie is sent with third party requests.
|
||||
One of (true|false|none|lax|strict)
|
||||
|
||||
Returns:
|
||||
The client-side Cookie object.
|
||||
|
||||
Note: expires (absolute Date) is not supported at this time.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.path = path
|
||||
inst.max_age = max_age
|
||||
inst.domain = domain
|
||||
inst.secure = secure
|
||||
inst.same_site = same_site
|
||||
return inst
|
||||
|
||||
|
||||
class LocalStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in localStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
sync: bool = False
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
sync: bool = False,
|
||||
) -> "LocalStorage":
|
||||
"""Create a client-side localStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use.
|
||||
name: The name of the storage key on the client side.
|
||||
sync: Whether changes should be propagated to other tabs.
|
||||
|
||||
Returns:
|
||||
The client-side localStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
inst.sync = sync
|
||||
return inst
|
||||
|
||||
|
||||
class SessionStorage(ClientStorageBase, str):
|
||||
"""Represents a state Var that is stored in sessionStorage in the browser."""
|
||||
|
||||
name: str | None
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
object: Any = "",
|
||||
encoding: str | None = None,
|
||||
errors: str | None = None,
|
||||
/,
|
||||
name: str | None = None,
|
||||
) -> "SessionStorage":
|
||||
"""Create a client-side sessionStorage (str).
|
||||
|
||||
Args:
|
||||
object: The initial object.
|
||||
encoding: The encoding to use.
|
||||
errors: The error handling scheme to use
|
||||
name: The name of the storage on the client side
|
||||
|
||||
Returns:
|
||||
The client-side sessionStorage object.
|
||||
"""
|
||||
if encoding or errors:
|
||||
inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
|
||||
else:
|
||||
inst = super().__new__(cls, object)
|
||||
inst.name = name
|
||||
return inst
|
||||
|
||||
|
||||
class MutableProxy(wrapt.ObjectProxy):
|
||||
"""A proxy for a mutable object that tracks changes."""
|
||||
|
||||
|
@ -214,7 +214,9 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
|
||||
return res
|
||||
|
||||
|
||||
def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
|
||||
def _generate_imports(
|
||||
typing_imports: Iterable[str],
|
||||
) -> list[ast.ImportFrom | ast.Import]:
|
||||
"""Generate the import statements for the stub file.
|
||||
|
||||
Args:
|
||||
@ -228,6 +230,7 @@ def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
|
||||
ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values])
|
||||
for name, values in DEFAULT_IMPORTS.items()
|
||||
],
|
||||
ast.Import([ast.alias("reflex")]),
|
||||
]
|
||||
|
||||
|
||||
@ -372,12 +375,13 @@ def _extract_class_props_as_ast_nodes(
|
||||
return kwargs
|
||||
|
||||
|
||||
def type_to_ast(typ) -> ast.AST:
|
||||
def type_to_ast(typ, cls: type) -> ast.AST:
|
||||
"""Converts any type annotation into its AST representation.
|
||||
Handles nested generic types, unions, etc.
|
||||
|
||||
Args:
|
||||
typ: The type annotation to convert.
|
||||
cls: The class where the type annotation is used.
|
||||
|
||||
Returns:
|
||||
The AST representation of the type annotation.
|
||||
@ -390,6 +394,16 @@ def type_to_ast(typ) -> ast.AST:
|
||||
# Handle plain types (int, str, custom classes, etc.)
|
||||
if origin is None:
|
||||
if hasattr(typ, "__name__"):
|
||||
if typ.__module__.startswith("reflex."):
|
||||
typ_parts = typ.__module__.split(".")
|
||||
cls_parts = cls.__module__.split(".")
|
||||
|
||||
zipped = list(zip(typ_parts, cls_parts, strict=False))
|
||||
|
||||
if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts):
|
||||
return ast.Name(id=typ.__name__)
|
||||
|
||||
return ast.Name(id=typ.__module__ + "." + typ.__name__)
|
||||
return ast.Name(id=typ.__name__)
|
||||
elif hasattr(typ, "_name"):
|
||||
return ast.Name(id=typ._name)
|
||||
@ -406,7 +420,7 @@ def type_to_ast(typ) -> ast.AST:
|
||||
return ast.Name(id=base_name)
|
||||
|
||||
# Convert all type arguments recursively
|
||||
arg_nodes = [type_to_ast(arg) for arg in args]
|
||||
arg_nodes = [type_to_ast(arg, cls) for arg in args]
|
||||
|
||||
# Special case for single-argument types (like List[T] or Optional[T])
|
||||
if len(arg_nodes) == 1:
|
||||
@ -487,7 +501,7 @@ def _generate_component_create_functiondef(
|
||||
]
|
||||
|
||||
# Convert each argument type to its AST representation
|
||||
type_args = [type_to_ast(arg) for arg in arguments_without_var]
|
||||
type_args = [type_to_ast(arg, cls=clz) for arg in arguments_without_var]
|
||||
|
||||
# Join the type arguments with commas for EventType
|
||||
args_str = ", ".join(ast.unparse(arg) for arg in type_args)
|
||||
|
Loading…
Reference in New Issue
Block a user