Browse Source

remove pydantic as base class of component (#5289)

* remove pydantic as base class of component

* remove deprecation

* fix things with components

* don't post init twice

* do the new guy

* keep that one as is

* fix unit tests

* invert bases

* precommit

* write that list comp as dict

* mro is weird

* add outer type and type for a bit of compatibility

* do not think about this one, __pydantic_validate_values__  is the guy who determines subfield validation

* maybe

* META

* a bit simpler

* make _ as not js

* add field_specifiers
Khaleel Al-Adhami 1 week ago
parent
commit
854ccd09aa

+ 1 - 1
pyi_hashes.json

@@ -24,7 +24,7 @@
   "reflex/components/datadisplay/__init__.pyi": "cf087efa8b3960decc6b231cc986cfa9",
   "reflex/components/datadisplay/code.pyi": "651fc3d417b998eb1c3d072328f505d0",
   "reflex/components/datadisplay/dataeditor.pyi": "601c59f3ced6ab94fcf5527b90472a4f",
-  "reflex/components/datadisplay/shiki_code_block.pyi": "9aa77c0834d2eea2c1693f01971fb244",
+  "reflex/components/datadisplay/shiki_code_block.pyi": "ac16fd6c23eef7ce0185437ecf2d529d",
   "reflex/components/el/__init__.pyi": "09042a2db5e0637e99b5173430600522",
   "reflex/components/el/element.pyi": "323cfb5d67d8ccb58ac36c7cc7641dc3",
   "reflex/components/el/elements/__init__.pyi": "280ed457675f3720e34b560a3f617739",

+ 1 - 1
reflex/app.py

@@ -1429,7 +1429,7 @@ class App(MiddlewareMixin, LifespanMixin):
         )
         if self.theme is not None:
             # Fix #2992 by removing the top-level appearance prop
-            self.theme.appearance = None
+            self.theme.appearance = None  # pyright: ignore[reportAttributeAccessIssue]
         progress.advance(task)
 
         # Compile the app root.

+ 1 - 1
reflex/compiler/utils.py

@@ -309,7 +309,7 @@ def compile_custom_component(
         A tuple of the compiled component and the imports required by the component.
     """
     # Render the component.
-    render = component.get_component(component)
+    render = component.get_component()
 
     # Get the imports.
     imports: ParsedImportDict = {

+ 352 - 66
reflex/components/component.py

@@ -7,19 +7,32 @@ import copy
 import dataclasses
 import functools
 import inspect
+import sys
 import typing
-from abc import ABC, abstractmethod
+from abc import ABC, ABCMeta, abstractmethod
 from collections.abc import Callable, Iterator, Mapping, Sequence
+from dataclasses import _MISSING_TYPE, MISSING
 from functools import wraps
 from hashlib import md5
 from types import SimpleNamespace
-from typing import Any, ClassVar, TypeVar, cast, get_args, get_origin
+from typing import (
+    TYPE_CHECKING,
+    Annotated,
+    Any,
+    ClassVar,
+    ForwardRef,
+    Generic,
+    TypeVar,
+    _eval_type,  # pyright: ignore [reportAttributeAccessIssue]
+    cast,
+    get_args,
+    get_origin,
+)
 
-import pydantic.v1
 from rich.markup import escape
+from typing_extensions import dataclass_transform
 
 import reflex.state
-from reflex.base import Base
 from reflex.compiler.templates import STATEFUL_COMPONENT
 from reflex.components.core.breakpoints import Breakpoints
 from reflex.components.dynamic import load_dynamic_serializer
@@ -62,26 +75,311 @@ from reflex.vars.object import ObjectVar
 from reflex.vars.sequence import LiteralArrayVar, LiteralStringVar, StringVar
 
 
-class BaseComponent(Base, ABC):
+def resolve_annotations(
+    raw_annotations: Mapping[str, type[Any]], module_name: str | None
+) -> dict[str, type[Any]]:
+    """Partially taken from typing.get_type_hints.
+
+    Resolve string or ForwardRef annotations into type objects if possible.
+
+    Args:
+        raw_annotations: The raw annotations to resolve.
+        module_name: The name of the module.
+
+    Returns:
+        The resolved annotations.
+    """
+    module = sys.modules.get(module_name, None) if module_name is not None else None
+
+    base_globals: dict[str, Any] | None = (
+        module.__dict__ if module is not None else None
+    )
+
+    annotations = {}
+    for name, value in raw_annotations.items():
+        if isinstance(value, str):
+            if sys.version_info == (3, 10, 0):
+                value = ForwardRef(value, is_argument=False)
+            else:
+                value = ForwardRef(value, is_argument=False, is_class=True)
+        try:
+            if sys.version_info >= (3, 13):
+                value = _eval_type(value, base_globals, None, type_params=())
+            else:
+                value = _eval_type(value, base_globals, None)
+        except NameError:
+            # this is ok, it can be fixed with update_forward_refs
+            pass
+        annotations[name] = value
+    return annotations
+
+
+FIELD_TYPE = TypeVar("FIELD_TYPE")
+
+
+class ComponentField(Generic[FIELD_TYPE]):
+    """A field for a component."""
+
+    def __init__(
+        self,
+        default: FIELD_TYPE | _MISSING_TYPE = MISSING,
+        default_factory: Callable[[], FIELD_TYPE] | None = None,
+        is_javascript: bool | None = None,
+        annotated_type: type[Any] | _MISSING_TYPE = MISSING,
+    ) -> None:
+        """Initialize the field.
+
+        Args:
+            default: The default value for the field.
+            default_factory: The default factory for the field.
+            is_javascript: Whether the field is a javascript property.
+            annotated_type: The annotated type for the field.
+        """
+        self.default = default
+        self.default_factory = default_factory
+        self.is_javascript = is_javascript
+        self.outer_type_ = self.annotated_type = annotated_type
+        type_origin = get_origin(annotated_type) or annotated_type
+        if type_origin is Annotated:
+            type_origin = annotated_type.__origin__  # pyright: ignore [reportAttributeAccessIssue]
+        self.type_ = self.type_origin = type_origin
+
+    def default_value(self) -> FIELD_TYPE:
+        """Get the default value for the field.
+
+        Returns:
+            The default value for the field.
+
+        Raises:
+            ValueError: If no default value or factory is provided.
+        """
+        if self.default is not MISSING:
+            return self.default
+        if self.default_factory is not None:
+            return self.default_factory()
+        raise ValueError("No default value or factory provided.")
+
+    def __repr__(self) -> str:
+        """Represent the field in a readable format.
+
+        Returns:
+            The string representation of the field.
+        """
+        annotated_type_str = (
+            f", annotated_type={self.annotated_type!r}"
+            if self.annotated_type is not MISSING
+            else ""
+        )
+        if self.default is not MISSING:
+            return f"ComponentField(default={self.default!r}, is_javascript={self.is_javascript!r}{annotated_type_str})"
+        return f"ComponentField(default_factory={self.default_factory!r}, is_javascript={self.is_javascript!r}{annotated_type_str})"
+
+
+def field(
+    default: FIELD_TYPE | _MISSING_TYPE = MISSING,
+    default_factory: Callable[[], FIELD_TYPE] | None = None,
+    is_javascript_property: bool | None = None,
+) -> FIELD_TYPE:
+    """Create a field for a component.
+
+    Args:
+        default: The default value for the field.
+        default_factory: The default factory for the field.
+        is_javascript_property: Whether the field is a javascript property.
+
+    Returns:
+        The field for the component.
+
+    Raises:
+        ValueError: If both default and default_factory are specified.
+    """
+    if default is not MISSING and default_factory is not None:
+        raise ValueError("cannot specify both default and default_factory")
+    return ComponentField(  # pyright: ignore [reportReturnType]
+        default=default,
+        default_factory=default_factory,
+        is_javascript=is_javascript_property,
+    )
+
+
+@dataclass_transform(kw_only_default=True, field_specifiers=(field,))
+class BaseComponentMeta(ABCMeta):
+    """Meta class for BaseComponent."""
+
+    if TYPE_CHECKING:
+        _inherited_fields: Mapping[str, ComponentField]
+        _own_fields: Mapping[str, ComponentField]
+        _fields: Mapping[str, ComponentField]
+        _js_fields: Mapping[str, ComponentField]
+
+    def __new__(cls, name: str, bases: tuple[type], namespace: dict[str, Any]) -> type:
+        """Create a new class.
+
+        Args:
+            name: The name of the class.
+            bases: The bases of the class.
+            namespace: The namespace of the class.
+
+        Returns:
+            The new class.
+        """
+        # Add the field to the class
+        inherited_fields: dict[str, ComponentField] = {}
+        own_fields: dict[str, ComponentField] = {}
+        resolved_annotations = resolve_annotations(
+            namespace.get("__annotations__", {}), namespace["__module__"]
+        )
+
+        for base in bases[::-1]:
+            if hasattr(base, "_inherited_fields"):
+                inherited_fields.update(base._inherited_fields)
+        for base in bases[::-1]:
+            if hasattr(base, "_own_fields"):
+                inherited_fields.update(base._own_fields)
+
+        for key, value, inherited_field in [
+            (key, value, inherited_field)
+            for key, value in namespace.items()
+            if key not in resolved_annotations
+            and ((inherited_field := inherited_fields.get(key)) is not None)
+        ]:
+            new_value = ComponentField(
+                default=value,
+                is_javascript=inherited_field.is_javascript,
+                annotated_type=inherited_field.annotated_type,
+            )
+
+            own_fields[key] = new_value
+
+        for key, annotation in resolved_annotations.items():
+            value = namespace.get(key, MISSING)
+
+            if types.is_classvar(annotation):
+                # If the annotation is a classvar, skip it.
+                continue
+
+            if value is MISSING:
+                value = ComponentField(
+                    default=None,
+                    is_javascript=(key[0] != "_"),
+                    annotated_type=annotation,
+                )
+            elif not isinstance(value, ComponentField):
+                value = ComponentField(
+                    default=value,
+                    is_javascript=(
+                        (key[0] != "_")
+                        if (existing_field := inherited_fields.get(key)) is None
+                        else existing_field.is_javascript
+                    ),
+                    annotated_type=annotation,
+                )
+            else:
+                value = ComponentField(
+                    default=value.default,
+                    default_factory=value.default_factory,
+                    is_javascript=value.is_javascript,
+                    annotated_type=annotation,
+                )
+
+            own_fields[key] = value
+
+        namespace["_own_fields"] = own_fields
+        namespace["_inherited_fields"] = inherited_fields
+        namespace["_fields"] = inherited_fields | own_fields
+        namespace["_js_fields"] = {
+            key: value
+            for key, value in own_fields.items()
+            if value.is_javascript is True
+        }
+        return super().__new__(cls, name, bases, namespace)
+
+
+class BaseComponent(metaclass=BaseComponentMeta):
     """The base class for all Reflex components.
 
     This is something that can be rendered as a Component via the Reflex compiler.
     """
 
     # The children nested within the component.
-    children: list[BaseComponent] = pydantic.v1.Field(default_factory=list)
+    children: list[BaseComponent] = field(
+        default_factory=list, is_javascript_property=False
+    )
 
     # The library that the component is based on.
-    library: str | None = pydantic.v1.Field(default_factory=lambda: None)
+    library: str | None = field(default=None, is_javascript_property=False)
 
     # List here the non-react dependency needed by `library`
-    lib_dependencies: list[str] = pydantic.v1.Field(default_factory=list)
+    lib_dependencies: list[str] = field(
+        default_factory=list, is_javascript_property=False
+    )
 
     # List here the dependencies that need to be transpiled by Next.js
-    transpile_packages: list[str] = pydantic.v1.Field(default_factory=list)
+    transpile_packages: list[str] = field(
+        default_factory=list, is_javascript_property=False
+    )
 
     # The tag to use when rendering the component.
-    tag: str | None = pydantic.v1.Field(default_factory=lambda: None)
+    tag: str | None = field(default=None, is_javascript_property=False)
+
+    def __init__(
+        self,
+        **kwargs,
+    ):
+        """Initialize the component.
+
+        Args:
+            **kwargs: The kwargs to pass to the component.
+        """
+        for key, value in kwargs.items():
+            setattr(self, key, value)
+        for name, value in self.get_fields().items():
+            if name not in kwargs:
+                setattr(self, name, value.default_value())
+
+    def set(self, **kwargs):
+        """Set the component props.
+
+        Args:
+            **kwargs: The kwargs to set.
+
+        Returns:
+            The component with the updated props.
+        """
+        for key, value in kwargs.items():
+            setattr(self, key, value)
+        return self
+
+    def __eq__(self, value: Any) -> bool:
+        """Check if the component is equal to another value.
+
+        Args:
+            value: The value to compare to.
+
+        Returns:
+            Whether the component is equal to the value.
+        """
+        return type(self) is type(value) and bool(
+            getattr(self, key) == getattr(value, key) for key in self.get_fields()
+        )
+
+    @classmethod
+    def get_fields(cls) -> Mapping[str, ComponentField]:
+        """Get the fields of the component.
+
+        Returns:
+            The fields of the component.
+        """
+        return cls._fields
+
+    @classmethod
+    def get_js_fields(cls) -> Mapping[str, ComponentField]:
+        """Get the javascript fields of the component.
+
+        Returns:
+            The javascript fields of the component.
+        """
+        return cls._js_fields
 
     @abstractmethod
     def render(self) -> dict:
@@ -258,39 +556,39 @@ class Component(BaseComponent, ABC):
     """A component with style, event trigger and other props."""
 
     # The style of the component.
-    style: Style = pydantic.v1.Field(default_factory=Style)
+    style: Style = field(default_factory=Style, is_javascript_property=False)
 
     # A mapping from event triggers to event chains.
-    event_triggers: dict[str, EventChain | Var] = pydantic.v1.Field(
-        default_factory=dict
+    event_triggers: dict[str, EventChain | Var] = field(
+        default_factory=dict, is_javascript_property=False
     )
 
     # The alias for the tag.
-    alias: str | None = pydantic.v1.Field(default_factory=lambda: None)
+    alias: str | None = field(default=None, is_javascript_property=False)
 
     # Whether the component is a global scope tag. True for tags like `html`, `head`, `body`.
     _is_tag_in_global_scope: ClassVar[bool] = False
 
     # Whether the import is default or named.
-    is_default: bool | None = pydantic.v1.Field(default_factory=lambda: False)
+    is_default: bool | None = field(default=False, is_javascript_property=False)
 
     # A unique key for the component.
-    key: Any = pydantic.v1.Field(default_factory=lambda: None)
+    key: Any = field(default=None, is_javascript_property=False)
 
     # The id for the component.
-    id: Any = pydantic.v1.Field(default_factory=lambda: None)
+    id: Any = field(default=None, is_javascript_property=False)
 
     # The Var to pass as the ref to the component.
-    ref: Var | None = pydantic.v1.Field(default_factory=lambda: None)
+    ref: Var | None = field(default=None, is_javascript_property=False)
 
     # The class name for the component.
-    class_name: Any = pydantic.v1.Field(default_factory=lambda: None)
+    class_name: Any = field(default=None, is_javascript_property=False)
 
     # Special component props.
-    special_props: list[Var] = pydantic.v1.Field(default_factory=list)
+    special_props: list[Var] = field(default_factory=list, is_javascript_property=False)
 
     # Whether the component should take the focus once the page is loaded
-    autofocus: bool = pydantic.v1.Field(default_factory=lambda: False)
+    autofocus: bool = field(default=False, is_javascript_property=False)
 
     # components that cannot be children
     _invalid_children: ClassVar[list[str]] = []
@@ -305,14 +603,18 @@ class Component(BaseComponent, ABC):
     _rename_props: ClassVar[dict[str, str]] = {}
 
     # custom attribute
-    custom_attrs: dict[str, Var | Any] = pydantic.v1.Field(default_factory=dict)
+    custom_attrs: dict[str, Var | Any] = field(
+        default_factory=dict, is_javascript_property=False
+    )
 
     # When to memoize this component and its children.
-    _memoization_mode: MemoizationMode = MemoizationMode()
+    _memoization_mode: MemoizationMode = field(
+        default_factory=MemoizationMode, is_javascript_property=False
+    )
 
     # State class associated with this component instance
-    State: type[reflex.state.State] | None = pydantic.v1.Field(
-        default_factory=lambda: None
+    State: type[reflex.state.State] | None = field(
+        default=None, is_javascript_property=False
     )
 
     def add_imports(self) -> ImportDict | list[ImportDict]:
@@ -409,25 +711,6 @@ class Component(BaseComponent, ABC):
         """
         super().__init_subclass__(**kwargs)
 
-        # Get all the props for the component.
-        props = cls.get_props()
-
-        # Convert fields to props, setting default values.
-        for field in cls.get_fields().values():
-            # If the field is not a component prop, skip it.
-            if field.name not in props:
-                continue
-
-            # Set default values for any props.
-            if field.type_ is Var:
-                field.required = False
-                if field.default is not None:
-                    field.default_factory = functools.partial(
-                        LiteralVar.create, field.default
-                    )
-            elif field.type_ is EventHandler:
-                field.required = False
-
         # Ensure renamed props from parent classes are applied to the subclass.
         if cls._rename_props:
             inherited_rename_props = {}
@@ -442,15 +725,15 @@ class Component(BaseComponent, ABC):
         Args:
             **kwargs: The kwargs to pass to the component.
         """
+        super().__init__(
+            children=kwargs.get("children", []),
+        )
         console.deprecate(
             "component-direct-instantiation",
             reason="Use the `create` method instead.",
             deprecation_version="0.7.2",
             removal_version="0.8.0",
         )
-        super().__init__(
-            children=kwargs.get("children", []),
-        )
         self._post_init(**kwargs)
 
     def _post_init(self, *args, **kwargs):
@@ -496,7 +779,9 @@ class Component(BaseComponent, ABC):
                 is_var = False
             elif key in props:
                 # Set the field type.
-                is_var = field.type_ is Var if (field := fields.get(key)) else False
+                is_var = (
+                    field.type_origin is Var if (field := fields.get(key)) else False
+                )
             else:
                 continue
 
@@ -577,11 +862,7 @@ class Component(BaseComponent, ABC):
 
         kwargs["style"] = Style(
             {
-                **(
-                    fields_style.default_factory()
-                    if fields_style.default_factory
-                    else fields_style.default
-                ),
+                **fields_style.default_value(),
                 **style,
                 **{attr: value for attr, value in kwargs.items() if attr not in fields},
             }
@@ -632,17 +913,20 @@ class Component(BaseComponent, ABC):
         Returns:
             The event triggers.
         """
-        triggers = DEFAULT_TRIGGERS.copy()
         # Look for component specific triggers,
         # e.g. variable declared as EventHandler types.
-        for field in self.get_fields().values():
-            if field.type_ is EventHandler:
-                args_spec = None
-                annotation = field.annotation
-                if (metadata := getattr(annotation, "__metadata__", None)) is not None:
-                    args_spec = metadata[0]
-                triggers[field.name] = args_spec or (no_args_event_spec)
-        return triggers
+        return DEFAULT_TRIGGERS | {
+            name: (
+                metadata[0]
+                if (
+                    (metadata := getattr(field.annotated_type, "__metadata__", None))
+                    is not None
+                )
+                else no_args_event_spec
+            )
+            for name, field in self.get_fields().items()
+            if field.type_origin is EventHandler
+        }
 
     def __repr__(self) -> str:
         """Represent the component in React.
@@ -848,7 +1132,8 @@ class Component(BaseComponent, ABC):
         Returns:
             The component.
         """
-        comp = cls.construct(id=props.get("id"), children=list(children))
+        comp = cls.__new__(cls)
+        super(Component, comp).__init__(id=props.get("id"), children=list(children))
         comp._post_init(children=list(children), **props)
         return comp
 
@@ -865,7 +1150,8 @@ class Component(BaseComponent, ABC):
         Returns:
             The component.
         """
-        comp = cls.construct(id=props.get("id"), children=list(children))
+        comp = cls.__new__(cls)
+        super(Component, comp).__init__(id=props.get("id"), children=list(children))
         for prop, value in props.items():
             setattr(comp, prop, value)
         return comp
@@ -1724,10 +2010,10 @@ class CustomComponent(Component):
     library = f"$/{Dirs.COMPONENTS_PATH}"
 
     # The function that creates the component.
-    component_fn: Callable[..., Component] = Component.create
+    component_fn: Callable[..., Component] = field(default=Component.create)
 
     # The props of the component.
-    props: dict[str, Any] = {}
+    props: dict[str, Any] = field(default_factory=dict)
 
     def _post_init(self, **kwargs):
         """Initialize the custom component.

+ 1 - 1
reflex/components/datadisplay/code.py

@@ -546,7 +546,7 @@ if ({language_var!s}) {{
 }}""",
             _var_data=VarData(
                 imports={
-                    cls.__fields__["library"].default: [
+                    cls.get_fields()["library"].default_value(): [
                         ImportVar(tag="PrismAsyncLight", alias="SyntaxHighlighter")
                     ]
                 },

+ 97 - 86
reflex/components/datadisplay/shiki_code_block.py

@@ -2,11 +2,12 @@
 
 from __future__ import annotations
 
+import dataclasses
 import re
 from collections import defaultdict
+from dataclasses import dataclass
 from typing import Any, Literal
 
-from reflex.base import Base
 from reflex.components.component import Component, ComponentNamespace
 from reflex.components.core.colors import color
 from reflex.components.core.cond import color_mode_cond
@@ -410,99 +411,109 @@ class ShikiDecorations(NoExtrasAllowedProps):
     always_wrap: bool = False
 
 
-class ShikiBaseTransformers(Base):
+@dataclass(kw_only=True)
+class ShikiBaseTransformers:
     """Base for creating transformers."""
 
-    library: str
-    fns: list[FunctionStringVar]
-    style: Style | None
+    library: str = ""
+    fns: list[FunctionStringVar] = dataclasses.field(default_factory=list)
+    style: Style | None = dataclasses.field(default=None)
 
 
+@dataclass(kw_only=True)
 class ShikiJsTransformer(ShikiBaseTransformers):
     """A Wrapped shikijs transformer."""
 
     library: str = "@shikijs/transformers@3.3.0"
-    fns: list[FunctionStringVar] = [
-        FunctionStringVar.create(fn) for fn in SHIKIJS_TRANSFORMER_FNS
-    ]
-    style: Style | None = Style(
-        {
-            "code": {"line-height": "1.7", "font-size": "0.875em", "display": "grid"},
-            # Diffs
-            ".diff": {
-                "margin": "0 -24px",
-                "padding": "0 24px",
-                "width": "calc(100% + 48px)",
-                "display": "inline-block",
-            },
-            ".diff.add": {
-                "background-color": "rgba(16, 185, 129, .14)",
-                "position": "relative",
-            },
-            ".diff.remove": {
-                "background-color": "rgba(244, 63, 94, .14)",
-                "opacity": "0.7",
-                "position": "relative",
-            },
-            ".diff.remove:after": {
-                "position": "absolute",
-                "left": "10px",
-                "content": "'-'",
-                "color": "#b34e52",
-            },
-            ".diff.add:after": {
-                "position": "absolute",
-                "left": "10px",
-                "content": "'+'",
-                "color": "#18794e",
-            },
-            # Highlight
-            ".highlighted": {
-                "background-color": "rgba(142, 150, 170, .14)",
-                "margin": "0 -24px",
-                "padding": "0 24px",
-                "width": "calc(100% + 48px)",
-                "display": "inline-block",
-            },
-            ".highlighted.error": {
-                "background-color": "rgba(244, 63, 94, .14)",
-            },
-            ".highlighted.warning": {
-                "background-color": "rgba(234, 179, 8, .14)",
-            },
-            # Highlighted Word
-            ".highlighted-word": {
-                "background-color": color("gray", 2),
-                "border": f"1px solid {color('gray', 5)}",
-                "padding": "1px 3px",
-                "margin": "-1px -3px",
-                "border-radius": "4px",
-            },
-            # Focused Lines
-            ".has-focused .line:not(.focused)": {
-                "opacity": "0.7",
-                "filter": "blur(0.095rem)",
-                "transition": "filter .35s, opacity .35s",
-            },
-            ".has-focused:hover .line:not(.focused)": {
-                "opacity": "1",
-                "filter": "none",
-            },
-            # White Space
-            # ".tab, .space": {
-            #     "position": "relative", # noqa: ERA001
-            # },
-            # ".tab::before": {
-            #     "content": "'⇥'", # noqa: ERA001
-            #     "position": "absolute", # noqa: ERA001
-            #     "opacity": "0.3",# noqa: ERA001
-            # },
-            # ".space::before": {
-            #     "content": "'·'", # noqa: ERA001
-            #     "position": "absolute", # noqa: ERA001
-            #     "opacity": "0.3", # noqa: ERA001
-            # },
-        }
+    fns: list[FunctionStringVar] = dataclasses.field(
+        default_factory=lambda: [
+            FunctionStringVar.create(fn) for fn in SHIKIJS_TRANSFORMER_FNS
+        ]
+    )
+    style: Style | None = dataclasses.field(
+        default_factory=lambda: Style(
+            {
+                "code": {
+                    "line-height": "1.7",
+                    "font-size": "0.875em",
+                    "display": "grid",
+                },
+                # Diffs
+                ".diff": {
+                    "margin": "0 -24px",
+                    "padding": "0 24px",
+                    "width": "calc(100% + 48px)",
+                    "display": "inline-block",
+                },
+                ".diff.add": {
+                    "background-color": "rgba(16, 185, 129, .14)",
+                    "position": "relative",
+                },
+                ".diff.remove": {
+                    "background-color": "rgba(244, 63, 94, .14)",
+                    "opacity": "0.7",
+                    "position": "relative",
+                },
+                ".diff.remove:after": {
+                    "position": "absolute",
+                    "left": "10px",
+                    "content": "'-'",
+                    "color": "#b34e52",
+                },
+                ".diff.add:after": {
+                    "position": "absolute",
+                    "left": "10px",
+                    "content": "'+'",
+                    "color": "#18794e",
+                },
+                # Highlight
+                ".highlighted": {
+                    "background-color": "rgba(142, 150, 170, .14)",
+                    "margin": "0 -24px",
+                    "padding": "0 24px",
+                    "width": "calc(100% + 48px)",
+                    "display": "inline-block",
+                },
+                ".highlighted.error": {
+                    "background-color": "rgba(244, 63, 94, .14)",
+                },
+                ".highlighted.warning": {
+                    "background-color": "rgba(234, 179, 8, .14)",
+                },
+                # Highlighted Word
+                ".highlighted-word": {
+                    "background-color": color("gray", 2),
+                    "border": f"1px solid {color('gray', 5)}",
+                    "padding": "1px 3px",
+                    "margin": "-1px -3px",
+                    "border-radius": "4px",
+                },
+                # Focused Lines
+                ".has-focused .line:not(.focused)": {
+                    "opacity": "0.7",
+                    "filter": "blur(0.095rem)",
+                    "transition": "filter .35s, opacity .35s",
+                },
+                ".has-focused:hover .line:not(.focused)": {
+                    "opacity": "1",
+                    "filter": "none",
+                },
+                # White Space
+                # ".tab, .space": {
+                #     "position": "relative", # noqa: ERA001
+                # },
+                # ".tab::before": {
+                #     "content": "'⇥'", # noqa: ERA001
+                #     "position": "absolute", # noqa: ERA001
+                #     "opacity": "0.3",# noqa: ERA001
+                # },
+                # ".space::before": {
+                #     "content": "'·'", # noqa: ERA001
+                #     "position": "absolute", # noqa: ERA001
+                #     "opacity": "0.3", # noqa: ERA001
+                # },
+            }
+        )
     )
 
     def __init__(self, **kwargs):

+ 1 - 1
reflex/components/el/elements/forms.py

@@ -189,7 +189,7 @@ class Form(BaseHTML):
         # Render the form hooks and use the hash of the resulting code to create a unique name.
         props["handle_submit_unique_name"] = ""
         form = super().create(*children, **props)
-        form.handle_submit_unique_name = md5(
+        form.handle_submit_unique_name = md5(  # pyright: ignore[reportAttributeAccessIssue]
             str(form._get_all_hooks()).encode("utf-8")
         ).hexdigest()
         return form

+ 3 - 1
reflex/components/radix/themes/base.py

@@ -135,7 +135,9 @@ class RadixThemesComponent(Component):
         """
         component = super().create(*children, **props)
         if component.library is None:
-            component.library = RadixThemesComponent.__fields__["library"].default
+            component.library = RadixThemesComponent.get_fields()[
+                "library"
+            ].default_value()
         component.alias = "RadixThemes" + (component.tag or type(component).__name__)
         return component
 

+ 2 - 2
reflex/components/radix/themes/components/icon_button.py

@@ -75,7 +75,7 @@ class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
             )
         if "size" in props:
             if isinstance(props["size"], str):
-                children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
+                children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]  # pyright: ignore[reportAttributeAccessIssue]
             else:
                 size_map_var = Match.create(
                     props["size"],
@@ -84,7 +84,7 @@ class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
                 )
                 if not isinstance(size_map_var, Var):
                     raise ValueError(f"Match did not return a Var: {size_map_var}")
-                children[0].size = size_map_var
+                children[0].size = size_map_var  # pyright: ignore[reportAttributeAccessIssue]
         return super().create(*children, **props)
 
     def add_style(self):

+ 1 - 1
reflex/components/radix/themes/layout/list.py

@@ -170,7 +170,7 @@ class ListItem(Li, MarkdownComponentMap):
         """
         for child in children:
             if isinstance(child, Text):
-                child.as_ = "span"
+                child.as_ = "span"  # pyright: ignore[reportAttributeAccessIssue]
             elif isinstance(child, Icon) and "display" not in child.style:
                 child.style["display"] = "inline"
         return super().create(*children, **props)

+ 12 - 2
reflex/utils/pyi_generator.py

@@ -10,6 +10,7 @@ import json
 import logging
 import re
 import subprocess
+import sys
 import typing
 from collections.abc import Callable, Iterable, Sequence
 from fileinput import FileInput
@@ -387,13 +388,22 @@ def _extract_class_props_as_ast_nodes(
                     if isinstance(default, Var):
                         default = default._decode()
 
+            modules = {cls.__module__ for cls in target_class.__mro__}
+            available_vars = {}
+            for module in modules:
+                available_vars.update(sys.modules[module].__dict__)
+
             kwargs.append(
                 (
                     ast.arg(
                         arg=name,
                         annotation=ast.Name(
                             id=OVERWRITE_TYPES.get(
-                                name, _get_type_hint(value, type_hint_globals)
+                                name,
+                                _get_type_hint(
+                                    value,
+                                    type_hint_globals | available_vars,
+                                ),
                             )
                         ),
                     ),
@@ -1227,7 +1237,7 @@ class PyiGenerator:
                     continue
                 subprocess.run(["git", "checkout", changed_file])
 
-        if cpu_count() == 1 or len(file_targets) < 5:
+        if True:
             self._scan_files(file_targets)
         else:
             self._scan_files_multiprocess(file_targets)

+ 14 - 0
reflex/utils/types.py

@@ -254,6 +254,20 @@ def is_optional(cls: GenericType) -> bool:
     return is_union(cls) and type(None) in get_args(cls)
 
 
+def is_classvar(a_type: Any) -> bool:
+    """Check if a type is a ClassVar.
+
+    Args:
+        a_type: The type to check.
+
+    Returns:
+        Whether the type is a ClassVar.
+    """
+    return a_type is ClassVar or (
+        type(a_type) is _GenericAlias and a_type.__origin__ is ClassVar
+    )
+
+
 def true_type_for_pydantic_field(f: ModelField):
     """Get the type for a pydantic field.
 

+ 22 - 1
reflex/vars/base.py

@@ -88,6 +88,12 @@ SEQUENCE_TYPE = TypeVar("SEQUENCE_TYPE", bound=Sequence)
 
 warnings.filterwarnings("ignore", message="fields may not start with an underscore")
 
+_PYDANTIC_VALIDATE_VALUES = "__pydantic_validate_values__"
+
+
+def _pydantic_validator(*args, **kwargs):
+    return None
+
 
 @dataclasses.dataclass(
     eq=False,
@@ -363,11 +369,26 @@ def can_use_in_object_var(cls: GenericType) -> bool:
     )
 
 
+class MetaclassVar(type):
+    """Metaclass for the Var class."""
+
+    def __setattr__(cls, name: str, value: Any):
+        """Set an attribute on the class.
+
+        Args:
+            name: The name of the attribute.
+            value: The value of the attribute.
+        """
+        super().__setattr__(
+            name, value if name != _PYDANTIC_VALIDATE_VALUES else _pydantic_validator
+        )
+
+
 @dataclasses.dataclass(
     eq=False,
     frozen=True,
 )
-class Var(Generic[VAR_TYPE]):
+class Var(Generic[VAR_TYPE], metaclass=MetaclassVar):
     """Base class for immutable vars."""
 
     # The name of the var.

+ 2 - 2
tests/units/components/test_component.py

@@ -911,7 +911,7 @@ def test_custom_component_wrapper():
     assert len(ccomponent.children) == 1
     assert isinstance(ccomponent.children[0], Text)
 
-    component = ccomponent.get_component(ccomponent)
+    component = ccomponent.get_component()
     assert isinstance(component, Box)
 
 
@@ -1823,7 +1823,7 @@ def test_custom_component_get_imports():
     assert "outer" not in custom_comp._get_all_imports()
 
     # The imports are only resolved during compilation.
-    custom_comp.get_component(custom_comp)
+    custom_comp.get_component()
     _, imports_inner = compile_custom_component(custom_comp)
     assert "inner" in imports_inner
     assert "outer" not in imports_inner