183 lines
5.8 KiB
Python
183 lines
5.8 KiB
Python
"""Shared field infrastructure for components and props."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import _MISSING_TYPE, MISSING
|
|
from typing import Annotated, Any, Generic, TypeVar, get_origin
|
|
|
|
from reflex_base.utils import types
|
|
from reflex_base.utils.compat import annotations_from_namespace
|
|
|
|
FIELD_TYPE = TypeVar("FIELD_TYPE")
|
|
|
|
|
|
class BaseField(Generic[FIELD_TYPE]):
|
|
"""Base field class used by internal metadata classes."""
|
|
|
|
def __init__(
|
|
self,
|
|
default: FIELD_TYPE | _MISSING_TYPE = MISSING,
|
|
default_factory: Callable[[], FIELD_TYPE] | 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.
|
|
annotated_type: The annotated type for the field.
|
|
"""
|
|
self.default = default
|
|
self.default_factory = default_factory
|
|
self.outer_type_ = self.annotated_type = annotated_type
|
|
|
|
# Process type annotation
|
|
type_origin = get_origin(annotated_type) or annotated_type
|
|
if type_origin is Annotated:
|
|
type_origin = annotated_type.__origin__ # pyright: ignore [reportAttributeAccessIssue]
|
|
# For Annotated types, use the actual type inside the annotation
|
|
self.type_ = annotated_type
|
|
else:
|
|
# For other types (including Union), preserve the original type
|
|
self.type_ = annotated_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()
|
|
msg = "No default value or factory provided."
|
|
raise ValueError(msg)
|
|
|
|
|
|
class FieldBasedMeta(type):
|
|
"""Shared metaclass for field-based classes like components and props.
|
|
|
|
Provides common field inheritance and processing logic for both
|
|
PropsBaseMeta and BaseComponentMeta.
|
|
"""
|
|
|
|
def __new__(
|
|
cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]
|
|
) -> type:
|
|
"""Create a new field-based class.
|
|
|
|
Args:
|
|
name: The name of the class.
|
|
bases: The base classes.
|
|
namespace: The class namespace.
|
|
|
|
Returns:
|
|
The new class.
|
|
"""
|
|
# Collect inherited fields from base classes
|
|
inherited_fields = cls._collect_inherited_fields(bases)
|
|
|
|
# Get annotations from the namespace
|
|
annotations = cls._resolve_annotations(namespace, name)
|
|
|
|
# Process field overrides (fields with values but no annotations)
|
|
own_fields = cls._process_field_overrides(
|
|
namespace, annotations, inherited_fields
|
|
)
|
|
|
|
# Process annotated fields
|
|
own_fields.update(
|
|
cls._process_annotated_fields(namespace, annotations, inherited_fields)
|
|
)
|
|
|
|
# Finalize fields and store on class
|
|
cls._finalize_fields(namespace, inherited_fields, own_fields)
|
|
|
|
return super().__new__(cls, name, bases, namespace)
|
|
|
|
@classmethod
|
|
def _collect_inherited_fields(cls, bases: tuple[type, ...]) -> dict[str, Any]:
|
|
inherited_fields: dict[str, Any] = {}
|
|
|
|
# Collect inherited fields from base classes
|
|
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)
|
|
|
|
return inherited_fields
|
|
|
|
@classmethod
|
|
def _resolve_annotations(
|
|
cls, namespace: dict[str, Any], name: str
|
|
) -> dict[str, Any]:
|
|
return types.resolve_annotations(
|
|
annotations_from_namespace(namespace),
|
|
namespace["__module__"],
|
|
)
|
|
|
|
@classmethod
|
|
def _process_field_overrides(
|
|
cls,
|
|
namespace: dict[str, Any],
|
|
annotations: dict[str, Any],
|
|
inherited_fields: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
own_fields: dict[str, Any] = {}
|
|
|
|
for key, value in namespace.items():
|
|
if key not in annotations and key in inherited_fields:
|
|
inherited_field = inherited_fields[key]
|
|
new_field = cls._create_field(
|
|
annotated_type=inherited_field.annotated_type,
|
|
default=value,
|
|
default_factory=None,
|
|
)
|
|
own_fields[key] = new_field
|
|
|
|
return own_fields
|
|
|
|
@classmethod
|
|
def _process_annotated_fields(
|
|
cls,
|
|
namespace: dict[str, Any],
|
|
annotations: dict[str, Any],
|
|
inherited_fields: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def _create_field(
|
|
cls,
|
|
annotated_type: Any,
|
|
default: Any = MISSING,
|
|
default_factory: Callable[[], Any] | None = None,
|
|
) -> Any:
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def _finalize_fields(
|
|
cls,
|
|
namespace: dict[str, Any],
|
|
inherited_fields: dict[str, Any],
|
|
own_fields: dict[str, Any],
|
|
) -> None:
|
|
# Combine all fields
|
|
all_fields = inherited_fields | own_fields
|
|
|
|
# Set field names for compatibility
|
|
for field_name, field in all_fields.items():
|
|
field._name = field_name
|
|
|
|
# Store field mappings on the class
|
|
namespace["_own_fields"] = own_fields
|
|
namespace["_inherited_fields"] = inherited_fields
|
|
namespace["_fields"] = all_fields
|