Merge remote-tracking branch 'upstream/main' into minify-state-names-v2

This commit is contained in:
Benedikt Bartscher 2024-11-12 23:23:17 +01:00
commit 3dd6e9c8ad
No known key found for this signature in database
16 changed files with 209 additions and 83 deletions

View File

@ -25,7 +25,7 @@
# Stage 1: init # Stage 1: init
FROM python:3.11 as init FROM python:3.11 as init
ARG uv=/root/.cargo/bin/uv ARG uv=/root/.local/bin/uv
# Install `uv` for faster package boostrapping # Install `uv` for faster package boostrapping
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh

View File

@ -4,7 +4,7 @@
# Stage 1: init # Stage 1: init
FROM python:3.11 as init FROM python:3.11 as init
ARG uv=/root/.cargo/bin/uv ARG uv=/root/.local/bin/uv
# Install `uv` for faster package boostrapping # Install `uv` for faster package boostrapping
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh

6
poetry.lock generated
View File

@ -1350,8 +1350,8 @@ files = [
[package.dependencies] [package.dependencies]
numpy = [ numpy = [
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.22.4", markers = "python_version < \"3.11\""},
] ]
python-dateutil = ">=2.8.2" python-dateutil = ">=2.8.2"
@ -1669,8 +1669,8 @@ files = [
annotated-types = ">=0.6.0" annotated-types = ">=0.6.0"
pydantic-core = "2.23.4" pydantic-core = "2.23.4"
typing-extensions = [ typing-extensions = [
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
{version = ">=4.6.1", markers = "python_version < \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""},
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
] ]
[package.extras] [package.extras]
@ -3050,4 +3050,4 @@ type = ["pytest-mypy"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "593a52e9f54e95b50074f1bc4b7cdbabe4fab325051c72b23219268c0c9aa3ba" content-hash = "937f0cadb1a4566117dad8d0be6018ad1a8fe9aeb19c499d2a010d36ef391ee1"

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "reflex" name = "reflex"
version = "0.6.5dev1" version = "0.6.6dev1"
description = "Web apps in pure Python." description = "Web apps in pure Python."
license = "Apache-2.0" license = "Apache-2.0"
authors = [ authors = [
@ -49,7 +49,7 @@ wrapt = [
{version = ">=1.11.0,<2.0", python = "<3.11"}, {version = ">=1.11.0,<2.0", python = "<3.11"},
] ]
packaging = ">=23.1,<25.0" packaging = ">=23.1,<25.0"
reflex-hosting-cli = ">=0.1.5,<2.0" reflex-hosting-cli = ">=0.1.15,<2.0"
charset-normalizer = ">=3.3.2,<4.0" charset-normalizer = ">=3.3.2,<4.0"
wheel = ">=0.42.0,<1.0" wheel = ">=0.42.0,<1.0"
build = ">=1.0.3,<2.0" build = ">=1.0.3,<2.0"

View File

@ -298,6 +298,7 @@ _MAPPING: dict = {
"components.moment": ["MomentDelta", "moment"], "components.moment": ["MomentDelta", "moment"],
"config": ["Config", "DBConfig"], "config": ["Config", "DBConfig"],
"constants": ["Env"], "constants": ["Env"],
"constants.colors": ["Color"],
"event": [ "event": [
"EventChain", "EventChain",
"EventHandler", "EventHandler",
@ -338,7 +339,7 @@ _MAPPING: dict = {
], ],
"istate.wrappers": ["get_state"], "istate.wrappers": ["get_state"],
"style": ["Style", "toggle_color_mode"], "style": ["Style", "toggle_color_mode"],
"utils.imports": ["ImportVar"], "utils.imports": ["ImportDict", "ImportVar"],
"utils.serializers": ["serializer"], "utils.serializers": ["serializer"],
"vars": ["Var", "field", "Field"], "vars": ["Var", "field", "Field"],
} }

View File

@ -152,6 +152,7 @@ from .components.suneditor import editor as editor
from .config import Config as Config from .config import Config as Config
from .config import DBConfig as DBConfig from .config import DBConfig as DBConfig
from .constants import Env as Env from .constants import Env as Env
from .constants.colors import Color as Color
from .event import EventChain as EventChain from .event import EventChain as EventChain
from .event import EventHandler as EventHandler from .event import EventHandler as EventHandler
from .event import background as background from .event import background as background
@ -192,6 +193,7 @@ from .state import dynamic as dynamic
from .state import var as var from .state import var as var
from .style import Style as Style from .style import Style as Style
from .style import toggle_color_mode as toggle_color_mode from .style import toggle_color_mode as toggle_color_mode
from .utils.imports import ImportDict as ImportDict
from .utils.imports import ImportVar as ImportVar from .utils.imports import ImportVar as ImportVar
from .utils.serializers import serializer as serializer from .utils.serializers import serializer as serializer
from .vars import Field as Field from .vars import Field as Field

View File

@ -130,8 +130,8 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
Returns: Returns:
The value of the field. The value of the field.
""" """
if isinstance(key, str) and key in self.__fields__: if isinstance(key, str):
# Seems like this function signature was wrong all along? # Seems like this function signature was wrong all along?
# If the user wants a field that we know of, get it and pass it off to _get_value # If the user wants a field that we know of, get it and pass it off to _get_value
key = getattr(self, key) return getattr(self, key, key)
return key return key

View File

@ -2,14 +2,15 @@
from __future__ import annotations from __future__ import annotations
from typing import Dict, List, Tuple from typing import Dict, Tuple
from reflex.compiler.compiler import _compile_component
from reflex.components.component import Component from reflex.components.component import Component
from reflex.components.el import div, p from reflex.components.datadisplay.logo import svg_logo
from reflex.event import EventHandler from reflex.components.el import a, button, details, div, h2, hr, p, pre, summary
from reflex.event import EventHandler, set_clipboard
from reflex.state import FrontendEventExceptionState from reflex.state import FrontendEventExceptionState
from reflex.vars.base import Var from reflex.vars.base import Var
from reflex.vars.function import ArgsFunctionOperation
def on_error_spec( def on_error_spec(
@ -40,38 +41,7 @@ class ErrorBoundary(Component):
on_error: EventHandler[on_error_spec] on_error: EventHandler[on_error_spec]
# Rendered instead of the children when an error is caught. # Rendered instead of the children when an error is caught.
Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace( fallback_render: Var[Component]
_var_type=Component
)
def add_custom_code(self) -> List[str]:
"""Add custom Javascript code into the page that contains this component.
Custom code is inserted at module level, after any imports.
Returns:
The custom code to add.
"""
fallback_container = div(
p("Ooops...Unknown Reflex error has occured:"),
p(
Var(_js_expr="error.message"),
color="red",
),
p("Please contact the support."),
)
compiled_fallback = _compile_component(fallback_container)
return [
f"""
function Fallback({{ error, resetErrorBoundary }}) {{
return (
{compiled_fallback}
);
}}
"""
]
@classmethod @classmethod
def create(cls, *children, **props): def create(cls, *children, **props):
@ -86,6 +56,99 @@ class ErrorBoundary(Component):
""" """
if "on_error" not in props: if "on_error" not in props:
props["on_error"] = FrontendEventExceptionState.handle_frontend_exception props["on_error"] = FrontendEventExceptionState.handle_frontend_exception
if "fallback_render" not in props:
props["fallback_render"] = ArgsFunctionOperation.create(
("event_args",),
Var.create(
div(
div(
div(
h2(
"An error occurred while rendering this page.",
font_size="1.25rem",
font_weight="bold",
),
p(
"This is an error with the application itself.",
opacity="0.75",
),
details(
summary("Error message", padding="0.5rem"),
div(
div(
pre(
Var(
_js_expr="event_args.error.stack",
),
),
padding="0.5rem",
width="fit-content",
),
width="100%",
max_height="50vh",
overflow="auto",
background="#000",
color="#fff",
border_radius="0.25rem",
),
button(
"Copy",
on_click=set_clipboard(
Var(_js_expr="event_args.error.stack"),
),
padding="0.35rem 0.75rem",
margin="0.5rem",
background="#fff",
color="#000",
border="1px solid #000",
border_radius="0.25rem",
font_weight="bold",
),
),
display="flex",
flex_direction="column",
gap="1rem",
max_width="50ch",
border="1px solid #888888",
border_radius="0.25rem",
padding="1rem",
),
hr(
border_color="currentColor",
opacity="0.25",
),
a(
div(
"Built with ",
svg_logo("currentColor"),
display="flex",
align_items="baseline",
justify_content="center",
font_family="monospace",
gap="0.5rem",
),
href="https://reflex.dev",
),
display="flex",
flex_direction="column",
gap="1rem",
),
height="100%",
width="100%",
position="absolute",
display="flex",
align_items="center",
justify_content="center",
)
),
_var_type=Component,
)
else:
props["fallback_render"] = ArgsFunctionOperation.create(
("event_args",),
props["fallback_render"],
_var_type=Component,
)
return super().create(*children, **props) return super().create(*children, **props)

View File

@ -3,7 +3,7 @@
# ------------------- DO NOT EDIT ---------------------- # ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`! # This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------ # ------------------------------------------------------
from typing import Any, Dict, List, Optional, Tuple, Union, overload from typing import Any, Dict, Optional, Tuple, Union, overload
from reflex.components.component import Component from reflex.components.component import Component
from reflex.event import BASE_STATE, EventType from reflex.event import BASE_STATE, EventType
@ -15,13 +15,12 @@ def on_error_spec(
) -> Tuple[Var[str], Var[str]]: ... ) -> Tuple[Var[str], Var[str]]: ...
class ErrorBoundary(Component): class ErrorBoundary(Component):
def add_custom_code(self) -> List[str]: ...
@overload @overload
@classmethod @classmethod
def create( # type: ignore def create( # type: ignore
cls, cls,
*children, *children,
Fallback_component: Optional[Union[Component, Var[Component]]] = None, fallback_render: Optional[Union[Component, Var[Component]]] = None,
style: Optional[Style] = None, style: Optional[Style] = None,
key: Optional[Any] = None, key: Optional[Any] = None,
id: Optional[Any] = None, id: Optional[Any] = None,
@ -57,7 +56,7 @@ class ErrorBoundary(Component):
Args: Args:
*children: The children of the component. *children: The children of the component.
on_error: Fired when the boundary catches an error. on_error: Fired when the boundary catches an error.
Fallback_component: Rendered instead of the children when an error is caught. fallback_render: Rendered instead of the children when an error is caught.
style: The style of the component. style: The style of the component.
key: A unique key for the component. key: A unique key for the component.
id: The id for the component. id: The id for the component.

View File

@ -171,6 +171,14 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
) )
@overload
def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # type: ignore
@overload
def color_mode_cond(light: Any, dark: Any = None) -> Var: ...
def color_mode_cond(light: Any, dark: Any = None) -> Var | Component: def color_mode_cond(light: Any, dark: Any = None) -> Var | Component:
"""Create a component or Prop based on color_mode. """Create a component or Prop based on color_mode.

View File

@ -1,22 +1,23 @@
"""A Reflex logo component.""" """A Reflex logo component."""
from typing import Union
import reflex as rx import reflex as rx
def logo(**props): def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "white")):
"""A Reflex logo. """A Reflex logo SVG.
Args: Args:
**props: The props to pass to the component. color: The color of the logo.
Returns: Returns:
The logo component. The Reflex logo SVG.
""" """
def logo_path(d): def logo_path(d):
return rx.el.svg.path( return rx.el.svg.path(
d=d, d=d,
fill=rx.color_mode_cond("#110F1F", "white"),
) )
paths = [ paths = [
@ -28,18 +29,30 @@ def logo(**props):
"M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z", "M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z",
] ]
return rx.el.svg(
*[logo_path(d) for d in paths],
width="56",
height="12",
viewBox="0 0 56 12",
fill=color,
xmlns="http://www.w3.org/2000/svg",
)
def logo(**props):
"""A Reflex logo.
Args:
**props: The props to pass to the component.
Returns:
The logo component.
"""
return rx.center( return rx.center(
rx.link( rx.link(
rx.hstack( rx.hstack(
"Built with ", "Built with ",
rx.el.svg( svg_logo(),
*[logo_path(d) for d in paths],
width="56",
height="12",
viewBox="0 0 56 12",
fill="none",
xmlns="http://www.w3.org/2000/svg",
),
text_align="center", text_align="center",
align="center", align="center",
padding="1em", padding="1em",

View File

@ -112,6 +112,9 @@ class RadixThemesComponent(Component):
library = "@radix-ui/themes@^3.0.0" library = "@radix-ui/themes@^3.0.0"
# Temporary pin < 3.1.5 until radix-ui/themes#627 is resolved.
library = library + " && <3.1.5"
# "Fake" prop color_scheme is used to avoid shadowing CSS prop "color". # "Fake" prop color_scheme is used to avoid shadowing CSS prop "color".
_rename_props: Dict[str, str] = {"colorScheme": "color"} _rename_props: Dict[str, str] = {"colorScheme": "color"}

View File

@ -899,7 +899,7 @@ def remove_session_storage(key: str) -> EventSpec:
) )
def set_clipboard(content: str) -> EventSpec: def set_clipboard(content: Union[str, Var[str]]) -> EventSpec:
"""Set the text in content in the clipboard. """Set the text in content in the clipboard.
Args: Args:

View File

@ -46,6 +46,7 @@ from reflex import event
from reflex.config import get_config from reflex.config import get_config
from reflex.istate.data import RouterData from reflex.istate.data import RouterData
from reflex.istate.storage import ClientStorageBase from reflex.istate.storage import ClientStorageBase
from reflex.model import Model
from reflex.vars.base import ( from reflex.vars.base import (
ComputedVar, ComputedVar,
DynamicRouteVar, DynamicRouteVar,
@ -1791,15 +1792,20 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
if value is None: if value is None:
continue continue
hinted_args = value_inside_optional(hinted_args) hinted_args = value_inside_optional(hinted_args)
if ( if isinstance(value, dict) and inspect.isclass(hinted_args):
isinstance(value, dict) if issubclass(hinted_args, Model):
and inspect.isclass(hinted_args) # Remove non-fields from the payload
and ( payload[arg] = hinted_args(
dataclasses.is_dataclass(hinted_args) **{
or issubclass(hinted_args, Base) key: value
) for key, value in value.items()
): if key in hinted_args.__fields__
payload[arg] = hinted_args(**value) }
)
elif dataclasses.is_dataclass(hinted_args) or issubclass(
hinted_args, Base
):
payload[arg] = hinted_args(**value)
if isinstance(value, list) and (hinted_args is set or hinted_args is Set): if isinstance(value, list) and (hinted_args is set or hinted_args is Set):
payload[arg] = set(value) payload[arg] = set(value)
if isinstance(value, list) and ( if isinstance(value, list) and (
@ -1942,7 +1948,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
) )
subdelta: Dict[str, Any] = { subdelta: Dict[str, Any] = {
prop: self.get_value(getattr(self, prop)) prop: self.get_value(prop)
for prop in delta_vars for prop in delta_vars
if not types.is_backend_base_variable(prop, type(self)) if not types.is_backend_base_variable(prop, type(self))
} }
@ -2034,9 +2040,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Returns: Returns:
The value of the field. The value of the field.
""" """
if isinstance(key, MutableProxy): value = super().get_value(key)
return super().get_value(key.__wrapped__) if isinstance(value, MutableProxy):
return super().get_value(key) return value.__wrapped__
return value
def dict( def dict(
self, include_computed: bool = True, initial: bool = False, **kwargs self, include_computed: bool = True, initial: bool = False, **kwargs
@ -2058,8 +2065,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
self._mark_dirty() self._mark_dirty()
base_vars = { base_vars = {
prop_name: self.get_value(getattr(self, prop_name)) prop_name: self.get_value(prop_name) for prop_name in self.base_vars
for prop_name in self.base_vars
} }
if initial and include_computed: if initial and include_computed:
computed_vars = { computed_vars = {
@ -2068,7 +2074,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
cv._initial_value cv._initial_value
if is_computed_var(cv) if is_computed_var(cv)
and not isinstance(cv._initial_value, types.Unset) and not isinstance(cv._initial_value, types.Unset)
else self.get_value(getattr(self, prop_name)) else self.get_value(prop_name)
) )
for prop_name, cv in self.computed_vars.items() for prop_name, cv in self.computed_vars.items()
if not cv._backend if not cv._backend
@ -2076,7 +2082,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
elif include_computed: elif include_computed:
computed_vars = { computed_vars = {
# Include the computed vars. # Include the computed vars.
prop_name: self.get_value(getattr(self, prop_name)) prop_name: self.get_value(prop_name)
for prop_name, cv in self.computed_vars.items() for prop_name, cv in self.computed_vars.items()
if not cv._backend if not cv._backend
} }

View File

@ -4,6 +4,7 @@ from reflex.components.core.banner import (
ConnectionPulser, ConnectionPulser,
WebsocketTargetURL, WebsocketTargetURL,
) )
from reflex.components.radix.themes.base import RadixThemesComponent
from reflex.components.radix.themes.typography.text import Text from reflex.components.radix.themes.typography.text import Text
@ -24,7 +25,7 @@ def test_connection_banner():
"react", "react",
"$/utils/context", "$/utils/context",
"$/utils/state", "$/utils/state",
"@radix-ui/themes@^3.0.0", RadixThemesComponent().library or "",
"$/env.json", "$/env.json",
) )
) )
@ -42,7 +43,7 @@ def test_connection_modal():
"react", "react",
"$/utils/context", "$/utils/context",
"$/utils/state", "$/utils/state",
"@radix-ui/themes@^3.0.0", RadixThemesComponent().library or "",
"$/env.json", "$/env.json",
) )
) )

View File

@ -3412,3 +3412,33 @@ def test_typed_state() -> None:
field: rx.Field[str] = rx.field("") field: rx.Field[str] = rx.field("")
_ = TypedState(field="str") _ = TypedState(field="str")
def test_get_value():
class GetValueState(rx.State):
foo: str = "FOO"
bar: str = "BAR"
state = GetValueState()
assert state.dict() == {
state.get_full_name(): {
"foo": "FOO",
"bar": "BAR",
}
}
assert state.get_delta() == {}
state.bar = "foo"
assert state.dict() == {
state.get_full_name(): {
"foo": "FOO",
"bar": "foo",
}
}
assert state.get_delta() == {
state.get_full_name(): {
"bar": "foo",
}
}