validate decorations dict

This commit is contained in:
Elijah 2024-10-24 23:08:12 +00:00
parent 6a67e8f417
commit ccaf2c65d4
6 changed files with 81 additions and 41 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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