validate decorations dict
This commit is contained in:
parent
6a67e8f417
commit
ccaf2c65d4
@ -12,6 +12,7 @@ from reflex.components.core.colors import color
|
||||
from reflex.components.core.cond import color_mode_cond
|
||||
from reflex.components.el.elements.forms import Button
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.props import NoExtrasAllowedProps
|
||||
from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.event import call_script, set_clipboard
|
||||
from reflex.style import Style
|
||||
@ -390,6 +391,23 @@ LiteralCodeTheme = Literal[
|
||||
]
|
||||
|
||||
|
||||
class Position(NoExtrasAllowedProps):
|
||||
"""Position of the decoration."""
|
||||
|
||||
line: str
|
||||
character: str
|
||||
|
||||
|
||||
class ShikiDecorations(NoExtrasAllowedProps):
|
||||
"""Decorations for the code block."""
|
||||
|
||||
start: Union[int, Position]
|
||||
end: Union[int, Position]
|
||||
tag_name: str = "span"
|
||||
properties: dict[str, Any] = {}
|
||||
always_wrap: bool = False
|
||||
|
||||
|
||||
class ShikiBaseTransformers(Base):
|
||||
"""Base for creating transformers."""
|
||||
|
||||
@ -538,7 +556,7 @@ class ShikiCodeBlock(Component):
|
||||
)
|
||||
|
||||
# The decorations to use for the syntax highlighter.
|
||||
decorations: Var[list[dict[str, Any]]] = Var.create([])
|
||||
decorations: Var[list[ShikiDecorations]] = Var.create([])
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
@ -558,6 +576,7 @@ class ShikiCodeBlock(Component):
|
||||
# Separate props for the code block and the wrapper
|
||||
code_block_props = {}
|
||||
code_wrapper_props = {}
|
||||
decorations = props.pop("decorations", [])
|
||||
|
||||
class_props = cls.get_props()
|
||||
|
||||
@ -577,6 +596,9 @@ class ShikiCodeBlock(Component):
|
||||
transformer_styles.update(transformer.style)
|
||||
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
||||
|
||||
decorations = [ShikiDecorations(**decoration) for decoration in decorations]
|
||||
code_block_props["decorations"] = decorations
|
||||
|
||||
return Box.create(
|
||||
code_block,
|
||||
*children[1:],
|
||||
|
@ -7,6 +7,7 @@ from typing import Any, Dict, Literal, Optional, Union, overload
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
from reflex.components.props import NoExtrasAllowedProps
|
||||
from reflex.event import EventType
|
||||
from reflex.style import Style
|
||||
from reflex.vars.base import Var
|
||||
@ -328,6 +329,17 @@ LiteralCodeTheme = Literal[
|
||||
"vitesse-light",
|
||||
]
|
||||
|
||||
class Position(NoExtrasAllowedProps):
|
||||
line: str
|
||||
character: str
|
||||
|
||||
class ShikiDecorations(NoExtrasAllowedProps):
|
||||
start: Union[int, Position]
|
||||
end: Union[int, Position]
|
||||
tag_name: str
|
||||
properties: dict[str, Any]
|
||||
always_wrap: bool
|
||||
|
||||
class ShikiBaseTransformers(Base):
|
||||
library: str
|
||||
fns: list[FunctionStringVar]
|
||||
@ -907,7 +919,7 @@ class ShikiCodeBlock(Component):
|
||||
]
|
||||
] = None,
|
||||
decorations: Optional[
|
||||
Union[Var[list[dict[str, Any]]], list[dict[str, Any]]]
|
||||
Union[Var[list[ShikiDecorations]], list[ShikiDecorations]]
|
||||
] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
@ -1534,7 +1546,7 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
|
||||
]
|
||||
] = None,
|
||||
decorations: Optional[
|
||||
Union[Var[list[dict[str, Any]]], list[dict[str, Any]]]
|
||||
Union[Var[list[ShikiDecorations]], list[ShikiDecorations]]
|
||||
] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
@ -2164,7 +2176,7 @@ class CodeblockNamespace(ComponentNamespace):
|
||||
]
|
||||
] = None,
|
||||
decorations: Optional[
|
||||
Union[Var[list[dict[str, Any]]], list[dict[str, Any]]]
|
||||
Union[Var[list[ShikiDecorations]], list[ShikiDecorations]]
|
||||
] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
|
@ -2,8 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.utils import format
|
||||
from reflex.utils.exceptions import InvalidPropValueError
|
||||
from reflex.vars.object import LiteralObjectVar
|
||||
|
||||
|
||||
@ -40,3 +43,34 @@ class PropsBase(Base):
|
||||
format.to_camel_case(key): value
|
||||
for key, value in super().dict(*args, **kwargs).items()
|
||||
}
|
||||
|
||||
|
||||
class NoExtrasAllowedProps(Base):
|
||||
"""A class that holds props to be passed or applied to a component with no extra props allowed."""
|
||||
|
||||
def __init__(self, component_name=None, **kwargs):
|
||||
"""Initialize the props.
|
||||
|
||||
Args:
|
||||
component_name: The custom name of the component.
|
||||
kwargs: Kwargs to initialize the props.
|
||||
|
||||
Raises:
|
||||
InvalidPropValueError: If invalid props are passed on instantiation.
|
||||
"""
|
||||
component_name = component_name or type(self).__name__
|
||||
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 InvalidPropValueError(
|
||||
f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}"
|
||||
) from None
|
||||
|
||||
class Config:
|
||||
"""Pydantic config."""
|
||||
|
||||
arbitrary_types_allowed = True
|
||||
use_enum_values = True
|
||||
extra = "forbid"
|
||||
|
@ -4,12 +4,10 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any, ClassVar, Literal, Optional, Union
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.props import PropsBase
|
||||
from reflex.components.props import NoExtrasAllowedProps, PropsBase
|
||||
from reflex.event import (
|
||||
EventSpec,
|
||||
call_script,
|
||||
@ -72,7 +70,7 @@ def _toast_callback_signature(toast: Var) -> list[Var]:
|
||||
]
|
||||
|
||||
|
||||
class ToastProps(PropsBase):
|
||||
class ToastProps(PropsBase, NoExtrasAllowedProps):
|
||||
"""Props for the toast component."""
|
||||
|
||||
# Toast's title, renders above the description.
|
||||
@ -132,24 +130,6 @@ 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.
|
||||
|
||||
@ -181,13 +161,6 @@ 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."""
|
||||
@ -281,7 +254,7 @@ class Toaster(Component):
|
||||
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 = LiteralVar.create(ToastProps(**props))
|
||||
args = LiteralVar.create(ToastProps(component_name="rx.toast", **props))
|
||||
toast = f"{toast_command}(`{message}`, {str(args)})"
|
||||
else:
|
||||
toast = f"{toast_command}(`{message}`)"
|
||||
|
@ -8,7 +8,7 @@ from typing import Any, ClassVar, Dict, Literal, Optional, Union, overload
|
||||
from reflex.base import Base
|
||||
from reflex.components.component import Component, ComponentNamespace
|
||||
from reflex.components.lucide.icon import Icon
|
||||
from reflex.components.props import PropsBase
|
||||
from reflex.components.props import NoExtrasAllowedProps, PropsBase
|
||||
from reflex.event import EventSpec, EventType
|
||||
from reflex.style import Style
|
||||
from reflex.utils.serializers import serializer
|
||||
@ -31,7 +31,7 @@ class ToastAction(Base):
|
||||
@serializer
|
||||
def serialize_action(action: ToastAction) -> dict: ...
|
||||
|
||||
class ToastProps(PropsBase):
|
||||
class ToastProps(PropsBase, NoExtrasAllowedProps):
|
||||
title: Optional[Union[str, Var]]
|
||||
description: Optional[Union[str, Var]]
|
||||
close_button: Optional[bool]
|
||||
@ -52,11 +52,6 @@ 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
|
||||
|
||||
|
@ -143,3 +143,7 @@ class EnvironmentVarValueError(ReflexError, ValueError):
|
||||
|
||||
class DynamicComponentInvalidSignature(ReflexError, TypeError):
|
||||
"""Raised when a dynamic component has an invalid signature."""
|
||||
|
||||
|
||||
class InvalidPropValueError(ReflexError, ValueError):
|
||||
"""Raised when a prop value is invalid."""
|
||||
|
Loading…
Reference in New Issue
Block a user