# SPDX-License-Identifier: GPLv3-or-later
# Copyright © 2025 pygaindalf Rui Pinheiro
import functools
from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Collection, Hashable, Iterable, Iterator, Sequence
from typing import TYPE_CHECKING, Any, Self, overload, override
from typing import cast as typing_cast
from pydantic import GetCoreSchemaHandler
from pydantic_core import CoreSchema, core_schema
if TYPE_CHECKING:
from _typeshed import SupportsRichComparison
from ..journalled.set.ordered_view_set import JournalledOrderedViewSet
from ....util.callguard import callguard_class
from ....util.helpers import generics
from ....util.helpers.instance_lru_cache import instance_lru_cache
from .protocols import SortKeyProtocol
[docs]
@callguard_class()
class OrderedViewCollection[T: Hashable](Collection[T], metaclass=ABCMeta):
get_content_type = generics.GenericIntrospectionMethod[T]()
[docs]
def __init__(self, data: Iterable[T] | None = None, /) -> None:
self._initialize_container(data)
@abstractmethod
def _initialize_container(self, data: Iterable[T] | None = None) -> None:
msg = "Subclasses must implement _initialize_container method."
raise NotImplementedError(msg)
@abstractmethod
def _get_container(self) -> Collection[T]:
msg = "Subclasses must implement _get_container method."
raise NotImplementedError(msg)
[docs]
def item_sort_key(self, item: T) -> SupportsRichComparison:
if isinstance(item, SortKeyProtocol):
return item.sort_key()
return typing_cast("SupportsRichComparison", item)
@property
def item_sort_reverse(self) -> bool:
return False
[docs]
@instance_lru_cache
def sort(self, *, key: Callable[[T], SupportsRichComparison] | None = None, reverse: bool | None = None) -> Sequence[T]:
if key is None:
key = self.item_sort_key
if reverse is None:
reverse = self.item_sort_reverse
return tuple(sorted(self._get_container(), key=key, reverse=reverse))
@property
def sorted(self) -> Sequence[T]:
return self.sort()
[docs]
def clear_sort_cache(self) -> None:
self.sort.cache_clear()
# MARK: Collection ABC
@override
def __contains__(self, value: object) -> bool:
return value in self._get_container()
@override
def __iter__(self) -> Iterator[T]:
return iter(self.sorted)
@overload
def __getitem__(self, index: int) -> T: ...
@overload
def __getitem__(self, index: slice) -> Sequence[T]: ...
def __getitem__(self, index: int | slice) -> T | Sequence[T]:
return self.sorted[index]
@override
def __len__(self) -> int:
return len(self._get_container())
@override
def __hash__(self) -> int:
return hash(self.sorted)
@override
def __str__(self) -> str:
return str(self.sorted)
@override
def __repr__(self) -> str:
return f"<{type(self).__name__}: {self.sorted!r}>"
# MARK: Pydantic
[docs]
@classmethod
@abstractmethod
def get_core_schema(cls, source, handler) -> CoreSchema: # noqa: ANN001
msg = "Subclasses must implement get_core_schema method."
raise NotImplementedError(msg)
[docs]
@classmethod
@abstractmethod
def serialize(cls, value: Self) -> Any:
msg = "Subclasses must implement serialize method."
raise NotImplementedError(msg)
@classmethod
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> CoreSchema:
schema = cls.get_core_schema(source, handler)
return core_schema.no_info_plain_validator_function(
function=functools.partial(
cls.validate_and_coerce,
source=source,
),
json_schema_input_schema=schema,
serialization=core_schema.plain_serializer_function_ser_schema(
function=cls.serialize,
),
)
[docs]
@classmethod
def validate_and_coerce(cls, value: Any, *, source: type[Self] | None = None) -> Self:
concrete_item_type = cls.get_content_type(source=source)
if not isinstance(value, Iterable):
msg = f"Expected an Iterable[{concrete_item_type.__name__}], got {type(value).__name__}."
raise TypeError(msg)
for item in value:
cls._validate_item(concrete_item_type, item, source=source)
return cls(value)
@classmethod
def _validate_item(cls, concrete_item_type: type[T], item: Any, *, source: type[Self] | None = None) -> None: # noqa: ARG003
if not isinstance(item, concrete_item_type):
msg = f"Expected item of type {concrete_item_type.__name__}, got {type(item).__name__}."
raise TypeError(msg)
# MARK: Journalled Ordered View
[docs]
@classmethod
def get_journalled_type(cls) -> type[JournalledOrderedViewSet]:
from ..journalled.set.ordered_view_set import JournalledOrderedViewSet
return JournalledOrderedViewSet