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
FROM python:3.11 as init
ARG uv=/root/.cargo/bin/uv
ARG uv=/root/.local/bin/uv
# Install `uv` for faster package boostrapping
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh

View File

@ -4,7 +4,7 @@
# Stage 1: 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
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh

6
poetry.lock generated
View File

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

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "reflex"
version = "0.6.5dev1"
version = "0.6.6dev1"
description = "Web apps in pure Python."
license = "Apache-2.0"
authors = [
@ -49,7 +49,7 @@ wrapt = [
{version = ">=1.11.0,<2.0", python = "<3.11"},
]
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"
wheel = ">=0.42.0,<1.0"
build = ">=1.0.3,<2.0"

View File

@ -298,6 +298,7 @@ _MAPPING: dict = {
"components.moment": ["MomentDelta", "moment"],
"config": ["Config", "DBConfig"],
"constants": ["Env"],
"constants.colors": ["Color"],
"event": [
"EventChain",
"EventHandler",
@ -338,7 +339,7 @@ _MAPPING: dict = {
],
"istate.wrappers": ["get_state"],
"style": ["Style", "toggle_color_mode"],
"utils.imports": ["ImportVar"],
"utils.imports": ["ImportDict", "ImportVar"],
"utils.serializers": ["serializer"],
"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 DBConfig as DBConfig
from .constants import Env as Env
from .constants.colors import Color as Color
from .event import EventChain as EventChain
from .event import EventHandler as EventHandler
from .event import background as background
@ -192,6 +193,7 @@ from .state import dynamic as dynamic
from .state import var as var
from .style import Style as Style
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.serializers import serializer as serializer
from .vars import Field as Field

View File

@ -130,8 +130,8 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
Returns:
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?
# 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

View File

@ -2,14 +2,15 @@
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.el import div, p
from reflex.event import EventHandler
from reflex.components.datadisplay.logo import svg_logo
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.vars.base import Var
from reflex.vars.function import ArgsFunctionOperation
def on_error_spec(
@ -40,38 +41,7 @@ class ErrorBoundary(Component):
on_error: EventHandler[on_error_spec]
# Rendered instead of the children when an error is caught.
Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace(
_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}
);
}}
"""
]
fallback_render: Var[Component]
@classmethod
def create(cls, *children, **props):
@ -86,6 +56,99 @@ class ErrorBoundary(Component):
"""
if "on_error" not in props:
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)

View File

@ -3,7 +3,7 @@
# ------------------- DO NOT EDIT ----------------------
# 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.event import BASE_STATE, EventType
@ -15,13 +15,12 @@ def on_error_spec(
) -> Tuple[Var[str], Var[str]]: ...
class ErrorBoundary(Component):
def add_custom_code(self) -> List[str]: ...
@overload
@classmethod
def create( # type: ignore
cls,
*children,
Fallback_component: Optional[Union[Component, Var[Component]]] = None,
fallback_render: Optional[Union[Component, Var[Component]]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
@ -57,7 +56,7 @@ class ErrorBoundary(Component):
Args:
*children: The children of the component.
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.
key: A unique key 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:
"""Create a component or Prop based on color_mode.

View File

@ -1,22 +1,23 @@
"""A Reflex logo component."""
from typing import Union
import reflex as rx
def logo(**props):
"""A Reflex logo.
def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "white")):
"""A Reflex logo SVG.
Args:
**props: The props to pass to the component.
color: The color of the logo.
Returns:
The logo component.
The Reflex logo SVG.
"""
def logo_path(d):
return rx.el.svg.path(
d=d,
fill=rx.color_mode_cond("#110F1F", "white"),
)
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",
]
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(
rx.link(
rx.hstack(
"Built with ",
rx.el.svg(
*[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",
),
svg_logo(),
text_align="center",
align="center",
padding="1em",

View File

@ -112,6 +112,9 @@ class RadixThemesComponent(Component):
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".
_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.
Args:

View File

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

View File

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

View File

@ -3412,3 +3412,33 @@ def test_typed_state() -> None:
field: rx.Field[str] = rx.field("")
_ = 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",
}
}