Merge remote-tracking branch 'upstream/main' into minify-state-names-v2
This commit is contained in:
commit
3dd6e9c8ad
@ -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
|
||||
|
@ -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
6
poetry.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"],
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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"}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
)
|
||||
)
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user