Merge remote-tracking branch 'upstream/main' into poc_shared_state
This commit is contained in:
commit
98dcec5177
2
.github/workflows/benchmarks.yml
vendored
2
.github/workflows/benchmarks.yml
vendored
@ -17,7 +17,7 @@ defaults:
|
||||
env:
|
||||
PYTHONIOENCODING: 'utf8'
|
||||
TELEMETRY_ENABLED: false
|
||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
NODE_OPTIONS: '--max_old_space_size=8192'
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
|
||||
jobs:
|
||||
|
2
.github/workflows/integration_tests.yml
vendored
2
.github/workflows/integration_tests.yml
vendored
@ -29,7 +29,7 @@ env:
|
||||
# - Best effort print lines that contain illegal chars (map to some default char, etc.)
|
||||
PYTHONIOENCODING: 'utf8'
|
||||
TELEMETRY_ENABLED: false
|
||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
NODE_OPTIONS: '--max_old_space_size=8192'
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
|
||||
jobs:
|
||||
|
@ -12,7 +12,10 @@ def BackgroundTask():
|
||||
"""Test that background tasks work as expected."""
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
import reflex as rx
|
||||
from reflex.state import ImmutableStateError
|
||||
|
||||
class State(rx.State):
|
||||
counter: int = 0
|
||||
@ -71,6 +74,38 @@ def BackgroundTask():
|
||||
self.racy_task(), self.racy_task(), self.racy_task(), self.racy_task()
|
||||
)
|
||||
|
||||
@rx.background
|
||||
async def nested_async_with_self(self):
|
||||
async with self:
|
||||
self.counter += 1
|
||||
with pytest.raises(ImmutableStateError):
|
||||
async with self:
|
||||
self.counter += 1
|
||||
|
||||
async def triple_count(self):
|
||||
third_state = await self.get_state(ThirdState)
|
||||
await third_state._triple_count()
|
||||
|
||||
class OtherState(rx.State):
|
||||
@rx.background
|
||||
async def get_other_state(self):
|
||||
async with self:
|
||||
state = await self.get_state(State)
|
||||
state.counter += 1
|
||||
await state.triple_count()
|
||||
with pytest.raises(ImmutableStateError):
|
||||
await state.triple_count()
|
||||
with pytest.raises(ImmutableStateError):
|
||||
state.counter += 1
|
||||
async with state:
|
||||
state.counter += 1
|
||||
await state.triple_count()
|
||||
|
||||
class ThirdState(rx.State):
|
||||
async def _triple_count(self):
|
||||
state = await self.get_state(State)
|
||||
state.counter *= 3
|
||||
|
||||
def index() -> rx.Component:
|
||||
return rx.vstack(
|
||||
rx.chakra.input(
|
||||
@ -109,6 +144,16 @@ def BackgroundTask():
|
||||
on_click=State.handle_racy_event,
|
||||
id="racy-increment",
|
||||
),
|
||||
rx.button(
|
||||
"Nested Async with Self",
|
||||
on_click=State.nested_async_with_self,
|
||||
id="nested-async-with-self",
|
||||
),
|
||||
rx.button(
|
||||
"Increment from OtherState",
|
||||
on_click=OtherState.get_other_state,
|
||||
id="increment-from-other-state",
|
||||
),
|
||||
rx.button("Reset", on_click=State.reset_counter, id="reset"),
|
||||
)
|
||||
|
||||
@ -230,3 +275,61 @@ def test_background_task(
|
||||
assert background_task._poll_for(
|
||||
lambda: not background_task.app_instance.background_tasks # type: ignore
|
||||
)
|
||||
|
||||
|
||||
def test_nested_async_with_self(
|
||||
background_task: AppHarness,
|
||||
driver: WebDriver,
|
||||
token: str,
|
||||
):
|
||||
"""Test that nested async with self in the same coroutine raises Exception.
|
||||
|
||||
Args:
|
||||
background_task: harness for BackgroundTask app.
|
||||
driver: WebDriver instance.
|
||||
token: The token for the connected client.
|
||||
"""
|
||||
assert background_task.app_instance is not None
|
||||
|
||||
# get a reference to all buttons
|
||||
nested_async_with_self_button = driver.find_element(By.ID, "nested-async-with-self")
|
||||
increment_button = driver.find_element(By.ID, "increment")
|
||||
|
||||
# get a reference to the counter
|
||||
counter = driver.find_element(By.ID, "counter")
|
||||
assert background_task._poll_for(lambda: counter.text == "0", timeout=5)
|
||||
|
||||
nested_async_with_self_button.click()
|
||||
assert background_task._poll_for(lambda: counter.text == "1", timeout=5)
|
||||
|
||||
increment_button.click()
|
||||
assert background_task._poll_for(lambda: counter.text == "2", timeout=5)
|
||||
|
||||
|
||||
def test_get_state(
|
||||
background_task: AppHarness,
|
||||
driver: WebDriver,
|
||||
token: str,
|
||||
):
|
||||
"""Test that get_state returns a state bound to the correct StateProxy.
|
||||
|
||||
Args:
|
||||
background_task: harness for BackgroundTask app.
|
||||
driver: WebDriver instance.
|
||||
token: The token for the connected client.
|
||||
"""
|
||||
assert background_task.app_instance is not None
|
||||
|
||||
# get a reference to all buttons
|
||||
other_state_button = driver.find_element(By.ID, "increment-from-other-state")
|
||||
increment_button = driver.find_element(By.ID, "increment")
|
||||
|
||||
# get a reference to the counter
|
||||
counter = driver.find_element(By.ID, "counter")
|
||||
assert background_task._poll_for(lambda: counter.text == "0", timeout=5)
|
||||
|
||||
other_state_button.click()
|
||||
assert background_task._poll_for(lambda: counter.text == "12", timeout=5)
|
||||
|
||||
increment_button.click()
|
||||
assert background_task._poll_for(lambda: counter.text == "13", timeout=5)
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "reflex"
|
||||
version = "0.5.7"
|
||||
version = "0.5.9"
|
||||
description = "Web apps in pure Python."
|
||||
license = "Apache-2.0"
|
||||
authors = [
|
||||
|
@ -23,6 +23,11 @@ export const useClientSideRouting = () => {
|
||||
router.replace({
|
||||
pathname: window.location.pathname,
|
||||
query: window.location.search.slice(1),
|
||||
}).then(()=>{
|
||||
// Check if the current route is /404
|
||||
if (router.pathname === '/404') {
|
||||
setRouteNotFound(true); // Mark as an actual 404
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
setRouteNotFound(true) // navigation failed, so this is a real 404
|
||||
|
@ -647,7 +647,12 @@ export const useEventLoop = (
|
||||
const [connectErrors, setConnectErrors] = useState([]);
|
||||
|
||||
// Function to add new events to the event queue.
|
||||
const addEvents = (events, _e, event_actions) => {
|
||||
const addEvents = (events, args, event_actions) => {
|
||||
if (!(args instanceof Array)) {
|
||||
args = [args];
|
||||
}
|
||||
const _e = args.filter((o) => o?.preventDefault !== undefined)[0]
|
||||
|
||||
if (event_actions?.preventDefault && _e?.preventDefault) {
|
||||
_e.preventDefault();
|
||||
}
|
||||
@ -777,7 +782,7 @@ export const useEventLoop = (
|
||||
// Route after the initial page hydration.
|
||||
useEffect(() => {
|
||||
const change_start = () => {
|
||||
const main_state_dispatch = dispatch["state"]
|
||||
const main_state_dispatch = dispatch["reflex___state____state"]
|
||||
if (main_state_dispatch !== undefined) {
|
||||
main_state_dispatch({ is_hydrated: false })
|
||||
}
|
||||
|
@ -112,11 +112,29 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
||||
EventSpec: The window alert event.
|
||||
|
||||
"""
|
||||
from reflex.components.sonner.toast import Toaster, toast
|
||||
|
||||
error = traceback.format_exc()
|
||||
|
||||
console.error(f"[Reflex Backend Exception]\n {error}\n")
|
||||
|
||||
return window_alert("An error occurred. See logs for details.")
|
||||
error_message = (
|
||||
["Contact the website administrator."]
|
||||
if is_prod_mode()
|
||||
else [f"{type(exception).__name__}: {exception}.", "See logs for details."]
|
||||
)
|
||||
if Toaster.is_used:
|
||||
return toast(
|
||||
"An error occurred.",
|
||||
level="error",
|
||||
description="<br/>".join(error_message),
|
||||
position="top-center",
|
||||
id="backend_error",
|
||||
style={"width": "500px"},
|
||||
) # type: ignore
|
||||
else:
|
||||
error_message.insert(0, "An error occurred.")
|
||||
return window_alert("\n".join(error_message))
|
||||
|
||||
|
||||
def default_overlay_component() -> Component:
|
||||
@ -183,7 +201,7 @@ class App(MiddlewareMixin, LifespanMixin, Base):
|
||||
|
||||
# A component that is present on every page (defaults to the Connection Error banner).
|
||||
overlay_component: Optional[Union[Component, ComponentCallable]] = (
|
||||
default_overlay_component
|
||||
default_overlay_component()
|
||||
)
|
||||
|
||||
# Error boundary component to wrap the app with.
|
||||
|
@ -24,6 +24,7 @@ from typing import (
|
||||
import reflex.state
|
||||
from reflex.base import Base
|
||||
from reflex.compiler.templates import STATEFUL_COMPONENT
|
||||
from reflex.components.core.breakpoints import Breakpoints
|
||||
from reflex.components.tags import Tag
|
||||
from reflex.constants import (
|
||||
Dirs,
|
||||
@ -466,6 +467,12 @@ class Component(BaseComponent, ABC):
|
||||
# Merge styles, the later ones overriding keys in the earlier ones.
|
||||
style = {k: v for style_dict in style for k, v in style_dict.items()}
|
||||
|
||||
if isinstance(style, Breakpoints):
|
||||
style = {
|
||||
# Assign the Breakpoints to the self-referential selector to avoid squashing down to a regular dict.
|
||||
"&": style,
|
||||
}
|
||||
|
||||
kwargs["style"] = Style(
|
||||
{
|
||||
**self.get_fields()["style"].default,
|
||||
|
@ -153,6 +153,20 @@ useEffect(() => {{
|
||||
hook,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props) -> Component:
|
||||
"""Create a connection toaster component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
**props: The properties of the component.
|
||||
|
||||
Returns:
|
||||
The connection toaster component.
|
||||
"""
|
||||
Toaster.is_used = True
|
||||
return super().create(*children, **props)
|
||||
|
||||
|
||||
class ConnectionBanner(Component):
|
||||
"""A connection banner component."""
|
||||
|
@ -187,7 +187,7 @@ class ConnectionToaster(Toaster):
|
||||
] = None,
|
||||
**props,
|
||||
) -> "ConnectionToaster":
|
||||
"""Create the component.
|
||||
"""Create a connection toaster component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
@ -211,10 +211,10 @@ class ConnectionToaster(Toaster):
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
**props: The properties of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
The connection toaster component.
|
||||
"""
|
||||
...
|
||||
|
||||
|
@ -10,6 +10,7 @@ _SUBMODULES: set[str] = {"elements"}
|
||||
_SUBMOD_ATTRS: dict[str, list[str]] = {
|
||||
f"elements.{k}": v for k, v in elements._MAPPING.items()
|
||||
}
|
||||
_PYRIGHT_IGNORE_IMPORTS = elements._PYRIGHT_IGNORE_IMPORTS
|
||||
|
||||
__getattr__, __dir__, __all__ = lazy_loader.attach(
|
||||
__name__,
|
||||
|
@ -3,6 +3,7 @@
|
||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||
# ------------------------------------------------------
|
||||
|
||||
from . import elements
|
||||
from .elements.forms import Button as Button
|
||||
from .elements.forms import Fieldset as Fieldset
|
||||
from .elements.forms import Form as Form
|
||||
@ -91,7 +92,7 @@ from .elements.media import Defs as Defs
|
||||
from .elements.media import Embed as Embed
|
||||
from .elements.media import Iframe as Iframe
|
||||
from .elements.media import Img as Img
|
||||
from .elements.media import Lineargradient as Lineargradient
|
||||
from .elements.media import LinearGradient as LinearGradient
|
||||
from .elements.media import Map as Map
|
||||
from .elements.media import Object as Object
|
||||
from .elements.media import Path as Path
|
||||
@ -104,19 +105,19 @@ from .elements.media import Track as Track
|
||||
from .elements.media import Video as Video
|
||||
from .elements.media import area as area
|
||||
from .elements.media import audio as audio
|
||||
from .elements.media import defs as defs
|
||||
from .elements.media import defs as defs # type: ignore
|
||||
from .elements.media import embed as embed
|
||||
from .elements.media import iframe as iframe
|
||||
from .elements.media import image as image
|
||||
from .elements.media import img as img
|
||||
from .elements.media import lineargradient as lineargradient
|
||||
from .elements.media import lineargradient as lineargradient # type: ignore
|
||||
from .elements.media import map as map
|
||||
from .elements.media import object as object
|
||||
from .elements.media import path as path
|
||||
from .elements.media import path as path # type: ignore
|
||||
from .elements.media import picture as picture
|
||||
from .elements.media import portal as portal
|
||||
from .elements.media import source as source
|
||||
from .elements.media import stop as stop
|
||||
from .elements.media import stop as stop # type: ignore
|
||||
from .elements.media import svg as svg
|
||||
from .elements.media import track as track
|
||||
from .elements.media import video as video
|
||||
@ -230,3 +231,5 @@ from .elements.typography import ol as ol
|
||||
from .elements.typography import p as p
|
||||
from .elements.typography import pre as pre
|
||||
from .elements.typography import ul as ul
|
||||
|
||||
_PYRIGHT_IGNORE_IMPORTS = elements._PYRIGHT_IGNORE_IMPORTS
|
||||
|
@ -67,6 +67,7 @@ _MAPPING = {
|
||||
"svg",
|
||||
"defs",
|
||||
"lineargradient",
|
||||
"LinearGradient",
|
||||
"stop",
|
||||
"path",
|
||||
],
|
||||
@ -129,12 +130,13 @@ _MAPPING = {
|
||||
}
|
||||
|
||||
|
||||
EXCLUDE = ["del_", "Del", "image"]
|
||||
EXCLUDE = ["del_", "Del", "image", "lineargradient", "LinearGradient"]
|
||||
for _, v in _MAPPING.items():
|
||||
v.extend([mod.capitalize() for mod in v if mod not in EXCLUDE])
|
||||
|
||||
_SUBMOD_ATTRS: dict[str, list[str]] = _MAPPING
|
||||
|
||||
_PYRIGHT_IGNORE_IMPORTS = ["stop", "lineargradient", "path", "defs"]
|
||||
__getattr__, __dir__, __all__ = lazy_loader.attach(
|
||||
__name__,
|
||||
submod_attrs=_SUBMOD_ATTRS,
|
||||
|
@ -91,7 +91,7 @@ from .media import Defs as Defs
|
||||
from .media import Embed as Embed
|
||||
from .media import Iframe as Iframe
|
||||
from .media import Img as Img
|
||||
from .media import Lineargradient as Lineargradient
|
||||
from .media import LinearGradient as LinearGradient
|
||||
from .media import Map as Map
|
||||
from .media import Object as Object
|
||||
from .media import Path as Path
|
||||
@ -104,19 +104,19 @@ from .media import Track as Track
|
||||
from .media import Video as Video
|
||||
from .media import area as area
|
||||
from .media import audio as audio
|
||||
from .media import defs as defs
|
||||
from .media import defs as defs # type: ignore
|
||||
from .media import embed as embed
|
||||
from .media import iframe as iframe
|
||||
from .media import image as image
|
||||
from .media import img as img
|
||||
from .media import lineargradient as lineargradient
|
||||
from .media import lineargradient as lineargradient # type: ignore
|
||||
from .media import map as map
|
||||
from .media import object as object
|
||||
from .media import path as path
|
||||
from .media import path as path # type: ignore
|
||||
from .media import picture as picture
|
||||
from .media import portal as portal
|
||||
from .media import source as source
|
||||
from .media import stop as stop
|
||||
from .media import stop as stop # type: ignore
|
||||
from .media import svg as svg
|
||||
from .media import track as track
|
||||
from .media import video as video
|
||||
@ -294,6 +294,7 @@ _MAPPING = {
|
||||
"svg",
|
||||
"defs",
|
||||
"lineargradient",
|
||||
"LinearGradient",
|
||||
"stop",
|
||||
"path",
|
||||
],
|
||||
@ -347,6 +348,7 @@ _MAPPING = {
|
||||
"Del",
|
||||
],
|
||||
}
|
||||
EXCLUDE = ["del_", "Del", "image"]
|
||||
EXCLUDE = ["del_", "Del", "image", "lineargradient", "LinearGradient"]
|
||||
for _, v in _MAPPING.items():
|
||||
v.extend([mod.capitalize() for mod in v if mod not in EXCLUDE])
|
||||
_PYRIGHT_IGNORE_IMPORTS = ["stop", "lineargradient", "path", "defs"]
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from reflex import Component
|
||||
from reflex import Component, ComponentNamespace
|
||||
from reflex.constants.colors import Color
|
||||
from reflex.utils import console
|
||||
from reflex.vars import Var as Var
|
||||
|
||||
from .base import BaseHTML
|
||||
@ -309,6 +310,56 @@ class Svg(BaseHTML):
|
||||
"""Display the svg element."""
|
||||
|
||||
tag = "svg"
|
||||
# The width of the svg.
|
||||
width: Var[Union[str, int]]
|
||||
# The height of the svg.
|
||||
height: Var[Union[str, int]]
|
||||
# The XML namespace declaration.
|
||||
xmlns: Var[str]
|
||||
|
||||
|
||||
class Circle(BaseHTML):
|
||||
"""The SVG circle component."""
|
||||
|
||||
tag = "circle"
|
||||
# The x-axis coordinate of the center of the circle.
|
||||
cx: Var[Union[str, int]]
|
||||
# The y-axis coordinate of the center of the circle.
|
||||
cy: Var[Union[str, int]]
|
||||
# The radius of the circle.
|
||||
r: Var[Union[str, int]]
|
||||
# The total length for the circle's circumference, in user units.
|
||||
path_length: Var[int]
|
||||
|
||||
|
||||
class Rect(BaseHTML):
|
||||
"""The SVG rect component."""
|
||||
|
||||
tag = "rect"
|
||||
# The x coordinate of the rect.
|
||||
x: Var[Union[str, int]]
|
||||
# The y coordinate of the rect.
|
||||
y: Var[Union[str, int]]
|
||||
# The width of the rect
|
||||
width: Var[Union[str, int]]
|
||||
# The height of the rect.
|
||||
height: Var[Union[str, int]]
|
||||
# The horizontal corner radius of the rect. Defaults to ry if it is specified.
|
||||
rx: Var[Union[str, int]]
|
||||
# The vertical corner radius of the rect. Defaults to rx if it is specified.
|
||||
ry: Var[Union[str, int]]
|
||||
# The total length of the rectangle's perimeter, in user units.
|
||||
path_length: Var[int]
|
||||
|
||||
|
||||
class Polygon(BaseHTML):
|
||||
"""The SVG polygon component."""
|
||||
|
||||
tag = "polygon"
|
||||
# defines the list of points (pairs of x,y absolute coordinates) required to draw the polygon.
|
||||
points: Var[str]
|
||||
# This prop lets specify the total length for the path, in user units.
|
||||
path_length: Var[int]
|
||||
|
||||
|
||||
class Defs(BaseHTML):
|
||||
@ -317,30 +368,30 @@ class Defs(BaseHTML):
|
||||
tag = "defs"
|
||||
|
||||
|
||||
class Lineargradient(BaseHTML):
|
||||
class LinearGradient(BaseHTML):
|
||||
"""Display the linearGradient element."""
|
||||
|
||||
tag = "linearGradient"
|
||||
|
||||
# Units for the gradient
|
||||
# Units for the gradient.
|
||||
gradient_units: Var[Union[str, bool]]
|
||||
|
||||
# Transform applied to the gradient
|
||||
# Transform applied to the gradient.
|
||||
gradient_transform: Var[Union[str, bool]]
|
||||
|
||||
# Method used to spread the gradient
|
||||
# Method used to spread the gradient.
|
||||
spread_method: Var[Union[str, bool]]
|
||||
|
||||
# X coordinate of the starting point of the gradient
|
||||
# X coordinate of the starting point of the gradient.
|
||||
x1: Var[Union[str, int, bool]]
|
||||
|
||||
# X coordinate of the ending point of the gradient
|
||||
# X coordinate of the ending point of the gradient.
|
||||
x2: Var[Union[str, int, bool]]
|
||||
|
||||
# Y coordinate of the starting point of the gradient
|
||||
# Y coordinate of the starting point of the gradient.
|
||||
y1: Var[Union[str, int, bool]]
|
||||
|
||||
# Y coordinate of the ending point of the gradient
|
||||
# Y coordinate of the ending point of the gradient.
|
||||
y2: Var[Union[str, int, bool]]
|
||||
|
||||
|
||||
@ -349,13 +400,13 @@ class Stop(BaseHTML):
|
||||
|
||||
tag = "stop"
|
||||
|
||||
# Offset of the gradient stop
|
||||
# Offset of the gradient stop.
|
||||
offset: Var[Union[str, float, int]]
|
||||
|
||||
# Color of the gradient stop
|
||||
# Color of the gradient stop.
|
||||
stop_color: Var[Union[str, Color, bool]]
|
||||
|
||||
# Opacity of the gradient stop
|
||||
# Opacity of the gradient stop.
|
||||
stop_opacity: Var[Union[str, float, int, bool]]
|
||||
|
||||
|
||||
@ -364,10 +415,23 @@ class Path(BaseHTML):
|
||||
|
||||
tag = "path"
|
||||
|
||||
# Defines the shape of the path
|
||||
# Defines the shape of the path.
|
||||
d: Var[Union[str, int, bool]]
|
||||
|
||||
|
||||
class SVG(ComponentNamespace):
|
||||
"""SVG component namespace."""
|
||||
|
||||
circle = staticmethod(Circle.create)
|
||||
rect = staticmethod(Rect.create)
|
||||
polygon = staticmethod(Polygon.create)
|
||||
path = staticmethod(Path.create)
|
||||
stop = staticmethod(Stop.create)
|
||||
linear_gradient = staticmethod(LinearGradient.create)
|
||||
defs = staticmethod(Defs.create)
|
||||
__call__ = staticmethod(Svg.create)
|
||||
|
||||
|
||||
area = Area.create
|
||||
audio = Audio.create
|
||||
image = img = Img.create
|
||||
@ -380,8 +444,24 @@ object = Object.create
|
||||
picture = Picture.create
|
||||
portal = Portal.create
|
||||
source = Source.create
|
||||
svg = Svg.create
|
||||
defs = Defs.create
|
||||
lineargradient = Lineargradient.create
|
||||
stop = Stop.create
|
||||
path = Path.create
|
||||
svg = SVG()
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
if name in ("defs", "lineargradient", "stop", "path"):
|
||||
console.deprecate(
|
||||
f"`rx.el.{name}`",
|
||||
reason=f"use `rx.el.svg.{'linear_gradient' if name =='lineargradient' else name}`",
|
||||
deprecation_version="0.5.8",
|
||||
removal_version="0.6.0",
|
||||
)
|
||||
return (
|
||||
LinearGradient.create
|
||||
if name == "lineargradient"
|
||||
else globals()[name.capitalize()].create
|
||||
)
|
||||
|
||||
try:
|
||||
return globals()[name]
|
||||
except KeyError:
|
||||
raise AttributeError(f"module '{__name__} has no attribute '{name}'") from None
|
||||
|
@ -5,6 +5,7 @@
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Callable, Dict, Optional, Union, overload
|
||||
|
||||
from reflex import ComponentNamespace
|
||||
from reflex.constants.colors import Color
|
||||
from reflex.event import EventHandler, EventSpec
|
||||
from reflex.style import Style
|
||||
@ -1563,6 +1564,9 @@ class Svg(BaseHTML):
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
width: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
height: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
xmlns: Optional[Union[Var[str], str]] = None,
|
||||
access_key: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
auto_capitalize: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
@ -1644,6 +1648,383 @@ class Svg(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
width: The width of the svg.
|
||||
height: The height of the svg.
|
||||
xmlns: The XML namespace declaration.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left)
|
||||
draggable: Defines whether the element can be dragged.
|
||||
enter_key_hint: Hints what media types the media element is able to play.
|
||||
hidden: Defines whether the element is hidden.
|
||||
input_mode: Defines the type of the element.
|
||||
item_prop: Defines the name of the element for metadata purposes.
|
||||
lang: Defines the language used in the element.
|
||||
role: Defines the role of the element.
|
||||
slot: Assigns a slot in a shadow DOM shadow tree to an element.
|
||||
spell_check: Defines whether the element may be checked for spelling errors.
|
||||
tab_index: Defines the position of the current element in the tabbing order.
|
||||
title: Defines a tooltip for the element.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
"""
|
||||
...
|
||||
|
||||
class Circle(BaseHTML):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
cx: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
cy: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
r: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
path_length: Optional[Union[Var[int], int]] = None,
|
||||
access_key: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
auto_capitalize: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
content_editable: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
context_menu: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
dir: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
draggable: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
enter_key_hint: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
hidden: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
input_mode: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
item_prop: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
lang: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
role: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
slot: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
spell_check: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
tab_index: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
title: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_context_menu: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_double_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_focus: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_down: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_enter: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_leave: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_move: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_out: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_over: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_up: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_scroll: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_unmount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
**props,
|
||||
) -> "Circle":
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
cx: The x-axis coordinate of the center of the circle.
|
||||
cy: The y-axis coordinate of the center of the circle.
|
||||
r: The radius of the circle.
|
||||
path_length: The total length for the circle's circumference, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left)
|
||||
draggable: Defines whether the element can be dragged.
|
||||
enter_key_hint: Hints what media types the media element is able to play.
|
||||
hidden: Defines whether the element is hidden.
|
||||
input_mode: Defines the type of the element.
|
||||
item_prop: Defines the name of the element for metadata purposes.
|
||||
lang: Defines the language used in the element.
|
||||
role: Defines the role of the element.
|
||||
slot: Assigns a slot in a shadow DOM shadow tree to an element.
|
||||
spell_check: Defines whether the element may be checked for spelling errors.
|
||||
tab_index: Defines the position of the current element in the tabbing order.
|
||||
title: Defines a tooltip for the element.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
"""
|
||||
...
|
||||
|
||||
class Rect(BaseHTML):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
x: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
y: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
width: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
height: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
rx: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
ry: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
path_length: Optional[Union[Var[int], int]] = None,
|
||||
access_key: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
auto_capitalize: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
content_editable: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
context_menu: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
dir: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
draggable: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
enter_key_hint: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
hidden: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
input_mode: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
item_prop: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
lang: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
role: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
slot: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
spell_check: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
tab_index: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
title: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_context_menu: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_double_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_focus: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_down: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_enter: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_leave: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_move: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_out: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_over: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_up: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_scroll: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_unmount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
**props,
|
||||
) -> "Rect":
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
x: The x coordinate of the rect.
|
||||
y: The y coordinate of the rect.
|
||||
width: The width of the rect
|
||||
height: The height of the rect.
|
||||
rx: The horizontal corner radius of the rect. Defaults to ry if it is specified.
|
||||
ry: The vertical corner radius of the rect. Defaults to rx if it is specified.
|
||||
path_length: The total length of the rectangle's perimeter, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left)
|
||||
draggable: Defines whether the element can be dragged.
|
||||
enter_key_hint: Hints what media types the media element is able to play.
|
||||
hidden: Defines whether the element is hidden.
|
||||
input_mode: Defines the type of the element.
|
||||
item_prop: Defines the name of the element for metadata purposes.
|
||||
lang: Defines the language used in the element.
|
||||
role: Defines the role of the element.
|
||||
slot: Assigns a slot in a shadow DOM shadow tree to an element.
|
||||
spell_check: Defines whether the element may be checked for spelling errors.
|
||||
tab_index: Defines the position of the current element in the tabbing order.
|
||||
title: Defines a tooltip for the element.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
"""
|
||||
...
|
||||
|
||||
class Polygon(BaseHTML):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
points: Optional[Union[Var[str], str]] = None,
|
||||
path_length: Optional[Union[Var[int], int]] = None,
|
||||
access_key: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
auto_capitalize: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
content_editable: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
context_menu: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
dir: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
draggable: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
enter_key_hint: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
hidden: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
input_mode: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
item_prop: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
lang: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
role: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
slot: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
spell_check: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
tab_index: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
title: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_context_menu: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_double_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_focus: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_down: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_enter: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_leave: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_move: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_out: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_over: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_up: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_scroll: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_unmount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
**props,
|
||||
) -> "Polygon":
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
points: defines the list of points (pairs of x,y absolute coordinates) required to draw the polygon.
|
||||
path_length: This prop lets specify the total length for the path, in user units.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
@ -1789,7 +2170,7 @@ class Defs(BaseHTML):
|
||||
"""
|
||||
...
|
||||
|
||||
class Lineargradient(BaseHTML):
|
||||
class LinearGradient(BaseHTML):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
@ -1878,18 +2259,18 @@ class Lineargradient(BaseHTML):
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
**props,
|
||||
) -> "Lineargradient":
|
||||
) -> "LinearGradient":
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
gradient_units: Units for the gradient
|
||||
gradient_transform: Transform applied to the gradient
|
||||
spread_method: Method used to spread the gradient
|
||||
x1: X coordinate of the starting point of the gradient
|
||||
x2: X coordinate of the ending point of the gradient
|
||||
y1: Y coordinate of the starting point of the gradient
|
||||
y2: Y coordinate of the ending point of the gradient
|
||||
gradient_units: Units for the gradient.
|
||||
gradient_transform: Transform applied to the gradient.
|
||||
spread_method: Method used to spread the gradient.
|
||||
x1: X coordinate of the starting point of the gradient.
|
||||
x2: X coordinate of the ending point of the gradient.
|
||||
y1: Y coordinate of the starting point of the gradient.
|
||||
y2: Y coordinate of the ending point of the gradient.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
@ -2013,9 +2394,9 @@ class Stop(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
offset: Offset of the gradient stop
|
||||
stop_color: Color of the gradient stop
|
||||
stop_opacity: Opacity of the gradient stop
|
||||
offset: Offset of the gradient stop.
|
||||
stop_color: Color of the gradient stop.
|
||||
stop_opacity: Opacity of the gradient stop.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
@ -2133,7 +2514,135 @@ class Path(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
d: Defines the shape of the path
|
||||
d: Defines the shape of the path.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
context_menu: Defines the ID of a <menu> element which will serve as the element's context menu.
|
||||
dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left)
|
||||
draggable: Defines whether the element can be dragged.
|
||||
enter_key_hint: Hints what media types the media element is able to play.
|
||||
hidden: Defines whether the element is hidden.
|
||||
input_mode: Defines the type of the element.
|
||||
item_prop: Defines the name of the element for metadata purposes.
|
||||
lang: Defines the language used in the element.
|
||||
role: Defines the role of the element.
|
||||
slot: Assigns a slot in a shadow DOM shadow tree to an element.
|
||||
spell_check: Defines whether the element may be checked for spelling errors.
|
||||
tab_index: Defines the position of the current element in the tabbing order.
|
||||
title: Defines a tooltip for the element.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
"""
|
||||
...
|
||||
|
||||
class SVG(ComponentNamespace):
|
||||
circle = staticmethod(Circle.create)
|
||||
rect = staticmethod(Rect.create)
|
||||
polygon = staticmethod(Polygon.create)
|
||||
path = staticmethod(Path.create)
|
||||
stop = staticmethod(Stop.create)
|
||||
linear_gradient = staticmethod(LinearGradient.create)
|
||||
defs = staticmethod(Defs.create)
|
||||
|
||||
@staticmethod
|
||||
def __call__(
|
||||
*children,
|
||||
width: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
height: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
xmlns: Optional[Union[Var[str], str]] = None,
|
||||
access_key: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
auto_capitalize: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
content_editable: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
context_menu: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
dir: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
draggable: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
enter_key_hint: Optional[
|
||||
Union[Var[Union[bool, int, str]], str, int, bool]
|
||||
] = None,
|
||||
hidden: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
input_mode: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
item_prop: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
lang: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
role: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
slot: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
spell_check: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
tab_index: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
title: Optional[Union[Var[Union[bool, int, str]], str, int, bool]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
id: Optional[Any] = None,
|
||||
class_name: Optional[Any] = None,
|
||||
autofocus: Optional[bool] = None,
|
||||
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
|
||||
on_blur: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_context_menu: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_double_click: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_focus: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_down: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_enter: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_leave: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_move: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_out: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_over: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_mouse_up: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_scroll: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
on_unmount: Optional[
|
||||
Union[EventHandler, EventSpec, list, Callable, BaseVar]
|
||||
] = None,
|
||||
**props,
|
||||
) -> "Svg":
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
width: The width of the svg.
|
||||
height: The height of the svg.
|
||||
xmlns: The XML namespace declaration.
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
@ -2175,8 +2684,4 @@ object = Object.create
|
||||
picture = Picture.create
|
||||
portal = Portal.create
|
||||
source = Source.create
|
||||
svg = Svg.create
|
||||
defs = Defs.create
|
||||
lineargradient = Lineargradient.create
|
||||
stop = Stop.create
|
||||
path = Path.create
|
||||
svg = SVG()
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""
|
||||
|
||||
from typing import Union
|
||||
from typing import Set, Union
|
||||
|
||||
from reflex.components.el.element import Element
|
||||
from reflex.vars import Var as Var
|
||||
@ -29,24 +29,49 @@ class Link(BaseHTML): # noqa: E742
|
||||
|
||||
tag = "link"
|
||||
|
||||
# Specifies the CORS settings for the linked resource
|
||||
cross_origin: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies the URL of the linked document/resource
|
||||
href: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies the language of the text in the linked document
|
||||
href_lang: Var[Union[str, int, bool]]
|
||||
|
||||
# Allows a browser to check the fetched link for integrity
|
||||
integrity: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies on what device the linked document will be displayed
|
||||
media: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies the referrer policy of the linked document
|
||||
referrer_policy: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies the relationship between the current document and the linked one
|
||||
rel: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies the sizes of icons for visual media
|
||||
sizes: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies the MIME type of the linked document
|
||||
type: Var[Union[str, int, bool]]
|
||||
|
||||
|
||||
class Meta(BaseHTML): # Inherits common attributes from BaseHTML
|
||||
"""Display the meta element."""
|
||||
|
||||
tag = "meta"
|
||||
tag = "meta" # The HTML tag for this element is <meta>
|
||||
|
||||
# Specifies the character encoding for the HTML document
|
||||
char_set: Var[Union[str, int, bool]]
|
||||
|
||||
# Defines the content of the metadata
|
||||
content: Var[Union[str, int, bool]]
|
||||
|
||||
# Provides an HTTP header for the information/value of the content attribute
|
||||
http_equiv: Var[Union[str, int, bool]]
|
||||
|
||||
# Specifies a name for the metadata
|
||||
name: Var[Union[str, int, bool]]
|
||||
|
||||
|
||||
@ -64,6 +89,10 @@ class StyleEl(Element): # noqa: E742
|
||||
|
||||
media: Var[Union[str, int, bool]]
|
||||
|
||||
special_props: Set[Var] = {
|
||||
Var.create_safe("suppressHydrationWarning", _var_is_string=False)
|
||||
}
|
||||
|
||||
|
||||
base = Base.create
|
||||
head = Head.create
|
||||
|
@ -346,6 +346,15 @@ class Link(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
cross_origin: Specifies the CORS settings for the linked resource
|
||||
href: Specifies the URL of the linked document/resource
|
||||
href_lang: Specifies the language of the text in the linked document
|
||||
integrity: Allows a browser to check the fetched link for integrity
|
||||
media: Specifies on what device the linked document will be displayed
|
||||
referrer_policy: Specifies the referrer policy of the linked document
|
||||
rel: Specifies the relationship between the current document and the linked one
|
||||
sizes: Specifies the sizes of icons for visual media
|
||||
type: Specifies the MIME type of the linked document
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
@ -466,6 +475,10 @@ class Meta(BaseHTML):
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
char_set: Specifies the character encoding for the HTML document
|
||||
content: Defines the content of the metadata
|
||||
http_equiv: Provides an HTTP header for the information/value of the content attribute
|
||||
name: Specifies a name for the metadata
|
||||
access_key: Provides a hint for generating a keyboard shortcut for the current element.
|
||||
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
|
||||
content_editable: Indicates whether the element's content is editable.
|
||||
|
@ -26,7 +26,7 @@ class RadixPrimitiveComponentWithClassName(RadixPrimitiveComponent):
|
||||
._render()
|
||||
.add_props(
|
||||
**{
|
||||
"class_name": format.to_title_case(self.tag or ""),
|
||||
"class_name": f"{format.to_title_case(self.tag or '')} {self.class_name or ''}",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -70,7 +70,6 @@ class BaseList(Component):
|
||||
children = [Foreach.create(items, ListItem.create)]
|
||||
else:
|
||||
children = [ListItem.create(item) for item in items] # type: ignore
|
||||
props["list_style_position"] = "outside"
|
||||
props["direction"] = "column"
|
||||
style = props.setdefault("style", {})
|
||||
style["list_style_type"] = list_style_type
|
||||
@ -86,7 +85,6 @@ class BaseList(Component):
|
||||
"""
|
||||
return {
|
||||
"direction": "column",
|
||||
"list_style_position": "inside",
|
||||
}
|
||||
|
||||
|
||||
|
@ -138,6 +138,9 @@ class XAxis(Axis):
|
||||
# Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden
|
||||
include_hidden: Var[bool] = Var.create_safe(False)
|
||||
|
||||
# The range of the axis. Work best in conjuction with allow_data_overflow.
|
||||
domain: Var[List]
|
||||
|
||||
|
||||
class YAxis(Axis):
|
||||
"""A YAxis component in Recharts."""
|
||||
@ -299,7 +302,7 @@ class Area(Cartesian):
|
||||
fill: Var[Union[str, Color]] = Var.create_safe(Color("accent", 5))
|
||||
|
||||
# The interpolation type of area. And customized interpolation function can be set to type. 'basis' | 'basisClosed' | 'basisOpen' | 'bumpX' | 'bumpY' | 'bump' | 'linear' | 'linearClosed' | 'natural' | 'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter' |
|
||||
type_: Var[LiteralAreaType] = Var.create_safe("monotone")
|
||||
type_: Var[LiteralAreaType] = Var.create_safe("monotone", _var_is_string=True)
|
||||
|
||||
# If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally.
|
||||
dot: Var[Union[bool, Dict[str, Any]]]
|
||||
@ -406,7 +409,7 @@ class Line(Cartesian):
|
||||
stroke: Var[Union[str, Color]] = Var.create_safe(Color("accent", 9))
|
||||
|
||||
# The width of the line stroke.
|
||||
stoke_width: Var[int]
|
||||
stroke_width: Var[int]
|
||||
|
||||
# The dot is shown when mouse enter a line chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
|
||||
dot: Var[Union[bool, Dict[str, Any]]] = Var.create_safe(
|
||||
|
@ -192,6 +192,7 @@ class XAxis(Axis):
|
||||
] = None,
|
||||
x_axis_id: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
include_hidden: Optional[Union[Var[bool], bool]] = None,
|
||||
domain: Optional[Union[Var[List], List]] = None,
|
||||
data_key: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
hide: Optional[Union[Var[bool], bool]] = None,
|
||||
width: Optional[Union[Var[Union[int, str]], str, int]] = None,
|
||||
@ -320,6 +321,7 @@ class XAxis(Axis):
|
||||
orientation: The orientation of axis 'top' | 'bottom'
|
||||
x_axis_id: The id of x-axis which is corresponding to the data.
|
||||
include_hidden: Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden
|
||||
domain: The range of the axis. Work best in conjuction with allow_data_overflow.
|
||||
data_key: The key of data displayed in the axis.
|
||||
hide: If set true, the axis do not display in the chart.
|
||||
width: The width of axis which is usually calculated internally.
|
||||
@ -1232,7 +1234,7 @@ class Line(Cartesian):
|
||||
]
|
||||
] = None,
|
||||
stroke: Optional[Union[Var[Union[Color, str]], str, Color]] = None,
|
||||
stoke_width: Optional[Union[Var[int], int]] = None,
|
||||
stroke_width: Optional[Union[Var[int], int]] = None,
|
||||
dot: Optional[
|
||||
Union[Var[Union[Dict[str, Any], bool]], bool, Dict[str, Any]]
|
||||
] = None,
|
||||
@ -1344,7 +1346,7 @@ class Line(Cartesian):
|
||||
*children: The children of the component.
|
||||
type_: The interpolation type of line. And customized interpolation function can be set to type. It's the same as type in Area.
|
||||
stroke: The color of the line stroke.
|
||||
stoke_width: The width of the line stroke.
|
||||
stroke_width: The width of the line stroke.
|
||||
dot: The dot is shown when mouse enter a line chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
|
||||
active_dot: The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
|
||||
label: If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally.
|
||||
|
@ -234,7 +234,7 @@ class LabelList(Recharts):
|
||||
fill: Var[Union[str, Color]] = Var.create_safe(Color("gray", 10))
|
||||
|
||||
# The stroke color of each label
|
||||
stroke: Var[Union[str, Color]] = Var.create_safe("none")
|
||||
stroke: Var[Union[str, Color]] = Var.create_safe("none", _var_is_string=True)
|
||||
|
||||
|
||||
responsive_container = ResponsiveContainer.create
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from typing import Any, ClassVar, Literal, Optional, Union
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
@ -27,7 +29,6 @@ LiteralPosition = Literal[
|
||||
"bottom-right",
|
||||
]
|
||||
|
||||
|
||||
toast_ref = Var.create_safe("refs['__toast']", _var_is_string=False)
|
||||
|
||||
|
||||
@ -128,6 +129,24 @@ class ToastProps(PropsBase):
|
||||
# Function that gets called when the toast disappears automatically after it's timeout (duration` prop).
|
||||
on_auto_close: Optional[Any]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize the props.
|
||||
|
||||
Args:
|
||||
kwargs: Kwargs to initialize the props.
|
||||
|
||||
Raises:
|
||||
ValueError: If invalid props are passed on instantiation.
|
||||
"""
|
||||
try:
|
||||
super().__init__(**kwargs)
|
||||
except ValidationError as e:
|
||||
invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # type: ignore
|
||||
supported_props_str = ", ".join(f'"{field}"' for field in self.get_fields())
|
||||
raise ValueError(
|
||||
f"Invalid prop(s) {invalid_fields} for rx.toast. Supported props are {supported_props_str}"
|
||||
) from None
|
||||
|
||||
def dict(self, *args, **kwargs) -> dict[str, Any]:
|
||||
"""Convert the object to a dictionary.
|
||||
|
||||
@ -159,6 +178,13 @@ class ToastProps(PropsBase):
|
||||
)
|
||||
return d
|
||||
|
||||
class Config:
|
||||
"""Pydantic config."""
|
||||
|
||||
arbitrary_types_allowed = True
|
||||
use_enum_values = True
|
||||
extra = "forbid"
|
||||
|
||||
|
||||
class Toaster(Component):
|
||||
"""A Toaster Component for displaying toast notifications."""
|
||||
@ -211,6 +237,9 @@ class Toaster(Component):
|
||||
# Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked.
|
||||
pause_when_page_is_hidden: Var[bool]
|
||||
|
||||
# Marked True when any Toast component is created.
|
||||
is_used: ClassVar[bool] = False
|
||||
|
||||
def add_hooks(self) -> list[Var | str]:
|
||||
"""Add hooks for the toaster component.
|
||||
|
||||
@ -231,7 +260,7 @@ class Toaster(Component):
|
||||
return [hook]
|
||||
|
||||
@staticmethod
|
||||
def send_toast(message: str, level: str | None = None, **props) -> EventSpec:
|
||||
def send_toast(message: str = "", level: str | None = None, **props) -> EventSpec:
|
||||
"""Send a toast message.
|
||||
|
||||
Args:
|
||||
@ -239,10 +268,19 @@ class Toaster(Component):
|
||||
level: The level of the toast.
|
||||
**props: The options for the toast.
|
||||
|
||||
Raises:
|
||||
ValueError: If the Toaster component is not created.
|
||||
|
||||
Returns:
|
||||
The toast event.
|
||||
"""
|
||||
if not Toaster.is_used:
|
||||
raise ValueError(
|
||||
"Toaster component must be created before sending a toast. (use `rx.toast.provider()`)"
|
||||
)
|
||||
toast_command = f"{toast_ref}.{level}" if level is not None else toast_ref
|
||||
if message == "" and ("title" not in props or "description" not in props):
|
||||
raise ValueError("Toast message or title or description must be provided.")
|
||||
if props:
|
||||
args = serialize(ToastProps(**props)) # type: ignore
|
||||
toast = f"{toast_command}(`{message}`, {args})"
|
||||
@ -331,6 +369,20 @@ class Toaster(Component):
|
||||
)
|
||||
return call_script(dismiss_action)
|
||||
|
||||
@classmethod
|
||||
def create(cls, *children, **props) -> Component:
|
||||
"""Create a toaster component.
|
||||
|
||||
Args:
|
||||
*children: The children of the toaster.
|
||||
**props: The properties of the toaster.
|
||||
|
||||
Returns:
|
||||
The toaster component.
|
||||
"""
|
||||
cls.is_used = True
|
||||
return super().create(*children, **props)
|
||||
|
||||
|
||||
# TODO: figure out why loading toast stay open forever
|
||||
# def toast_loading(message: str, **kwargs):
|
||||
|
@ -3,7 +3,7 @@
|
||||
# ------------------- DO NOT EDIT ----------------------
|
||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||
# ------------------------------------------------------
|
||||
from typing import Any, Callable, Dict, Literal, Optional, Union, overload
|
||||
from typing import Any, Callable, ClassVar, Dict, Literal, Optional, Union, overload
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
@ -51,10 +51,19 @@ class ToastProps(PropsBase):
|
||||
|
||||
def dict(self, *args, **kwargs) -> dict[str, Any]: ...
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
use_enum_values = True
|
||||
extra = "forbid"
|
||||
|
||||
class Toaster(Component):
|
||||
is_used: ClassVar[bool] = False
|
||||
|
||||
def add_hooks(self) -> list[Var | str]: ...
|
||||
@staticmethod
|
||||
def send_toast(message: str, level: str | None = None, **props) -> EventSpec: ...
|
||||
def send_toast(
|
||||
message: str = "", level: str | None = None, **props
|
||||
) -> EventSpec: ...
|
||||
@staticmethod
|
||||
def toast_info(message: str, **kwargs): ...
|
||||
@staticmethod
|
||||
@ -158,10 +167,10 @@ class Toaster(Component):
|
||||
] = None,
|
||||
**props,
|
||||
) -> "Toaster":
|
||||
"""Create the component.
|
||||
"""Create a toaster component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
*children: The children of the toaster.
|
||||
theme: the theme of the toast
|
||||
rich_colors: whether to show rich colors
|
||||
expand: whether to expand the toast
|
||||
@ -182,10 +191,10 @@ class Toaster(Component):
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
**props: The properties of the toaster.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
The toaster component.
|
||||
"""
|
||||
...
|
||||
|
||||
@ -200,7 +209,7 @@ class ToastNamespace(ComponentNamespace):
|
||||
|
||||
@staticmethod
|
||||
def __call__(
|
||||
message: str, level: Optional[str] = None, **props
|
||||
message: str = "", level: Optional[str] = None, **props
|
||||
) -> "Optional[EventSpec]":
|
||||
"""Send a toast message.
|
||||
|
||||
@ -209,6 +218,9 @@ class ToastNamespace(ComponentNamespace):
|
||||
level: The level of the toast.
|
||||
**props: The options for the toast.
|
||||
|
||||
Raises:
|
||||
ValueError: If the Toaster component is not created.
|
||||
|
||||
Returns:
|
||||
The toast event.
|
||||
"""
|
||||
|
@ -77,6 +77,8 @@ class Reflex(SimpleNamespace):
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)
|
||||
|
||||
RELEASES_URL = f"https://api.github.com/repos/reflex-dev/templates/releases"
|
||||
|
||||
|
||||
class ReflexHostingCLI(SimpleNamespace):
|
||||
"""Base constants concerning Reflex Hosting CLI."""
|
||||
@ -94,6 +96,27 @@ class Templates(SimpleNamespace):
|
||||
# The default template
|
||||
DEFAULT = "blank"
|
||||
|
||||
# The reflex.build frontend host
|
||||
REFLEX_BUILD_FRONTEND = os.environ.get(
|
||||
"REFLEX_BUILD_FRONTEND", "https://flexgen.reflex.run"
|
||||
)
|
||||
|
||||
# The reflex.build backend host
|
||||
REFLEX_BUILD_BACKEND = os.environ.get(
|
||||
"REFLEX_BUILD_BACKEND", "https://rxh-prod-flexgen.fly.dev"
|
||||
)
|
||||
|
||||
# The URL to redirect to reflex.build
|
||||
REFLEX_BUILD_URL = (
|
||||
REFLEX_BUILD_FRONTEND + "/gen?reflex_init_token={reflex_init_token}"
|
||||
)
|
||||
|
||||
# The URL to poll waiting for the user to select a generation.
|
||||
REFLEX_BUILD_POLL_URL = REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}"
|
||||
|
||||
# The URL to fetch the generation's reflex code
|
||||
REFLEX_BUILD_CODE_URL = REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}"
|
||||
|
||||
class Dirs(SimpleNamespace):
|
||||
"""Folders used by the template system of Reflex."""
|
||||
|
||||
|
@ -51,6 +51,8 @@ class Bun(SimpleNamespace):
|
||||
WINDOWS_INSTALL_URL = (
|
||||
"https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/install.ps1"
|
||||
)
|
||||
# Path of the bunfig file
|
||||
CONFIG_PATH = "bunfig.toml"
|
||||
|
||||
|
||||
# FNM config.
|
||||
|
@ -7,7 +7,7 @@ from reflex.vars import Var, VarData
|
||||
|
||||
|
||||
def _compose_react_imports(tags: list[str]) -> dict[str, list[ImportVar]]:
|
||||
return {"react": [ImportVar(tag=tag, install=False) for tag in tags]}
|
||||
return {"react": [ImportVar(tag=tag) for tag in tags]}
|
||||
|
||||
|
||||
def const(name, value) -> Var:
|
||||
|
@ -1,12 +1,20 @@
|
||||
"""Experimental Immutable-Based Var System."""
|
||||
|
||||
from .base import ArrayVar as ArrayVar
|
||||
from .base import BooleanVar as BooleanVar
|
||||
from .base import ConcatVarOperation as ConcatVarOperation
|
||||
from .base import FunctionVar as FunctionVar
|
||||
from .base import ImmutableVar as ImmutableVar
|
||||
from .base import LiteralStringVar as LiteralStringVar
|
||||
from .base import LiteralVar as LiteralVar
|
||||
from .base import NumberVar as NumberVar
|
||||
from .base import ObjectVar as ObjectVar
|
||||
from .base import StringVar as StringVar
|
||||
from .base import var_operation as var_operation
|
||||
from .function import FunctionStringVar as FunctionStringVar
|
||||
from .function import FunctionVar as FunctionVar
|
||||
from .function import VarOperationCall as VarOperationCall
|
||||
from .number import BooleanVar as BooleanVar
|
||||
from .number import LiteralBooleanVar as LiteralBooleanVar
|
||||
from .number import LiteralNumberVar as LiteralNumberVar
|
||||
from .number import NumberVar as NumberVar
|
||||
from .object import LiteralObjectVar as LiteralObjectVar
|
||||
from .object import ObjectVar as ObjectVar
|
||||
from .sequence import ArrayJoinOperation as ArrayJoinOperation
|
||||
from .sequence import ArrayVar as ArrayVar
|
||||
from .sequence import ConcatVarOperation as ConcatVarOperation
|
||||
from .sequence import LiteralArrayVar as LiteralArrayVar
|
||||
from .sequence import LiteralStringVar as LiteralStringVar
|
||||
from .sequence import StringVar as StringVar
|
||||
|
@ -3,14 +3,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import re
|
||||
import functools
|
||||
import inspect
|
||||
import sys
|
||||
from functools import cached_property
|
||||
from typing import Any, Optional, Type
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from typing_extensions import ParamSpec, get_origin
|
||||
|
||||
from reflex import constants
|
||||
from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
|
||||
from reflex.base import Base
|
||||
from reflex.utils import serializers, types
|
||||
from reflex.utils.exceptions import VarTypeError
|
||||
from reflex.vars import (
|
||||
@ -22,20 +37,33 @@ from reflex.vars import (
|
||||
_global_vars,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .function import FunctionVar, ToFunctionOperation
|
||||
from .number import (
|
||||
BooleanVar,
|
||||
NumberVar,
|
||||
ToBooleanVarOperation,
|
||||
ToNumberVarOperation,
|
||||
)
|
||||
from .object import ObjectVar, ToObjectOperation
|
||||
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
|
||||
|
||||
VAR_TYPE = TypeVar("VAR_TYPE")
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ImmutableVar(Var):
|
||||
class ImmutableVar(Var, Generic[VAR_TYPE]):
|
||||
"""Base class for immutable vars."""
|
||||
|
||||
# The name of the var.
|
||||
_var_name: str = dataclasses.field()
|
||||
|
||||
# The type of the var.
|
||||
_var_type: Type = dataclasses.field(default=Any)
|
||||
_var_type: types.GenericType = dataclasses.field(default=Any)
|
||||
|
||||
# Extra metadata associated with the Var
|
||||
_var_data: Optional[ImmutableVarData] = dataclasses.field(default=None)
|
||||
@ -79,11 +107,12 @@ class ImmutableVar(Var):
|
||||
"""Post-initialize the var."""
|
||||
# Decode any inline Var markup and apply it to the instance
|
||||
_var_data, _var_name = _decode_var_immutable(self._var_name)
|
||||
if _var_data:
|
||||
|
||||
if _var_data or _var_name != self._var_name:
|
||||
self.__init__(
|
||||
_var_name,
|
||||
self._var_type,
|
||||
ImmutableVarData.merge(self._var_data, _var_data),
|
||||
_var_name=_var_name,
|
||||
_var_type=self._var_type,
|
||||
_var_data=ImmutableVarData.merge(self._var_data, _var_data),
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
@ -95,6 +124,11 @@ class ImmutableVar(Var):
|
||||
return hash((self._var_name, self._var_type, self._var_data))
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the Var.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._var_data
|
||||
|
||||
def _replace(self, merge_var_data=None, **kwargs: Any):
|
||||
@ -249,64 +283,154 @@ class ImmutableVar(Var):
|
||||
_global_vars[hashed_var] = self
|
||||
|
||||
# Encode the _var_data into the formatted output for tracking purposes.
|
||||
return f"{REFLEX_VAR_OPENING_TAG}{hashed_var}{REFLEX_VAR_CLOSING_TAG}{self._var_name}"
|
||||
return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._var_name}"
|
||||
|
||||
@overload
|
||||
def to(
|
||||
self, output: Type[NumberVar], var_type: type[int] | type[float] = float
|
||||
) -> ToNumberVarOperation: ...
|
||||
|
||||
@overload
|
||||
def to(self, output: Type[BooleanVar]) -> ToBooleanVarOperation: ...
|
||||
|
||||
@overload
|
||||
def to(
|
||||
self,
|
||||
output: Type[ArrayVar],
|
||||
var_type: type[list] | type[tuple] | type[set] = list,
|
||||
) -> ToArrayOperation: ...
|
||||
|
||||
@overload
|
||||
def to(self, output: Type[StringVar]) -> ToStringOperation: ...
|
||||
|
||||
@overload
|
||||
def to(
|
||||
self, output: Type[ObjectVar], var_type: types.GenericType = dict
|
||||
) -> ToObjectOperation: ...
|
||||
|
||||
@overload
|
||||
def to(
|
||||
self, output: Type[FunctionVar], var_type: Type[Callable] = Callable
|
||||
) -> ToFunctionOperation: ...
|
||||
|
||||
@overload
|
||||
def to(
|
||||
self, output: Type[OUTPUT], var_type: types.GenericType | None = None
|
||||
) -> OUTPUT: ...
|
||||
|
||||
def to(
|
||||
self, output: Type[OUTPUT], var_type: types.GenericType | None = None
|
||||
) -> Var:
|
||||
"""Convert the var to a different type.
|
||||
|
||||
Args:
|
||||
output: The output type.
|
||||
var_type: The type of the var.
|
||||
|
||||
Raises:
|
||||
TypeError: If the var_type is not a supported type for the output.
|
||||
|
||||
Returns:
|
||||
The converted var.
|
||||
"""
|
||||
from .number import (
|
||||
BooleanVar,
|
||||
NumberVar,
|
||||
ToBooleanVarOperation,
|
||||
ToNumberVarOperation,
|
||||
)
|
||||
|
||||
fixed_type = (
|
||||
var_type
|
||||
if var_type is None or inspect.isclass(var_type)
|
||||
else get_origin(var_type)
|
||||
)
|
||||
|
||||
if issubclass(output, NumberVar):
|
||||
if fixed_type is not None and not issubclass(fixed_type, (int, float)):
|
||||
raise TypeError(
|
||||
f"Unsupported type {var_type} for NumberVar. Must be int or float."
|
||||
)
|
||||
return ToNumberVarOperation(self, var_type or float)
|
||||
if issubclass(output, BooleanVar):
|
||||
return ToBooleanVarOperation(self)
|
||||
|
||||
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
|
||||
|
||||
if issubclass(output, ArrayVar):
|
||||
if fixed_type is not None and not issubclass(
|
||||
fixed_type, (list, tuple, set)
|
||||
):
|
||||
raise TypeError(
|
||||
f"Unsupported type {var_type} for ArrayVar. Must be list, tuple, or set."
|
||||
)
|
||||
return ToArrayOperation(self, var_type or list)
|
||||
if issubclass(output, StringVar):
|
||||
return ToStringOperation(self)
|
||||
|
||||
from .object import ObjectVar, ToObjectOperation
|
||||
|
||||
if issubclass(output, ObjectVar):
|
||||
return ToObjectOperation(self, var_type or dict)
|
||||
|
||||
from .function import FunctionVar, ToFunctionOperation
|
||||
|
||||
if issubclass(output, FunctionVar):
|
||||
if fixed_type is not None and not issubclass(fixed_type, Callable):
|
||||
raise TypeError(
|
||||
f"Unsupported type {var_type} for FunctionVar. Must be Callable."
|
||||
)
|
||||
return ToFunctionOperation(self, var_type or Callable)
|
||||
|
||||
return output(
|
||||
_var_name=self._var_name,
|
||||
_var_type=self._var_type if var_type is None else var_type,
|
||||
_var_data=self._var_data,
|
||||
)
|
||||
|
||||
def guess_type(self) -> ImmutableVar:
|
||||
"""Guess the type of the var.
|
||||
|
||||
Returns:
|
||||
The guessed type.
|
||||
"""
|
||||
from .number import NumberVar
|
||||
from .object import ObjectVar
|
||||
from .sequence import ArrayVar, StringVar
|
||||
|
||||
if self._var_type is Any:
|
||||
return self
|
||||
|
||||
var_type = self._var_type
|
||||
|
||||
fixed_type = var_type if inspect.isclass(var_type) else get_origin(var_type)
|
||||
|
||||
if issubclass(fixed_type, (int, float)):
|
||||
return self.to(NumberVar, var_type)
|
||||
if issubclass(fixed_type, dict):
|
||||
return self.to(ObjectVar, var_type)
|
||||
if issubclass(fixed_type, (list, tuple, set)):
|
||||
return self.to(ArrayVar, var_type)
|
||||
if issubclass(fixed_type, str):
|
||||
return self.to(StringVar)
|
||||
if issubclass(fixed_type, Base):
|
||||
return self.to(ObjectVar, var_type)
|
||||
return self
|
||||
|
||||
|
||||
class StringVar(ImmutableVar):
|
||||
"""Base class for immutable string vars."""
|
||||
|
||||
|
||||
class NumberVar(ImmutableVar):
|
||||
"""Base class for immutable number vars."""
|
||||
|
||||
|
||||
class BooleanVar(ImmutableVar):
|
||||
"""Base class for immutable boolean vars."""
|
||||
|
||||
|
||||
class ObjectVar(ImmutableVar):
|
||||
"""Base class for immutable object vars."""
|
||||
|
||||
|
||||
class ArrayVar(ImmutableVar):
|
||||
"""Base class for immutable array vars."""
|
||||
|
||||
|
||||
class FunctionVar(ImmutableVar):
|
||||
"""Base class for immutable function vars."""
|
||||
OUTPUT = TypeVar("OUTPUT", bound=ImmutableVar)
|
||||
|
||||
|
||||
class LiteralVar(ImmutableVar):
|
||||
"""Base class for immutable literal vars."""
|
||||
|
||||
def __post_init__(self):
|
||||
"""Post-initialize the var."""
|
||||
|
||||
|
||||
# Compile regex for finding reflex var tags.
|
||||
_decode_var_pattern_re = (
|
||||
rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
|
||||
)
|
||||
_decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralStringVar(LiteralVar):
|
||||
"""Base class for immutable literal string vars."""
|
||||
|
||||
_var_value: Optional[str] = dataclasses.field(default=None)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
value: str,
|
||||
value: Any,
|
||||
_var_data: VarData | None = None,
|
||||
) -> LiteralStringVar | ConcatVarOperation:
|
||||
"""Create a var from a string value.
|
||||
) -> Var:
|
||||
"""Create a var from a value.
|
||||
|
||||
Args:
|
||||
value: The value to create the var from.
|
||||
@ -314,141 +438,146 @@ class LiteralStringVar(LiteralVar):
|
||||
|
||||
Returns:
|
||||
The var.
|
||||
|
||||
Raises:
|
||||
TypeError: If the value is not a supported type for LiteralVar.
|
||||
"""
|
||||
if REFLEX_VAR_OPENING_TAG in value:
|
||||
strings_and_vals: list[Var] = []
|
||||
offset = 0
|
||||
if isinstance(value, Var):
|
||||
if _var_data is None:
|
||||
return value
|
||||
return value._replace(merge_var_data=_var_data)
|
||||
|
||||
# Initialize some methods for reading json.
|
||||
var_data_config = VarData().__config__
|
||||
if value is None:
|
||||
return ImmutableVar.create_safe("null", _var_data=_var_data)
|
||||
|
||||
def json_loads(s):
|
||||
try:
|
||||
return var_data_config.json_loads(s)
|
||||
except json.decoder.JSONDecodeError:
|
||||
return var_data_config.json_loads(
|
||||
var_data_config.json_loads(f'"{s}"')
|
||||
from .object import LiteralObjectVar
|
||||
|
||||
if isinstance(value, Base):
|
||||
return LiteralObjectVar(
|
||||
value.dict(), _var_type=type(value), _var_data=_var_data
|
||||
)
|
||||
|
||||
# Find all tags.
|
||||
while m := _decode_var_pattern.search(value):
|
||||
start, end = m.span()
|
||||
if start > 0:
|
||||
strings_and_vals.append(LiteralStringVar.create(value[:start]))
|
||||
from .number import LiteralBooleanVar, LiteralNumberVar
|
||||
from .sequence import LiteralArrayVar, LiteralStringVar
|
||||
|
||||
serialized_data = m.group(1)
|
||||
if isinstance(value, str):
|
||||
return LiteralStringVar.create(value, _var_data=_var_data)
|
||||
|
||||
if serialized_data[1:].isnumeric():
|
||||
# This is a global immutable var.
|
||||
var = _global_vars[int(serialized_data)]
|
||||
strings_and_vals.append(var)
|
||||
value = value[(end + len(var._var_name)) :]
|
||||
else:
|
||||
data = json_loads(serialized_data)
|
||||
string_length = data.pop("string_length", None)
|
||||
var_data = VarData.parse_obj(data)
|
||||
type_mapping = {
|
||||
int: LiteralNumberVar,
|
||||
float: LiteralNumberVar,
|
||||
bool: LiteralBooleanVar,
|
||||
dict: LiteralObjectVar,
|
||||
list: LiteralArrayVar,
|
||||
tuple: LiteralArrayVar,
|
||||
set: LiteralArrayVar,
|
||||
}
|
||||
|
||||
# Use string length to compute positions of interpolations.
|
||||
if string_length is not None:
|
||||
realstart = start + offset
|
||||
var_data.interpolations = [
|
||||
(realstart, realstart + string_length)
|
||||
constructor = type_mapping.get(type(value))
|
||||
|
||||
if constructor is None:
|
||||
raise TypeError(f"Unsupported type {type(value)} for LiteralVar.")
|
||||
|
||||
return constructor(value, _var_data=_var_data)
|
||||
|
||||
def __post_init__(self):
|
||||
"""Post-initialize the var."""
|
||||
|
||||
def json(self) -> str:
|
||||
"""Serialize the var to a JSON string.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If the method is not implemented.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"LiteralVar subclasses must implement the json method."
|
||||
)
|
||||
|
||||
|
||||
P = ParamSpec("P")
|
||||
T = TypeVar("T", bound=ImmutableVar)
|
||||
|
||||
|
||||
def var_operation(*, output: Type[T]) -> Callable[[Callable[P, str]], Callable[P, T]]:
|
||||
"""Decorator for creating a var operation.
|
||||
|
||||
Example:
|
||||
```python
|
||||
@var_operation(output=NumberVar)
|
||||
def add(a: NumberVar, b: NumberVar):
|
||||
return f"({a} + {b})"
|
||||
```
|
||||
|
||||
Args:
|
||||
output: The output type of the operation.
|
||||
|
||||
Returns:
|
||||
The decorator.
|
||||
"""
|
||||
|
||||
def decorator(func: Callable[P, str], output=output):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
args_vars = [
|
||||
LiteralVar.create(arg) if not isinstance(arg, Var) else arg
|
||||
for arg in args
|
||||
]
|
||||
strings_and_vals.append(
|
||||
ImmutableVar.create_safe(
|
||||
value[end : (end + string_length)], _var_data=var_data
|
||||
)
|
||||
)
|
||||
value = value[(end + string_length) :]
|
||||
|
||||
offset += end - start
|
||||
|
||||
if value:
|
||||
strings_and_vals.append(LiteralStringVar.create(value))
|
||||
|
||||
return ConcatVarOperation.create(
|
||||
tuple(strings_and_vals), _var_data=_var_data
|
||||
kwargs_vars = {
|
||||
key: LiteralVar.create(value) if not isinstance(value, Var) else value
|
||||
for key, value in kwargs.items()
|
||||
}
|
||||
return output(
|
||||
_var_name=func(*args_vars, **kwargs_vars), # type: ignore
|
||||
_var_data=VarData.merge(
|
||||
*[arg._get_all_var_data() for arg in args if isinstance(arg, Var)],
|
||||
*[
|
||||
arg._get_all_var_data()
|
||||
for arg in kwargs.values()
|
||||
if isinstance(arg, Var)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
return cls(
|
||||
_var_value=value,
|
||||
_var_name=f'"{value}"',
|
||||
_var_type=str,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ConcatVarOperation(StringVar):
|
||||
"""Representing a concatenation of literal string vars."""
|
||||
|
||||
_var_value: tuple[Var, ...] = dataclasses.field(default_factory=tuple)
|
||||
|
||||
def __init__(self, _var_value: tuple[Var, ...], _var_data: VarData | None = None):
|
||||
"""Initialize the operation of concatenating literal string vars.
|
||||
def unionize(*args: Type) -> Type:
|
||||
"""Unionize the types.
|
||||
|
||||
Args:
|
||||
_var_value: The list of vars to concatenate.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
"""
|
||||
super(ConcatVarOperation, self).__init__(
|
||||
_var_name="", _var_data=ImmutableVarData.merge(_var_data), _var_type=str
|
||||
)
|
||||
object.__setattr__(self, "_var_value", _var_value)
|
||||
object.__setattr__(self, "_var_name", self._cached_var_name)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
args: The types to unionize.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
The unionized types.
|
||||
"""
|
||||
return "+".join([str(element) for element in self._var_value])
|
||||
if not args:
|
||||
return Any
|
||||
first, *rest = args
|
||||
if not rest:
|
||||
return first
|
||||
return Union[first, unionize(*rest)]
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the Var.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
*[var._get_all_var_data() for var in self._var_value], self._var_data
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
||||
|
||||
def __post_init__(self):
|
||||
"""Post-initialize the var."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
value: tuple[Var, ...],
|
||||
_var_data: VarData | None = None,
|
||||
) -> ConcatVarOperation:
|
||||
"""Create a var from a tuple of values.
|
||||
def figure_out_type(value: Any) -> Type:
|
||||
"""Figure out the type of the value.
|
||||
|
||||
Args:
|
||||
value: The value to create the var from.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
value: The value to figure out the type of.
|
||||
|
||||
Returns:
|
||||
The var.
|
||||
The type of the value.
|
||||
"""
|
||||
return ConcatVarOperation(
|
||||
_var_value=value,
|
||||
_var_data=_var_data,
|
||||
)
|
||||
if isinstance(value, list):
|
||||
return List[unionize(*(figure_out_type(v) for v in value))]
|
||||
if isinstance(value, set):
|
||||
return Set[unionize(*(figure_out_type(v) for v in value))]
|
||||
if isinstance(value, tuple):
|
||||
return Tuple[unionize(*(figure_out_type(v) for v in value)), ...]
|
||||
if isinstance(value, dict):
|
||||
return Dict[
|
||||
unionize(*(figure_out_type(k) for k in value)),
|
||||
unionize(*(figure_out_type(v) for v in value.values())),
|
||||
]
|
||||
return type(value)
|
||||
|
290
reflex/experimental/vars/function.py
Normal file
290
reflex/experimental/vars/function.py
Normal file
@ -0,0 +1,290 @@
|
||||
"""Immutable function vars."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import sys
|
||||
from functools import cached_property
|
||||
from typing import Any, Callable, Optional, Tuple, Type, Union
|
||||
|
||||
from reflex.experimental.vars.base import ImmutableVar, LiteralVar
|
||||
from reflex.vars import ImmutableVarData, Var, VarData
|
||||
|
||||
|
||||
class FunctionVar(ImmutableVar[Callable]):
|
||||
"""Base class for immutable function vars."""
|
||||
|
||||
def __call__(self, *args: Var | Any) -> ArgsFunctionOperation:
|
||||
"""Call the function with the given arguments.
|
||||
|
||||
Args:
|
||||
*args: The arguments to call the function with.
|
||||
|
||||
Returns:
|
||||
The function call operation.
|
||||
"""
|
||||
return ArgsFunctionOperation(
|
||||
("...args",),
|
||||
VarOperationCall(self, *args, ImmutableVar.create_safe("...args")),
|
||||
)
|
||||
|
||||
def call(self, *args: Var | Any) -> VarOperationCall:
|
||||
"""Call the function with the given arguments.
|
||||
|
||||
Args:
|
||||
*args: The arguments to call the function with.
|
||||
|
||||
Returns:
|
||||
The function call operation.
|
||||
"""
|
||||
return VarOperationCall(self, *args)
|
||||
|
||||
|
||||
class FunctionStringVar(FunctionVar):
|
||||
"""Base class for immutable function vars from a string."""
|
||||
|
||||
def __init__(self, func: str, _var_data: VarData | None = None) -> None:
|
||||
"""Initialize the function var.
|
||||
|
||||
Args:
|
||||
func: The function to call.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
"""
|
||||
super(FunctionVar, self).__init__(
|
||||
_var_name=func,
|
||||
_var_type=Callable,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class VarOperationCall(ImmutableVar):
|
||||
"""Base class for immutable vars that are the result of a function call."""
|
||||
|
||||
_func: Optional[FunctionVar] = dataclasses.field(default=None)
|
||||
_args: Tuple[Union[Var, Any], ...] = dataclasses.field(default_factory=tuple)
|
||||
|
||||
def __init__(
|
||||
self, func: FunctionVar, *args: Var | Any, _var_data: VarData | None = None
|
||||
):
|
||||
"""Initialize the function call var.
|
||||
|
||||
Args:
|
||||
func: The function to call.
|
||||
*args: The arguments to call the function with.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
"""
|
||||
super(VarOperationCall, self).__init__(
|
||||
_var_name="",
|
||||
_var_type=Any,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(self, "_func", func)
|
||||
object.__setattr__(self, "_args", args)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the var.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the var.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
"""
|
||||
return f"({str(self._func)}({', '.join([str(LiteralVar.create(arg)) for arg in self._args])}))"
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the Var.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
self._func._get_all_var_data() if self._func is not None else None,
|
||||
*[var._get_all_var_data() for var in self._args],
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
||||
|
||||
def __post_init__(self):
|
||||
"""Post-initialize the var."""
|
||||
pass
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ArgsFunctionOperation(FunctionVar):
|
||||
"""Base class for immutable function defined via arguments and return expression."""
|
||||
|
||||
_args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
|
||||
_return_expr: Union[Var, Any] = dataclasses.field(default=None)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
args_names: Tuple[str, ...],
|
||||
return_expr: Var | Any,
|
||||
_var_data: VarData | None = None,
|
||||
) -> None:
|
||||
"""Initialize the function with arguments var.
|
||||
|
||||
Args:
|
||||
args_names: The names of the arguments.
|
||||
return_expr: The return expression of the function.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
"""
|
||||
super(ArgsFunctionOperation, self).__init__(
|
||||
_var_name=f"",
|
||||
_var_type=Callable,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(self, "_args_names", args_names)
|
||||
object.__setattr__(self, "_return_expr", return_expr)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the var.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the var.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
"""
|
||||
return f"(({', '.join(self._args_names)}) => ({str(LiteralVar.create(self._return_expr))}))"
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the Var.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
self._return_expr._get_all_var_data(),
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
||||
|
||||
def __post_init__(self):
|
||||
"""Post-initialize the var."""
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToFunctionOperation(FunctionVar):
|
||||
"""Base class of converting a var to a function."""
|
||||
|
||||
_original_var: Var = dataclasses.field(
|
||||
default_factory=lambda: LiteralVar.create(None)
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
original_var: Var,
|
||||
_var_type: Type[Callable] = Callable,
|
||||
_var_data: VarData | None = None,
|
||||
) -> None:
|
||||
"""Initialize the function with arguments var.
|
||||
|
||||
Args:
|
||||
original_var: The original var to convert to a function.
|
||||
_var_type: The type of the function.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
"""
|
||||
super(ToFunctionOperation, self).__init__(
|
||||
_var_name=f"",
|
||||
_var_type=_var_type,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(self, "_original_var", original_var)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the var.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the var.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
"""
|
||||
return str(self._original_var)
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the Var.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
self._original_var._get_all_var_data(),
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
1458
reflex/experimental/vars/number.py
Normal file
1458
reflex/experimental/vars/number.py
Normal file
File diff suppressed because it is too large
Load Diff
804
reflex/experimental/vars/object.py
Normal file
804
reflex/experimental/vars/object.py
Normal file
@ -0,0 +1,804 @@
|
||||
"""Classes for immutable object vars."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import sys
|
||||
import typing
|
||||
from functools import cached_property
|
||||
from inspect import isclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
NoReturn,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
get_args,
|
||||
overload,
|
||||
)
|
||||
|
||||
from typing_extensions import get_origin
|
||||
|
||||
from reflex.experimental.vars.base import (
|
||||
ImmutableVar,
|
||||
LiteralVar,
|
||||
figure_out_type,
|
||||
)
|
||||
from reflex.experimental.vars.number import NumberVar
|
||||
from reflex.experimental.vars.sequence import ArrayVar, StringVar
|
||||
from reflex.utils.exceptions import VarAttributeError
|
||||
from reflex.utils.types import GenericType, get_attribute_access_type
|
||||
from reflex.vars import ImmutableVarData, Var, VarData
|
||||
|
||||
OBJECT_TYPE = TypeVar("OBJECT_TYPE")
|
||||
|
||||
KEY_TYPE = TypeVar("KEY_TYPE")
|
||||
VALUE_TYPE = TypeVar("VALUE_TYPE")
|
||||
|
||||
ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE")
|
||||
|
||||
OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE")
|
||||
|
||||
|
||||
class ObjectVar(ImmutableVar[OBJECT_TYPE]):
|
||||
"""Base class for immutable object vars."""
|
||||
|
||||
@overload
|
||||
def _key_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> KEY_TYPE: ...
|
||||
|
||||
@overload
|
||||
def _key_type(self) -> Type: ...
|
||||
|
||||
def _key_type(self) -> Type:
|
||||
"""Get the type of the keys of the object.
|
||||
|
||||
Returns:
|
||||
The type of the keys of the object.
|
||||
"""
|
||||
fixed_type = (
|
||||
self._var_type if isclass(self._var_type) else get_origin(self._var_type)
|
||||
)
|
||||
args = get_args(self._var_type) if issubclass(fixed_type, dict) else ()
|
||||
return args[0] if args else Any
|
||||
|
||||
@overload
|
||||
def _value_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> VALUE_TYPE: ...
|
||||
|
||||
@overload
|
||||
def _value_type(self) -> Type: ...
|
||||
|
||||
def _value_type(self) -> Type:
|
||||
"""Get the type of the values of the object.
|
||||
|
||||
Returns:
|
||||
The type of the values of the object.
|
||||
"""
|
||||
fixed_type = (
|
||||
self._var_type if isclass(self._var_type) else get_origin(self._var_type)
|
||||
)
|
||||
args = get_args(self._var_type) if issubclass(fixed_type, dict) else ()
|
||||
return args[1] if args else Any
|
||||
|
||||
@overload
|
||||
def keys(
|
||||
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
|
||||
) -> ArrayVar[List[KEY_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def keys(self) -> ArrayVar: ...
|
||||
|
||||
def keys(self) -> ArrayVar:
|
||||
"""Get the keys of the object.
|
||||
|
||||
Returns:
|
||||
The keys of the object.
|
||||
"""
|
||||
return ObjectKeysOperation(self)
|
||||
|
||||
@overload
|
||||
def values(
|
||||
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
|
||||
) -> ArrayVar[List[VALUE_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def values(self) -> ArrayVar: ...
|
||||
|
||||
def values(self) -> ArrayVar:
|
||||
"""Get the values of the object.
|
||||
|
||||
Returns:
|
||||
The values of the object.
|
||||
"""
|
||||
return ObjectValuesOperation(self)
|
||||
|
||||
@overload
|
||||
def entries(
|
||||
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
|
||||
) -> ArrayVar[List[Tuple[KEY_TYPE, VALUE_TYPE]]]: ...
|
||||
|
||||
@overload
|
||||
def entries(self) -> ArrayVar: ...
|
||||
|
||||
def entries(self) -> ArrayVar:
|
||||
"""Get the entries of the object.
|
||||
|
||||
Returns:
|
||||
The entries of the object.
|
||||
"""
|
||||
return ObjectEntriesOperation(self)
|
||||
|
||||
def merge(self, other: ObjectVar) -> ObjectMergeOperation:
|
||||
"""Merge two objects.
|
||||
|
||||
Args:
|
||||
other: The other object to merge.
|
||||
|
||||
Returns:
|
||||
The merged object.
|
||||
"""
|
||||
return ObjectMergeOperation(self, other)
|
||||
|
||||
# NoReturn is used here to catch when key value is Any
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, NoReturn]],
|
||||
key: Var | Any,
|
||||
) -> ImmutableVar: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: (
|
||||
ObjectVar[Dict[KEY_TYPE, int]]
|
||||
| ObjectVar[Dict[KEY_TYPE, float]]
|
||||
| ObjectVar[Dict[KEY_TYPE, int | float]]
|
||||
),
|
||||
key: Var | Any,
|
||||
) -> NumberVar: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, str]],
|
||||
key: Var | Any,
|
||||
) -> StringVar: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, list[ARRAY_INNER_TYPE]]],
|
||||
key: Var | Any,
|
||||
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, set[ARRAY_INNER_TYPE]]],
|
||||
key: Var | Any,
|
||||
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||
key: Var | Any,
|
||||
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||
key: Var | Any,
|
||||
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||
|
||||
def __getitem__(self, key: Var | Any) -> ImmutableVar:
|
||||
"""Get an item from the object.
|
||||
|
||||
Args:
|
||||
key: The key to get from the object.
|
||||
|
||||
Returns:
|
||||
The item from the object.
|
||||
"""
|
||||
return ObjectItemOperation(self, key).guess_type()
|
||||
|
||||
# NoReturn is used here to catch when key value is Any
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, NoReturn]],
|
||||
name: str,
|
||||
) -> ImmutableVar: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: (
|
||||
ObjectVar[Dict[KEY_TYPE, int]]
|
||||
| ObjectVar[Dict[KEY_TYPE, float]]
|
||||
| ObjectVar[Dict[KEY_TYPE, int | float]]
|
||||
),
|
||||
name: str,
|
||||
) -> NumberVar: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, str]],
|
||||
name: str,
|
||||
) -> StringVar: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, list[ARRAY_INNER_TYPE]]],
|
||||
name: str,
|
||||
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, set[ARRAY_INNER_TYPE]]],
|
||||
name: str,
|
||||
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||
name: str,
|
||||
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[KEY_TYPE, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||
name: str,
|
||||
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||
|
||||
def __getattr__(self, name) -> ImmutableVar:
|
||||
"""Get an attribute of the var.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Raises:
|
||||
VarAttributeError: The State var has no such attribute or may have been annotated wrongly.
|
||||
|
||||
Returns:
|
||||
The attribute of the var.
|
||||
"""
|
||||
fixed_type = (
|
||||
self._var_type if isclass(self._var_type) else get_origin(self._var_type)
|
||||
)
|
||||
if not issubclass(fixed_type, dict):
|
||||
attribute_type = get_attribute_access_type(self._var_type, name)
|
||||
if attribute_type is None:
|
||||
raise VarAttributeError(
|
||||
f"The State var `{self._var_name}` has no attribute '{name}' or may have been annotated "
|
||||
f"wrongly."
|
||||
)
|
||||
return ObjectItemOperation(self, name, attribute_type).guess_type()
|
||||
else:
|
||||
return ObjectItemOperation(self, name).guess_type()
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
|
||||
"""Base class for immutable literal object vars."""
|
||||
|
||||
_var_value: Dict[Union[Var, Any], Union[Var, Any]] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self: LiteralObjectVar[OBJECT_TYPE],
|
||||
_var_value: OBJECT_TYPE,
|
||||
_var_type: Type[OBJECT_TYPE] | None = None,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the object var.
|
||||
|
||||
Args:
|
||||
_var_value: The value of the var.
|
||||
_var_type: The type of the var.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
"""
|
||||
super(LiteralObjectVar, self).__init__(
|
||||
_var_name="",
|
||||
_var_type=(figure_out_type(_var_value) if _var_type is None else _var_type),
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(
|
||||
self,
|
||||
"_var_value",
|
||||
_var_value,
|
||||
)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
def _key_type(self) -> Type:
|
||||
"""Get the type of the keys of the object.
|
||||
|
||||
Returns:
|
||||
The type of the keys of the object.
|
||||
"""
|
||||
args_list = typing.get_args(self._var_type)
|
||||
return args_list[0] if args_list else Any
|
||||
|
||||
def _value_type(self) -> Type:
|
||||
"""Get the type of the values of the object.
|
||||
|
||||
Returns:
|
||||
The type of the values of the object.
|
||||
"""
|
||||
args_list = typing.get_args(self._var_type)
|
||||
return args_list[1] if args_list else Any
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the var.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the var.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the var.
|
||||
|
||||
Returns:
|
||||
The name of the var.
|
||||
"""
|
||||
return (
|
||||
"({ "
|
||||
+ ", ".join(
|
||||
[
|
||||
f"[{str(LiteralVar.create(key))}] : {str(LiteralVar.create(value))}"
|
||||
for key, value in self._var_value.items()
|
||||
]
|
||||
)
|
||||
+ " })"
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the Var.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
*[
|
||||
value._get_all_var_data()
|
||||
for key, value in self._var_value
|
||||
if isinstance(value, Var)
|
||||
],
|
||||
*[
|
||||
key._get_all_var_data()
|
||||
for key, value in self._var_value
|
||||
if isinstance(key, Var)
|
||||
],
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
||||
|
||||
def json(self) -> str:
|
||||
"""Get the JSON representation of the object.
|
||||
|
||||
Returns:
|
||||
The JSON representation of the object.
|
||||
"""
|
||||
return (
|
||||
"{"
|
||||
+ ", ".join(
|
||||
[
|
||||
f"{LiteralVar.create(key).json()}:{LiteralVar.create(value).json()}"
|
||||
for key, value in self._var_value.items()
|
||||
]
|
||||
)
|
||||
+ "}"
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Get the hash of the var.
|
||||
|
||||
Returns:
|
||||
The hash of the var.
|
||||
"""
|
||||
return hash((self.__class__.__name__, self._var_name))
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ObjectToArrayOperation(ArrayVar):
|
||||
"""Base class for object to array operations."""
|
||||
|
||||
value: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_var_value: ObjectVar,
|
||||
_var_type: Type = list,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the object to array operation.
|
||||
|
||||
Args:
|
||||
_var_value: The value of the operation.
|
||||
_var_data: Additional hooks and imports associated with the operation.
|
||||
"""
|
||||
super(ObjectToArrayOperation, self).__init__(
|
||||
_var_name="",
|
||||
_var_type=_var_type,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(self, "value", _var_value)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the operation.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: Must implement _cached_var_name.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"ObjectToArrayOperation must implement _cached_var_name"
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the operation.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the operation.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the operation.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
self.value._get_all_var_data(),
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
||||
|
||||
|
||||
class ObjectKeysOperation(ObjectToArrayOperation):
|
||||
"""Operation to get the keys of an object."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: ObjectVar,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the object keys operation.
|
||||
|
||||
Args:
|
||||
value: The value of the operation.
|
||||
_var_data: Additional hooks and imports associated with the operation.
|
||||
"""
|
||||
super(ObjectKeysOperation, self).__init__(
|
||||
value, List[value._key_type()], _var_data
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the operation.
|
||||
|
||||
Returns:
|
||||
The name of the operation.
|
||||
"""
|
||||
return f"Object.keys({self.value._var_name})"
|
||||
|
||||
|
||||
class ObjectValuesOperation(ObjectToArrayOperation):
|
||||
"""Operation to get the values of an object."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: ObjectVar,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the object values operation.
|
||||
|
||||
Args:
|
||||
value: The value of the operation.
|
||||
_var_data: Additional hooks and imports associated with the operation.
|
||||
"""
|
||||
super(ObjectValuesOperation, self).__init__(
|
||||
value, List[value._value_type()], _var_data
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the operation.
|
||||
|
||||
Returns:
|
||||
The name of the operation.
|
||||
"""
|
||||
return f"Object.values({self.value._var_name})"
|
||||
|
||||
|
||||
class ObjectEntriesOperation(ObjectToArrayOperation):
|
||||
"""Operation to get the entries of an object."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: ObjectVar,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the object entries operation.
|
||||
|
||||
Args:
|
||||
value: The value of the operation.
|
||||
_var_data: Additional hooks and imports associated with the operation.
|
||||
"""
|
||||
super(ObjectEntriesOperation, self).__init__(
|
||||
value, List[Tuple[value._key_type(), value._value_type()]], _var_data
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the operation.
|
||||
|
||||
Returns:
|
||||
The name of the operation.
|
||||
"""
|
||||
return f"Object.entries({self.value._var_name})"
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ObjectMergeOperation(ObjectVar):
|
||||
"""Operation to merge two objects."""
|
||||
|
||||
left: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
|
||||
right: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
left: ObjectVar,
|
||||
right: ObjectVar,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the object merge operation.
|
||||
|
||||
Args:
|
||||
left: The left object to merge.
|
||||
right: The right object to merge.
|
||||
_var_data: Additional hooks and imports associated with the operation.
|
||||
"""
|
||||
super(ObjectMergeOperation, self).__init__(
|
||||
_var_name="",
|
||||
_var_type=left._var_type,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(self, "left", left)
|
||||
object.__setattr__(self, "right", right)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the operation.
|
||||
|
||||
Returns:
|
||||
The name of the operation.
|
||||
"""
|
||||
return f"Object.assign({self.left._var_name}, {self.right._var_name})"
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the operation.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the operation.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the operation.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
self.left._get_all_var_data(),
|
||||
self.right._get_all_var_data(),
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ObjectItemOperation(ImmutableVar):
|
||||
"""Operation to get an item from an object."""
|
||||
|
||||
value: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
|
||||
key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: ObjectVar,
|
||||
key: Var | Any,
|
||||
_var_type: GenericType | None = None,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the object item operation.
|
||||
|
||||
Args:
|
||||
value: The value of the operation.
|
||||
key: The key to get from the object.
|
||||
_var_data: Additional hooks and imports associated with the operation.
|
||||
"""
|
||||
super(ObjectItemOperation, self).__init__(
|
||||
_var_name="",
|
||||
_var_type=value._value_type() if _var_type is None else _var_type,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(self, "value", value)
|
||||
object.__setattr__(
|
||||
self, "key", key if isinstance(key, Var) else LiteralVar.create(key)
|
||||
)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the operation.
|
||||
|
||||
Returns:
|
||||
The name of the operation.
|
||||
"""
|
||||
return f"{str(self.value)}[{str(self.key)}]"
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the operation.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the operation.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the operation.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
self.value._get_all_var_data(),
|
||||
self.key._get_all_var_data(),
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
eq=False,
|
||||
frozen=True,
|
||||
**{"slots": True} if sys.version_info >= (3, 10) else {},
|
||||
)
|
||||
class ToObjectOperation(ObjectVar):
|
||||
"""Operation to convert a var to an object."""
|
||||
|
||||
_original_var: Var = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
_original_var: Var,
|
||||
_var_type: Type = dict,
|
||||
_var_data: VarData | None = None,
|
||||
):
|
||||
"""Initialize the to object operation.
|
||||
|
||||
Args:
|
||||
_original_var: The original var to convert.
|
||||
_var_type: The type of the var.
|
||||
_var_data: Additional hooks and imports associated with the operation.
|
||||
"""
|
||||
super(ToObjectOperation, self).__init__(
|
||||
_var_name="",
|
||||
_var_type=_var_type,
|
||||
_var_data=ImmutableVarData.merge(_var_data),
|
||||
)
|
||||
object.__setattr__(self, "_original_var", _original_var)
|
||||
object.__delattr__(self, "_var_name")
|
||||
|
||||
@cached_property
|
||||
def _cached_var_name(self) -> str:
|
||||
"""The name of the operation.
|
||||
|
||||
Returns:
|
||||
The name of the operation.
|
||||
"""
|
||||
return str(self._original_var)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Get an attribute of the operation.
|
||||
|
||||
Args:
|
||||
name: The name of the attribute.
|
||||
|
||||
Returns:
|
||||
The attribute of the operation.
|
||||
"""
|
||||
if name == "_var_name":
|
||||
return self._cached_var_name
|
||||
return super(type(self), self).__getattr__(name)
|
||||
|
||||
@cached_property
|
||||
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Get all VarData associated with the operation.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return ImmutableVarData.merge(
|
||||
self._original_var._get_all_var_data(),
|
||||
self._var_data,
|
||||
)
|
||||
|
||||
def _get_all_var_data(self) -> ImmutableVarData | None:
|
||||
"""Wrapper method for cached property.
|
||||
|
||||
Returns:
|
||||
The VarData of the components and all of its children.
|
||||
"""
|
||||
return self._cached_get_all_var_data
|
1764
reflex/experimental/vars/sequence.py
Normal file
1764
reflex/experimental/vars/sequence.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ from reflex_cli.utils import dependency
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.custom_components.custom_components import custom_components_cli
|
||||
from reflex.utils import console, telemetry
|
||||
from reflex.utils import console, redir, telemetry
|
||||
|
||||
# Disable typer+rich integration for help panels
|
||||
typer.core.rich = False # type: ignore
|
||||
@ -65,6 +65,7 @@ def _init(
|
||||
name: str,
|
||||
template: str | None = None,
|
||||
loglevel: constants.LogLevel = config.loglevel,
|
||||
ai: bool = False,
|
||||
):
|
||||
"""Initialize a new Reflex app in the given directory."""
|
||||
from reflex.utils import exec, prerequisites
|
||||
@ -91,9 +92,31 @@ def _init(
|
||||
# Set up the web project.
|
||||
prerequisites.initialize_frontend_dependencies()
|
||||
|
||||
# Integrate with reflex.build.
|
||||
generation_hash = None
|
||||
if ai:
|
||||
if template is None:
|
||||
# If AI is requested and no template specified, redirect the user to reflex.build.
|
||||
generation_hash = redir.reflex_build_redirect()
|
||||
elif prerequisites.is_generation_hash(template):
|
||||
# Otherwise treat the template as a generation hash.
|
||||
generation_hash = template
|
||||
else:
|
||||
console.error(
|
||||
"Cannot use `--template` option with `--ai` option. Please remove `--template` option."
|
||||
)
|
||||
raise typer.Exit(2)
|
||||
template = constants.Templates.DEFAULT
|
||||
|
||||
# Initialize the app.
|
||||
prerequisites.initialize_app(app_name, template)
|
||||
|
||||
# If a reflex.build generation hash is available, download the code and apply it to the main module.
|
||||
if generation_hash:
|
||||
prerequisites.initialize_main_module_index_from_generation(
|
||||
app_name, generation_hash=generation_hash
|
||||
)
|
||||
|
||||
# Migrate Pynecone projects to Reflex.
|
||||
prerequisites.migrate_to_reflex()
|
||||
|
||||
@ -119,9 +142,13 @@ def init(
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
ai: bool = typer.Option(
|
||||
False,
|
||||
help="Use AI to create the initial template. Cannot be used with existing app or `--template` option.",
|
||||
),
|
||||
):
|
||||
"""Initialize a new Reflex app in the current directory."""
|
||||
_init(name, template, loglevel)
|
||||
_init(name, template, loglevel, ai)
|
||||
|
||||
|
||||
def _run(
|
||||
|
@ -32,6 +32,8 @@ from typing import (
|
||||
import dill
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
from reflex.config import get_config
|
||||
|
||||
try:
|
||||
import pydantic.v1 as pydantic
|
||||
except ModuleNotFoundError:
|
||||
@ -43,7 +45,6 @@ from redis.exceptions import ResponseError
|
||||
|
||||
from reflex import constants
|
||||
from reflex.base import Base
|
||||
from reflex.config import get_config
|
||||
from reflex.event import (
|
||||
BACKGROUND_TASK_MARKER,
|
||||
Event,
|
||||
@ -218,7 +219,7 @@ def _no_chain_background_task(
|
||||
|
||||
def _substate_key(
|
||||
token: str,
|
||||
state_cls_or_name: BaseState | Type[BaseState] | str | list[str],
|
||||
state_cls_or_name: BaseState | Type[BaseState] | str | Sequence[str],
|
||||
) -> str:
|
||||
"""Get the substate key.
|
||||
|
||||
@ -2085,19 +2086,38 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
self.counter += 1
|
||||
"""
|
||||
|
||||
def __init__(self, state_instance):
|
||||
def __init__(
|
||||
self, state_instance, parent_state_proxy: Optional["StateProxy"] = None
|
||||
):
|
||||
"""Create a proxy for a state instance.
|
||||
|
||||
If `get_state` is used on a StateProxy, the resulting state will be
|
||||
linked to the given state via parent_state_proxy. The first state in the
|
||||
chain is the state that initiated the background task.
|
||||
|
||||
Args:
|
||||
state_instance: The state instance to proxy.
|
||||
parent_state_proxy: The parent state proxy, for linked mutability and context tracking.
|
||||
"""
|
||||
super().__init__(state_instance)
|
||||
# compile is not relevant to backend logic
|
||||
self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
self._self_substate_path = state_instance.get_full_name().split(".")
|
||||
self._self_substate_path = tuple(state_instance.get_full_name().split("."))
|
||||
self._self_actx = None
|
||||
self._self_mutable = False
|
||||
self._self_actx_lock = asyncio.Lock()
|
||||
self._self_actx_lock_holder = None
|
||||
self._self_parent_state_proxy = parent_state_proxy
|
||||
|
||||
def _is_mutable(self) -> bool:
|
||||
"""Check if the state is mutable.
|
||||
|
||||
Returns:
|
||||
Whether the state is mutable.
|
||||
"""
|
||||
if self._self_parent_state_proxy is not None:
|
||||
return self._self_parent_state_proxy._is_mutable() or self._self_mutable
|
||||
return self._self_mutable
|
||||
|
||||
async def __aenter__(self) -> StateProxy:
|
||||
"""Enter the async context manager protocol.
|
||||
@ -2110,8 +2130,31 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
|
||||
Returns:
|
||||
This StateProxy instance in mutable mode.
|
||||
|
||||
Raises:
|
||||
ImmutableStateError: If the state is already mutable.
|
||||
"""
|
||||
if self._self_parent_state_proxy is not None:
|
||||
parent_state = (
|
||||
await self._self_parent_state_proxy.__aenter__()
|
||||
).__wrapped__
|
||||
super().__setattr__(
|
||||
"__wrapped__",
|
||||
await parent_state.get_state(
|
||||
State.get_class_substate(self._self_substate_path)
|
||||
),
|
||||
)
|
||||
return self
|
||||
current_task = asyncio.current_task()
|
||||
if (
|
||||
self._self_actx_lock.locked()
|
||||
and current_task == self._self_actx_lock_holder
|
||||
):
|
||||
raise ImmutableStateError(
|
||||
"The state is already mutable. Do not nest `async with self` blocks."
|
||||
)
|
||||
await self._self_actx_lock.acquire()
|
||||
self._self_actx_lock_holder = current_task
|
||||
self._self_actx = self._self_app.modify_state(
|
||||
token=_substate_key(
|
||||
self.__wrapped__.router.session.client_token,
|
||||
@ -2133,12 +2176,16 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
Args:
|
||||
exc_info: The exception info tuple.
|
||||
"""
|
||||
if self._self_parent_state_proxy is not None:
|
||||
await self._self_parent_state_proxy.__aexit__(*exc_info)
|
||||
return
|
||||
if self._self_actx is None:
|
||||
return
|
||||
self._self_mutable = False
|
||||
try:
|
||||
await self._self_actx.__aexit__(*exc_info)
|
||||
finally:
|
||||
self._self_actx_lock_holder = None
|
||||
self._self_actx_lock.release()
|
||||
self._self_actx = None
|
||||
|
||||
@ -2173,7 +2220,7 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
Raises:
|
||||
ImmutableStateError: If the state is not in mutable mode.
|
||||
"""
|
||||
if name in ["substates", "parent_state"] and not self._self_mutable:
|
||||
if name in ["substates", "parent_state"] and not self._is_mutable():
|
||||
raise ImmutableStateError(
|
||||
"Background task StateProxy is immutable outside of a context "
|
||||
"manager. Use `async with self` to modify state."
|
||||
@ -2213,7 +2260,7 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
"""
|
||||
if (
|
||||
name.startswith("_self_") # wrapper attribute
|
||||
or self._self_mutable # lock held
|
||||
or self._is_mutable() # lock held
|
||||
# non-persisted state attribute
|
||||
or name in self.__wrapped__.get_skip_vars()
|
||||
):
|
||||
@ -2237,7 +2284,7 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
Raises:
|
||||
ImmutableStateError: If the state is not in mutable mode.
|
||||
"""
|
||||
if not self._self_mutable:
|
||||
if not self._is_mutable():
|
||||
raise ImmutableStateError(
|
||||
"Background task StateProxy is immutable outside of a context "
|
||||
"manager. Use `async with self` to modify state."
|
||||
@ -2256,12 +2303,14 @@ class StateProxy(wrapt.ObjectProxy):
|
||||
Raises:
|
||||
ImmutableStateError: If the state is not in mutable mode.
|
||||
"""
|
||||
if not self._self_mutable:
|
||||
if not self._is_mutable():
|
||||
raise ImmutableStateError(
|
||||
"Background task StateProxy is immutable outside of a context "
|
||||
"manager. Use `async with self` to modify state."
|
||||
)
|
||||
return await self.__wrapped__.get_state(state_cls)
|
||||
return type(self)(
|
||||
await self.__wrapped__.get_state(state_cls), parent_state_proxy=self
|
||||
)
|
||||
|
||||
def _as_state_update(self, *args, **kwargs) -> StateUpdate:
|
||||
"""Temporarily allow mutability to access parent_state.
|
||||
@ -3325,7 +3374,7 @@ class ImmutableMutableProxy(MutableProxy):
|
||||
Raises:
|
||||
ImmutableStateError: if the StateProxy is not mutable.
|
||||
"""
|
||||
if not self._self_state._self_mutable:
|
||||
if not self._self_state._is_mutable():
|
||||
raise ImmutableStateError(
|
||||
"Background task StateProxy is immutable outside of a context "
|
||||
"manager. Use `async with self` to modify state."
|
||||
|
@ -115,7 +115,7 @@ class AppHarness:
|
||||
|
||||
app_name: str
|
||||
app_source: Optional[
|
||||
types.FunctionType | types.ModuleType | str | functools.partial
|
||||
types.FunctionType | types.ModuleType | str | functools.partial[Any]
|
||||
]
|
||||
app_path: pathlib.Path
|
||||
app_module_path: pathlib.Path
|
||||
@ -134,7 +134,9 @@ class AppHarness:
|
||||
def create(
|
||||
cls,
|
||||
root: pathlib.Path,
|
||||
app_source: Optional[types.FunctionType | types.ModuleType | str] = None,
|
||||
app_source: Optional[
|
||||
types.FunctionType | types.ModuleType | str | functools.partial[Any]
|
||||
] = None,
|
||||
app_name: Optional[str] = None,
|
||||
) -> "AppHarness":
|
||||
"""Create an AppHarness instance at root.
|
||||
@ -274,7 +276,10 @@ class AppHarness:
|
||||
before_decorated_pages = reflex.app.DECORATED_PAGES[self.app_name].copy()
|
||||
# Ensure the AppHarness test does not skip State assignment due to running via pytest
|
||||
os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
|
||||
self.app_module = reflex.utils.prerequisites.get_compiled_app(reload=True)
|
||||
self.app_module = reflex.utils.prerequisites.get_compiled_app(
|
||||
# Do not reload the module for pre-existing apps (only apps generated from source)
|
||||
reload=self.app_source is not None
|
||||
)
|
||||
# Save the pages that were added during testing
|
||||
self._decorated_pages = [
|
||||
p
|
||||
|
@ -438,15 +438,16 @@ def format_prop(
|
||||
sig = inspect.signature(prop.args_spec) # type: ignore
|
||||
if sig.parameters:
|
||||
arg_def = ",".join(f"_{p}" for p in sig.parameters)
|
||||
arg_def = f"({arg_def})"
|
||||
arg_def_expr = f"[{arg_def}]"
|
||||
else:
|
||||
# add a default argument for addEvents if none were specified in prop.args_spec
|
||||
# used to trigger the preventDefault() on the event.
|
||||
arg_def = "(_e)"
|
||||
arg_def = "...args"
|
||||
arg_def_expr = "args"
|
||||
|
||||
chain = ",".join([format_event(event) for event in prop.events])
|
||||
event = f"addEvents([{chain}], {arg_def}, {json_dumps(prop.event_actions)})"
|
||||
prop = f"{arg_def} => {event}"
|
||||
event = f"addEvents([{chain}], {arg_def_expr}, {json_dumps(prop.event_actions)})"
|
||||
prop = f"({arg_def}) => {event}"
|
||||
|
||||
# Handle other types.
|
||||
elif isinstance(prop, str):
|
||||
|
@ -16,6 +16,7 @@ import shutil
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from fileinput import FileInput
|
||||
@ -37,6 +38,7 @@ from reflex.compiler import templates
|
||||
from reflex.config import Config, get_config
|
||||
from reflex.utils import console, path_ops, processes
|
||||
from reflex.utils.format import format_library_name
|
||||
from reflex.utils.registry import _get_best_registry
|
||||
|
||||
CURRENTLY_INSTALLING_NODE = False
|
||||
|
||||
@ -576,6 +578,15 @@ def initialize_package_json():
|
||||
code = _compile_package_json()
|
||||
output_path.write_text(code)
|
||||
|
||||
best_registry = _get_best_registry()
|
||||
bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
|
||||
bun_config_path.write_text(
|
||||
f"""
|
||||
[install]
|
||||
registry = "{best_registry}"
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def init_reflex_json(project_hash: int | None):
|
||||
"""Write the hash of the Reflex project to a REFLEX_JSON.
|
||||
@ -1310,39 +1321,60 @@ def migrate_to_reflex():
|
||||
print(line, end="")
|
||||
|
||||
|
||||
def fetch_app_templates() -> dict[str, Template]:
|
||||
"""Fetch the list of app templates from the Reflex backend server.
|
||||
def fetch_app_templates(version: str) -> dict[str, Template]:
|
||||
"""Fetch a dict of templates from the templates repo using github API.
|
||||
|
||||
Args:
|
||||
version: The version of the templates to fetch.
|
||||
|
||||
Returns:
|
||||
The name and download URL as a dictionary.
|
||||
The dict of templates.
|
||||
"""
|
||||
config = get_config()
|
||||
if not config.cp_backend_url:
|
||||
console.info(
|
||||
"Skip fetching App templates. No backend URL is specified in the config."
|
||||
)
|
||||
return {}
|
||||
try:
|
||||
response = httpx.get(
|
||||
f"{config.cp_backend_url}{constants.Templates.APP_TEMPLATES_ROUTE}"
|
||||
)
|
||||
|
||||
def get_release_by_tag(tag: str) -> dict | None:
|
||||
response = httpx.get(constants.Reflex.RELEASES_URL)
|
||||
response.raise_for_status()
|
||||
releases = response.json()
|
||||
for release in releases:
|
||||
if release["tag_name"] == f"v{tag}":
|
||||
return release
|
||||
return None
|
||||
|
||||
release = get_release_by_tag(version)
|
||||
if release is None:
|
||||
console.warn(f"No templates known for version {version}")
|
||||
return {}
|
||||
|
||||
assets = release.get("assets", [])
|
||||
asset = next((a for a in assets if a["name"] == "templates.json"), None)
|
||||
if asset is None:
|
||||
console.warn(f"Templates metadata not found for version {version}")
|
||||
return {}
|
||||
else:
|
||||
templates_url = asset["browser_download_url"]
|
||||
|
||||
templates_data = httpx.get(templates_url, follow_redirects=True).json()["templates"]
|
||||
|
||||
for template in templates_data:
|
||||
if template["name"] == "blank":
|
||||
template["code_url"] = ""
|
||||
continue
|
||||
template["code_url"] = next(
|
||||
(
|
||||
a["browser_download_url"]
|
||||
for a in assets
|
||||
if a["name"] == f"{template['name']}.zip"
|
||||
),
|
||||
None,
|
||||
)
|
||||
return {
|
||||
template["name"]: Template.parse_obj(template)
|
||||
for template in response.json()
|
||||
tp["name"]: Template.parse_obj(tp)
|
||||
for tp in templates_data
|
||||
if not tp["hidden"] and tp["code_url"] is not None
|
||||
}
|
||||
except httpx.HTTPError as ex:
|
||||
console.info(f"Failed to fetch app templates: {ex}")
|
||||
return {}
|
||||
except (TypeError, KeyError, json.JSONDecodeError) as tkje:
|
||||
console.info(f"Unable to process server response for app templates: {tkje}")
|
||||
return {}
|
||||
|
||||
|
||||
def create_config_init_app_from_remote_template(
|
||||
app_name: str,
|
||||
template_url: str,
|
||||
):
|
||||
def create_config_init_app_from_remote_template(app_name: str, template_url: str):
|
||||
"""Create new rxconfig and initialize app using a remote template.
|
||||
|
||||
Args:
|
||||
@ -1412,7 +1444,11 @@ def create_config_init_app_from_remote_template(
|
||||
template_code_dir_name=template_name,
|
||||
template_dir=template_dir,
|
||||
)
|
||||
|
||||
req_file = Path("requirements.txt")
|
||||
if req_file.exists() and len(req_file.read_text().splitlines()) > 1:
|
||||
console.info(
|
||||
"Run `pip install -r requirements.txt` to install the required python packages for this template."
|
||||
)
|
||||
# Clean up the temp directories.
|
||||
shutil.rmtree(temp_dir)
|
||||
shutil.rmtree(unzip_dir)
|
||||
@ -1436,15 +1472,20 @@ def initialize_app(app_name: str, template: str | None = None):
|
||||
telemetry.send("reinit")
|
||||
return
|
||||
|
||||
# Get the available templates
|
||||
templates: dict[str, Template] = fetch_app_templates()
|
||||
templates: dict[str, Template] = {}
|
||||
|
||||
# Prompt for a template if not provided.
|
||||
# Don't fetch app templates if the user directly asked for DEFAULT.
|
||||
if template is None or (template != constants.Templates.DEFAULT):
|
||||
try:
|
||||
# Get the available templates
|
||||
templates = fetch_app_templates(constants.Reflex.VERSION)
|
||||
if template is None and len(templates) > 0:
|
||||
template = prompt_for_template(list(templates.values()))
|
||||
elif template is None:
|
||||
template = constants.Templates.DEFAULT
|
||||
assert template is not None
|
||||
except Exception as e:
|
||||
console.warn("Failed to fetch templates. Falling back to default template.")
|
||||
console.debug(f"Error while fetching templates: {e}")
|
||||
finally:
|
||||
template = template or constants.Templates.DEFAULT
|
||||
|
||||
# If the blank template is selected, create a blank app.
|
||||
if template == constants.Templates.DEFAULT:
|
||||
@ -1467,14 +1508,52 @@ def initialize_app(app_name: str, template: str | None = None):
|
||||
else:
|
||||
console.error(f"Template `{template}` not found.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if template_url is None:
|
||||
return
|
||||
|
||||
create_config_init_app_from_remote_template(
|
||||
app_name=app_name,
|
||||
template_url=template_url,
|
||||
app_name=app_name, template_url=template_url
|
||||
)
|
||||
|
||||
telemetry.send("init", template=template)
|
||||
|
||||
|
||||
def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
|
||||
"""Overwrite the `index` function in the main module with reflex.build generated code.
|
||||
|
||||
Args:
|
||||
app_name: The name of the app.
|
||||
generation_hash: The generation hash from reflex.build.
|
||||
"""
|
||||
# Download the reflex code for the generation.
|
||||
resp = httpx.get(
|
||||
constants.Templates.REFLEX_BUILD_CODE_URL.format(
|
||||
generation_hash=generation_hash
|
||||
)
|
||||
).raise_for_status()
|
||||
|
||||
def replace_content(_match):
|
||||
return "\n".join(
|
||||
[
|
||||
"def index() -> rx.Component:",
|
||||
textwrap.indent("return " + resp.text, " "),
|
||||
"",
|
||||
"",
|
||||
],
|
||||
)
|
||||
|
||||
main_module_path = Path(app_name, app_name + constants.Ext.PY)
|
||||
main_module_code = main_module_path.read_text()
|
||||
main_module_path.write_text(
|
||||
re.sub(
|
||||
r"def index\(\).*:\n([^\n]\s+.*\n+)+",
|
||||
replace_content,
|
||||
main_module_code,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def format_address_width(address_width) -> int | None:
|
||||
"""Cast address width to an int.
|
||||
|
||||
@ -1562,3 +1641,15 @@ def is_windows_bun_supported() -> bool:
|
||||
and cpu_info.model_name is not None
|
||||
and "ARM" not in cpu_info.model_name
|
||||
)
|
||||
|
||||
|
||||
def is_generation_hash(template: str) -> bool:
|
||||
"""Check if the template looks like a generation hash.
|
||||
|
||||
Args:
|
||||
template: The template name.
|
||||
|
||||
Returns:
|
||||
True if the template is composed of 32 or more hex characters.
|
||||
"""
|
||||
return re.match(r"^[0-9a-f]{32,}$", template) is not None
|
||||
|
@ -882,6 +882,7 @@ class PyiGenerator:
|
||||
# retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present.
|
||||
sub_mods = getattr(mod, "_SUBMODULES", None)
|
||||
sub_mod_attrs = getattr(mod, "_SUBMOD_ATTRS", None)
|
||||
pyright_ignore_imports = getattr(mod, "_PYRIGHT_IGNORE_IMPORTS", [])
|
||||
|
||||
if not sub_mods and not sub_mod_attrs:
|
||||
return
|
||||
@ -901,6 +902,7 @@ class PyiGenerator:
|
||||
# construct the import statement and handle special cases for aliases
|
||||
sub_mod_attrs_imports = [
|
||||
f"from .{path} import {mod if not isinstance(mod, tuple) else mod[0]} as {mod if not isinstance(mod, tuple) else mod[1]}"
|
||||
+ (" # type: ignore" if mod in pyright_ignore_imports else "")
|
||||
for mod, path in sub_mod_attrs.items()
|
||||
]
|
||||
sub_mod_attrs_imports.append("")
|
||||
|
52
reflex/utils/redir.py
Normal file
52
reflex/utils/redir.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Utilities to handle redirection to browser UI."""
|
||||
|
||||
import time
|
||||
import uuid
|
||||
import webbrowser
|
||||
|
||||
import httpx
|
||||
|
||||
from .. import constants
|
||||
from . import console
|
||||
|
||||
|
||||
def open_browser_and_wait(
|
||||
target_url: str, poll_url: str, interval: int = 2
|
||||
) -> httpx.Response:
|
||||
"""Open a browser window to target_url and request poll_url until it returns successfully.
|
||||
|
||||
Args:
|
||||
target_url: The URL to open in the browser.
|
||||
poll_url: The URL to poll for success.
|
||||
interval: The interval in seconds to wait between polling.
|
||||
|
||||
Returns:
|
||||
The response from the poll_url.
|
||||
"""
|
||||
if not webbrowser.open(target_url):
|
||||
console.warn(
|
||||
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
|
||||
)
|
||||
console.info("[b]Complete the workflow in the browser to continue.[/b]")
|
||||
while True:
|
||||
try:
|
||||
response = httpx.get(poll_url, follow_redirects=True)
|
||||
if response.is_success:
|
||||
break
|
||||
except httpx.RequestError as err:
|
||||
console.info(f"Will retry after error occurred while polling: {err}.")
|
||||
time.sleep(interval)
|
||||
return response
|
||||
|
||||
|
||||
def reflex_build_redirect() -> str:
|
||||
"""Open the browser window to reflex.build and wait for the user to select a generation.
|
||||
|
||||
Returns:
|
||||
The selected generation hash.
|
||||
"""
|
||||
token = str(uuid.uuid4())
|
||||
target_url = constants.Templates.REFLEX_BUILD_URL.format(reflex_init_token=token)
|
||||
poll_url = constants.Templates.REFLEX_BUILD_POLL_URL.format(reflex_init_token=token)
|
||||
response = open_browser_and_wait(target_url, poll_url)
|
||||
return response.json()["generation_hash"]
|
48
reflex/utils/registry.py
Normal file
48
reflex/utils/registry.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""Utilities for working with registries."""
|
||||
|
||||
import httpx
|
||||
|
||||
from reflex.utils import console
|
||||
|
||||
|
||||
def latency(registry: str) -> int:
|
||||
"""Get the latency of a registry.
|
||||
|
||||
Args:
|
||||
registry (str): The URL of the registry.
|
||||
|
||||
Returns:
|
||||
int: The latency of the registry in microseconds.
|
||||
"""
|
||||
try:
|
||||
return httpx.get(registry).elapsed.microseconds
|
||||
except httpx.HTTPError:
|
||||
console.info(f"Failed to connect to {registry}.")
|
||||
return 10_000_000
|
||||
|
||||
|
||||
def average_latency(registry, attempts: int = 3) -> int:
|
||||
"""Get the average latency of a registry.
|
||||
|
||||
Args:
|
||||
registry (str): The URL of the registry.
|
||||
attempts (int): The number of attempts to make. Defaults to 10.
|
||||
|
||||
Returns:
|
||||
int: The average latency of the registry in microseconds.
|
||||
"""
|
||||
return sum(latency(registry) for _ in range(attempts)) // attempts
|
||||
|
||||
|
||||
def _get_best_registry() -> str:
|
||||
"""Get the best registry based on latency.
|
||||
|
||||
Returns:
|
||||
str: The best registry.
|
||||
"""
|
||||
registries = [
|
||||
"https://registry.npmjs.org",
|
||||
"https://r.cnpmjs.org",
|
||||
]
|
||||
|
||||
return min(registries, key=average_latency)
|
@ -379,7 +379,9 @@ def _decode_var_immutable(value: str) -> tuple[ImmutableVarData | None, str]:
|
||||
|
||||
serialized_data = m.group(1)
|
||||
|
||||
if serialized_data[1:].isnumeric():
|
||||
if serialized_data.isnumeric() or (
|
||||
serialized_data[0] == "-" and serialized_data[1:].isnumeric()
|
||||
):
|
||||
# This is a global immutable var.
|
||||
var = _global_vars[int(serialized_data)]
|
||||
var_data = var._var_data
|
||||
@ -473,7 +475,9 @@ def _decode_var(value: str) -> tuple[VarData | None, str]:
|
||||
|
||||
serialized_data = m.group(1)
|
||||
|
||||
if serialized_data[1:].isnumeric():
|
||||
if serialized_data.isnumeric() or (
|
||||
serialized_data[0] == "-" and serialized_data[1:].isnumeric()
|
||||
):
|
||||
# This is a global immutable var.
|
||||
var = _global_vars[int(serialized_data)]
|
||||
var_data = var._var_data
|
||||
@ -1997,6 +2001,14 @@ class Var:
|
||||
"""
|
||||
return self._var_data
|
||||
|
||||
def json(self) -> str:
|
||||
"""Serialize the var to a JSON string.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If the method is not implemented.
|
||||
"""
|
||||
raise NotImplementedError("Var subclasses must implement the json method.")
|
||||
|
||||
@property
|
||||
def _var_name_unwrapped(self) -> str:
|
||||
"""Get the var str without wrapping in curly braces.
|
||||
@ -2170,6 +2182,24 @@ class ComputedVar(Var, property):
|
||||
# Interval at which the computed var should be updated
|
||||
_update_interval: Optional[datetime.timedelta] = dataclasses.field(default=None)
|
||||
|
||||
# The name of the var.
|
||||
_var_name: str = dataclasses.field()
|
||||
|
||||
# The type of the var.
|
||||
_var_type: Type = dataclasses.field(default=Any)
|
||||
|
||||
# Whether this is a local javascript variable.
|
||||
_var_is_local: bool = dataclasses.field(default=False)
|
||||
|
||||
# Whether the var is a string literal.
|
||||
_var_is_string: bool = dataclasses.field(default=False)
|
||||
|
||||
# _var_full_name should be prefixed with _var_state
|
||||
_var_full_name_needs_state_prefix: bool = dataclasses.field(default=False)
|
||||
|
||||
# Extra metadata associated with the Var
|
||||
_var_data: Optional[VarData] = dataclasses.field(default=None)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fget: Callable[[BaseState], Any],
|
||||
@ -2458,7 +2488,7 @@ class ComputedVar(Var, property):
|
||||
|
||||
def computed_var(
|
||||
fget: Callable[[BaseState], Any] | None = None,
|
||||
initial_value: Any | None = None,
|
||||
initial_value: Any | types.Unset = types.Unset(),
|
||||
cache: bool = False,
|
||||
deps: Optional[List[Union[str, Var]]] = None,
|
||||
auto_deps: bool = True,
|
||||
@ -2554,17 +2584,25 @@ class CallableVar(BaseVar):
|
||||
|
||||
|
||||
def get_uuid_string_var() -> Var:
|
||||
"""Return a var that generates UUIDs via .web/utils/state.js.
|
||||
"""Return a Var that generates a single memoized UUID via .web/utils/state.js.
|
||||
|
||||
useMemo with an empty dependency array ensures that the generated UUID is
|
||||
consistent across re-renders of the component.
|
||||
|
||||
Returns:
|
||||
the var to generate UUIDs at runtime.
|
||||
A Var that generates a UUID at runtime.
|
||||
"""
|
||||
from reflex.utils.imports import ImportVar
|
||||
|
||||
unique_uuid_var_data = VarData(
|
||||
imports={f"/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}} # type: ignore
|
||||
imports={
|
||||
f"/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # type: ignore
|
||||
"react": "useMemo",
|
||||
}
|
||||
)
|
||||
|
||||
return BaseVar(
|
||||
_var_name="generateUUID()", _var_type=str, _var_data=unique_uuid_var_data
|
||||
_var_name="useMemo(generateUUID, [])",
|
||||
_var_type=str,
|
||||
_var_data=unique_uuid_var_data,
|
||||
)
|
||||
|
@ -151,6 +151,7 @@ class Var:
|
||||
def _var_full_name(self) -> str: ...
|
||||
def _var_set_state(self, state: Type[BaseState] | str) -> Any: ...
|
||||
def _get_all_var_data(self) -> VarData: ...
|
||||
def json(self) -> str: ...
|
||||
|
||||
@dataclass(eq=False)
|
||||
class BaseVar(Var):
|
||||
@ -189,7 +190,7 @@ class ComputedVar(Var):
|
||||
@overload
|
||||
def computed_var(
|
||||
fget: Callable[[BaseState], Any] | None = None,
|
||||
initial_value: Any | None = None,
|
||||
initial_value: Any | types.Unset = types.Unset(),
|
||||
cache: bool = False,
|
||||
deps: Optional[List[Union[str, Var]]] = None,
|
||||
auto_deps: bool = True,
|
||||
@ -201,7 +202,7 @@ def computed_var(fget: Callable[[Any], Any]) -> ComputedVar: ...
|
||||
@overload
|
||||
def cached_var(
|
||||
fget: Callable[[BaseState], Any] | None = None,
|
||||
initial_value: Any | None = None,
|
||||
initial_value: Any | types.Unset = types.Unset(),
|
||||
deps: Optional[List[Union[str, Var]]] = None,
|
||||
auto_deps: bool = True,
|
||||
interval: Optional[Union[datetime.timedelta, int]] = None,
|
||||
|
@ -58,14 +58,14 @@ def test_script_event_handler():
|
||||
)
|
||||
render_dict = component.render()
|
||||
assert (
|
||||
f'onReady={{(_e) => addEvents([Event("{EvState.get_full_name()}.on_ready", {{}})], (_e), {{}})}}'
|
||||
f'onReady={{(...args) => addEvents([Event("{EvState.get_full_name()}.on_ready", {{}})], args, {{}})}}'
|
||||
in render_dict["props"]
|
||||
)
|
||||
assert (
|
||||
f'onLoad={{(_e) => addEvents([Event("{EvState.get_full_name()}.on_load", {{}})], (_e), {{}})}}'
|
||||
f'onLoad={{(...args) => addEvents([Event("{EvState.get_full_name()}.on_load", {{}})], args, {{}})}}'
|
||||
in render_dict["props"]
|
||||
)
|
||||
assert (
|
||||
f'onError={{(_e) => addEvents([Event("{EvState.get_full_name()}.on_error", {{}})], (_e), {{}})}}'
|
||||
f'onError={{(...args) => addEvents([Event("{EvState.get_full_name()}.on_error", {{}})], args, {{}})}}'
|
||||
in render_dict["props"]
|
||||
)
|
||||
|
@ -826,7 +826,7 @@ def test_component_event_trigger_arbitrary_args():
|
||||
assert comp.render()["props"][0] == (
|
||||
"onFoo={(__e,_alpha,_bravo,_charlie) => addEvents("
|
||||
f'[Event("{C1State.get_full_name()}.mock_handler", {{_e:__e.target.value,_bravo:_bravo["nested"],_charlie:((_charlie.custom) + (42))}})], '
|
||||
"(__e,_alpha,_bravo,_charlie), {})}"
|
||||
"[__e,_alpha,_bravo,_charlie], {})}"
|
||||
)
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@ import reflex.config
|
||||
from reflex import constants
|
||||
from reflex.app import App
|
||||
from reflex.base import Base
|
||||
from reflex.components.sonner.toast import Toaster
|
||||
from reflex.constants import CompileVars, RouteVar, SocketEvent
|
||||
from reflex.event import Event, EventHandler
|
||||
from reflex.state import (
|
||||
@ -1011,6 +1012,21 @@ def interdependent_state() -> BaseState:
|
||||
return s
|
||||
|
||||
|
||||
def test_interdependent_state_initial_dict() -> None:
|
||||
s = InterdependentState()
|
||||
state_name = s.get_name()
|
||||
d = s.dict(initial=True)[state_name]
|
||||
d.pop("router")
|
||||
assert d == {
|
||||
"x": 0,
|
||||
"v1": 0,
|
||||
"v1x2": 0,
|
||||
"v2x2": 2,
|
||||
"v1x2x2": 0,
|
||||
"v3x2": 2,
|
||||
}
|
||||
|
||||
|
||||
def test_not_dirty_computed_var_from_var(
|
||||
interdependent_state: InterdependentState,
|
||||
) -> None:
|
||||
@ -1527,7 +1543,6 @@ async def test_state_with_invalid_yield(capsys, mock_app):
|
||||
Args:
|
||||
capsys: Pytest fixture for capture standard streams.
|
||||
mock_app: Mock app fixture.
|
||||
|
||||
"""
|
||||
|
||||
class StateWithInvalidYield(BaseState):
|
||||
@ -1546,8 +1561,27 @@ async def test_state_with_invalid_yield(capsys, mock_app):
|
||||
rx.event.Event(token="fake_token", name="invalid_handler")
|
||||
):
|
||||
assert not update.delta
|
||||
if Toaster.is_used:
|
||||
assert update.events == rx.event.fix_events(
|
||||
[rx.window_alert("An error occurred. See logs for details.")],
|
||||
[
|
||||
rx.toast(
|
||||
"An error occurred.",
|
||||
description="TypeError: Your handler test_state_with_invalid_yield.<locals>.StateWithInvalidYield.invalid_handler must only return/yield: None, Events or other EventHandlers referenced by their class (not using `self`).<br/>See logs for details.",
|
||||
level="error",
|
||||
id="backend_error",
|
||||
position="top-center",
|
||||
style={"width": "500px"},
|
||||
) # type: ignore
|
||||
],
|
||||
token="",
|
||||
)
|
||||
else:
|
||||
assert update.events == rx.event.fix_events(
|
||||
[
|
||||
rx.window_alert(
|
||||
"An error occurred.\nContact the website administrator."
|
||||
)
|
||||
],
|
||||
token="",
|
||||
)
|
||||
captured = capsys.readouterr()
|
||||
@ -1806,7 +1840,7 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App):
|
||||
|
||||
sp = StateProxy(grandchild_state)
|
||||
assert sp.__wrapped__ == grandchild_state
|
||||
assert sp._self_substate_path == grandchild_state.get_full_name().split(".")
|
||||
assert sp._self_substate_path == tuple(grandchild_state.get_full_name().split("."))
|
||||
assert sp._self_app is mock_app
|
||||
assert not sp._self_mutable
|
||||
assert sp._self_actx is None
|
||||
|
@ -1,4 +1,5 @@
|
||||
import json
|
||||
import math
|
||||
import typing
|
||||
from typing import Dict, List, Set, Tuple, Union
|
||||
|
||||
@ -8,8 +9,21 @@ from pandas import DataFrame
|
||||
from reflex.base import Base
|
||||
from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
|
||||
from reflex.experimental.vars.base import (
|
||||
ConcatVarOperation,
|
||||
ImmutableVar,
|
||||
LiteralVar,
|
||||
var_operation,
|
||||
)
|
||||
from reflex.experimental.vars.function import ArgsFunctionOperation, FunctionStringVar
|
||||
from reflex.experimental.vars.number import (
|
||||
LiteralBooleanVar,
|
||||
LiteralNumberVar,
|
||||
NumberVar,
|
||||
)
|
||||
from reflex.experimental.vars.object import LiteralObjectVar
|
||||
from reflex.experimental.vars.sequence import (
|
||||
ArrayVar,
|
||||
ConcatVarOperation,
|
||||
LiteralArrayVar,
|
||||
LiteralStringVar,
|
||||
)
|
||||
from reflex.state import BaseState
|
||||
@ -858,6 +872,220 @@ def test_state_with_initial_computed_var(
|
||||
assert runtime_dict[var_name] == expected_runtime
|
||||
|
||||
|
||||
def test_literal_var():
|
||||
complicated_var = LiteralVar.create(
|
||||
[
|
||||
{"a": 1, "b": 2, "c": {"d": 3, "e": 4}},
|
||||
[1, 2, 3, 4],
|
||||
9,
|
||||
"string",
|
||||
True,
|
||||
False,
|
||||
None,
|
||||
set([1, 2, 3]),
|
||||
]
|
||||
)
|
||||
assert (
|
||||
str(complicated_var)
|
||||
== '[({ ["a"] : 1, ["b"] : 2, ["c"] : ({ ["d"] : 3, ["e"] : 4 }) }), [1, 2, 3, 4], 9, "string", true, false, null, [1, 2, 3]]'
|
||||
)
|
||||
|
||||
|
||||
def test_function_var():
|
||||
addition_func = FunctionStringVar("((a, b) => a + b)")
|
||||
assert str(addition_func.call(1, 2)) == "(((a, b) => a + b)(1, 2))"
|
||||
|
||||
manual_addition_func = ArgsFunctionOperation(
|
||||
("a", "b"),
|
||||
{
|
||||
"args": [ImmutableVar.create_safe("a"), ImmutableVar.create_safe("b")],
|
||||
"result": ImmutableVar.create_safe("a + b"),
|
||||
},
|
||||
)
|
||||
assert (
|
||||
str(manual_addition_func.call(1, 2))
|
||||
== '(((a, b) => (({ ["args"] : [a, b], ["result"] : a + b })))(1, 2))'
|
||||
)
|
||||
|
||||
increment_func = addition_func(1)
|
||||
assert (
|
||||
str(increment_func.call(2))
|
||||
== "(((...args) => ((((a, b) => a + b)(1, ...args))))(2))"
|
||||
)
|
||||
|
||||
create_hello_statement = ArgsFunctionOperation(
|
||||
("name",), f"Hello, {ImmutableVar.create_safe('name')}!"
|
||||
)
|
||||
first_name = LiteralStringVar("Steven")
|
||||
last_name = LiteralStringVar("Universe")
|
||||
assert (
|
||||
str(create_hello_statement.call(f"{first_name} {last_name}"))
|
||||
== '(((name) => (("Hello, "+name+"!")))(("Steven"+" "+"Universe")))'
|
||||
)
|
||||
|
||||
|
||||
def test_var_operation():
|
||||
@var_operation(output=NumberVar)
|
||||
def add(a: Union[NumberVar, int], b: Union[NumberVar, int]) -> str:
|
||||
return f"({a} + {b})"
|
||||
|
||||
assert str(add(1, 2)) == "(1 + 2)"
|
||||
assert str(add(a=4, b=-9)) == "(4 + -9)"
|
||||
|
||||
five = LiteralNumberVar(5)
|
||||
seven = add(2, five)
|
||||
|
||||
assert isinstance(seven, NumberVar)
|
||||
|
||||
|
||||
def test_string_operations():
|
||||
basic_string = LiteralStringVar.create("Hello, World!")
|
||||
|
||||
assert str(basic_string.length()) == '"Hello, World!".split("").length'
|
||||
assert str(basic_string.lower()) == '"Hello, World!".toLowerCase()'
|
||||
assert str(basic_string.upper()) == '"Hello, World!".toUpperCase()'
|
||||
assert str(basic_string.strip()) == '"Hello, World!".trim()'
|
||||
assert str(basic_string.contains("World")) == '"Hello, World!".includes("World")'
|
||||
assert (
|
||||
str(basic_string.split(" ").join(",")) == '"Hello, World!".split(" ").join(",")'
|
||||
)
|
||||
|
||||
|
||||
def test_all_number_operations():
|
||||
starting_number = LiteralNumberVar(-5.4)
|
||||
|
||||
complicated_number = (((-(starting_number + 1)) * 2 / 3) // 2 % 3) ** 2
|
||||
|
||||
assert (
|
||||
str(complicated_number)
|
||||
== "((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)"
|
||||
)
|
||||
|
||||
even_more_complicated_number = ~(
|
||||
abs(math.floor(complicated_number)) | 2 & 3 & round(complicated_number)
|
||||
)
|
||||
|
||||
assert (
|
||||
str(even_more_complicated_number)
|
||||
== "!(((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) != 0) || (true && (Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)) != 0))))"
|
||||
)
|
||||
|
||||
assert str(LiteralNumberVar(5) > False) == "(5 > 0)"
|
||||
assert str(LiteralBooleanVar(False) < 5) == "((false ? 1 : 0) < 5)"
|
||||
assert (
|
||||
str(LiteralBooleanVar(False) < LiteralBooleanVar(True))
|
||||
== "((false ? 1 : 0) < (true ? 1 : 0))"
|
||||
)
|
||||
|
||||
|
||||
def test_index_operation():
|
||||
array_var = LiteralArrayVar([1, 2, 3, 4, 5])
|
||||
assert str(array_var[0]) == "[1, 2, 3, 4, 5].at(0)"
|
||||
assert str(array_var[1:2]) == "[1, 2, 3, 4, 5].slice(1, 2)"
|
||||
assert (
|
||||
str(array_var[1:4:2])
|
||||
== "[1, 2, 3, 4, 5].slice(1, 4).filter((_, i) => i % 2 === 0)"
|
||||
)
|
||||
assert (
|
||||
str(array_var[::-1])
|
||||
== "[1, 2, 3, 4, 5].slice(0, [1, 2, 3, 4, 5].length).reverse().slice(undefined, undefined).filter((_, i) => i % 1 === 0)"
|
||||
)
|
||||
assert str(array_var.reverse()) == "[1, 2, 3, 4, 5].reverse()"
|
||||
assert str(array_var[0].to(NumberVar) + 9) == "([1, 2, 3, 4, 5].at(0) + 9)"
|
||||
|
||||
|
||||
def test_array_operations():
|
||||
array_var = LiteralArrayVar.create([1, 2, 3, 4, 5])
|
||||
|
||||
assert str(array_var.length()) == "[1, 2, 3, 4, 5].length"
|
||||
assert str(array_var.contains(3)) == "[1, 2, 3, 4, 5].includes(3)"
|
||||
assert str(array_var.reverse()) == "[1, 2, 3, 4, 5].reverse()"
|
||||
assert (
|
||||
str(ArrayVar.range(10))
|
||||
== "Array.from({ length: (10 - 0) / 1 }, (_, i) => 0 + i * 1)"
|
||||
)
|
||||
assert (
|
||||
str(ArrayVar.range(1, 10))
|
||||
== "Array.from({ length: (10 - 1) / 1 }, (_, i) => 1 + i * 1)"
|
||||
)
|
||||
assert (
|
||||
str(ArrayVar.range(1, 10, 2))
|
||||
== "Array.from({ length: (10 - 1) / 2 }, (_, i) => 1 + i * 2)"
|
||||
)
|
||||
assert (
|
||||
str(ArrayVar.range(1, 10, -1))
|
||||
== "Array.from({ length: (10 - 1) / -1 }, (_, i) => 1 + i * -1)"
|
||||
)
|
||||
|
||||
|
||||
def test_object_operations():
|
||||
object_var = LiteralObjectVar({"a": 1, "b": 2, "c": 3})
|
||||
|
||||
assert (
|
||||
str(object_var.keys()) == 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))'
|
||||
)
|
||||
assert (
|
||||
str(object_var.values())
|
||||
== 'Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))'
|
||||
)
|
||||
assert (
|
||||
str(object_var.entries())
|
||||
== 'Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))'
|
||||
)
|
||||
assert str(object_var.a) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]'
|
||||
assert str(object_var["a"]) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]'
|
||||
assert (
|
||||
str(object_var.merge(LiteralObjectVar({"c": 4, "d": 5})))
|
||||
== 'Object.assign(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }), ({ ["c"] : 4, ["d"] : 5 }))'
|
||||
)
|
||||
|
||||
|
||||
def test_type_chains():
|
||||
object_var = LiteralObjectVar({"a": 1, "b": 2, "c": 3})
|
||||
assert (object_var._key_type(), object_var._value_type()) == (str, int)
|
||||
assert (object_var.keys()._var_type, object_var.values()._var_type) == (
|
||||
List[str],
|
||||
List[int],
|
||||
)
|
||||
assert (
|
||||
str(object_var.keys()[0].upper()) # type: ignore
|
||||
== 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(0).toUpperCase()'
|
||||
)
|
||||
assert (
|
||||
str(object_var.entries()[1][1] - 1) # type: ignore
|
||||
== '(Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(1).at(1) - 1)'
|
||||
)
|
||||
assert (
|
||||
str(object_var["c"] + object_var["b"]) # type: ignore
|
||||
== '(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["c"] + ({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["b"])'
|
||||
)
|
||||
|
||||
|
||||
def test_nested_dict():
|
||||
arr = LiteralArrayVar([{"bar": ["foo", "bar"]}], List[Dict[str, List[str]]])
|
||||
|
||||
assert (
|
||||
str(arr[0]["bar"][0]) == '[({ ["bar"] : ["foo", "bar"] })].at(0)["bar"].at(0)'
|
||||
)
|
||||
|
||||
|
||||
def nested_base():
|
||||
class Boo(Base):
|
||||
foo: str
|
||||
bar: int
|
||||
|
||||
class Foo(Base):
|
||||
bar: Boo
|
||||
baz: int
|
||||
|
||||
parent_obj = LiteralVar.create(Foo(bar=Boo(foo="bar", bar=5), baz=5))
|
||||
|
||||
assert (
|
||||
str(parent_obj.bar.foo)
|
||||
== '({ ["bar"] : ({ ["foo"] : "bar", ["bar"] : 5 }), ["baz"] : 5 })["bar"]["foo"]'
|
||||
)
|
||||
|
||||
|
||||
def test_retrival():
|
||||
var_without_data = ImmutableVar.create("test")
|
||||
assert var_without_data is not None
|
||||
@ -931,7 +1159,7 @@ def test_fstring_concat():
|
||||
),
|
||||
)
|
||||
|
||||
assert str(string_concat) == '"foo"+imagination+"bar"+consequences+"baz"'
|
||||
assert str(string_concat) == '("foo"+imagination+"bar"+consequences+"baz")'
|
||||
assert isinstance(string_concat, ConcatVarOperation)
|
||||
assert string_concat._get_all_var_data() == ImmutableVarData(
|
||||
state="fear",
|
||||
|
@ -477,7 +477,7 @@ def test_format_match(
|
||||
events=[EventSpec(handler=EventHandler(fn=mock_event))],
|
||||
args_spec=lambda: [],
|
||||
),
|
||||
'{(_e) => addEvents([Event("mock_event", {})], (_e), {})}',
|
||||
'{(...args) => addEvents([Event("mock_event", {})], args, {})}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
@ -495,9 +495,9 @@ def test_format_match(
|
||||
),
|
||||
)
|
||||
],
|
||||
args_spec=lambda: [],
|
||||
args_spec=lambda e: [e.target.value],
|
||||
),
|
||||
'{(_e) => addEvents([Event("mock_event", {arg:_e.target.value})], (_e), {})}',
|
||||
'{(_e) => addEvents([Event("mock_event", {arg:_e.target.value})], [_e], {})}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
@ -505,7 +505,7 @@ def test_format_match(
|
||||
args_spec=lambda: [],
|
||||
event_actions={"stopPropagation": True},
|
||||
),
|
||||
'{(_e) => addEvents([Event("mock_event", {})], (_e), {"stopPropagation": true})}',
|
||||
'{(...args) => addEvents([Event("mock_event", {})], args, {"stopPropagation": true})}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
@ -513,7 +513,7 @@ def test_format_match(
|
||||
args_spec=lambda: [],
|
||||
event_actions={"preventDefault": True},
|
||||
),
|
||||
'{(_e) => addEvents([Event("mock_event", {})], (_e), {"preventDefault": true})}',
|
||||
'{(...args) => addEvents([Event("mock_event", {})], args, {"preventDefault": true})}',
|
||||
),
|
||||
({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
|
||||
(BaseVar(_var_name="var", _var_type="int"), "{var}"),
|
||||
|
Loading…
Reference in New Issue
Block a user