Merge remote-tracking branch 'upstream/main' into poc_shared_state

This commit is contained in:
Andreas Eismann 2024-08-16 22:08:40 +02:00
commit 98dcec5177
No known key found for this signature in database
49 changed files with 6256 additions and 348 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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 = [

View File

@ -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

View File

@ -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 })
}

View File

@ -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.

View File

@ -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,

View File

@ -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."""

View File

@ -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.
"""
...

View File

@ -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__,

View File

@ -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

View File

@ -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,

View File

@ -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"]

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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.

View File

@ -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 ''}",
}
)
)

View File

@ -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",
}

View File

@ -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(

View File

@ -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.

View File

@ -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

View File

@ -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):

View File

@ -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.
"""

View File

@ -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."""

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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)

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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(

View File

@ -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."

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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
View 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
View 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)

View File

@ -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,
)

View File

@ -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,

View File

@ -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"]
)

View File

@ -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], {})}"
)

View File

@ -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

View File

@ -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",

View File

@ -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}"),