about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/annotated_types
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/annotated_types
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/annotated_types')
-rw-r--r--.venv/lib/python3.12/site-packages/annotated_types/__init__.py432
-rw-r--r--.venv/lib/python3.12/site-packages/annotated_types/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/annotated_types/test_cases.py151
3 files changed, 583 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/annotated_types/__init__.py b/.venv/lib/python3.12/site-packages/annotated_types/__init__.py
new file mode 100644
index 00000000..74e0deea
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/annotated_types/__init__.py
@@ -0,0 +1,432 @@
+import math
+import sys
+import types
+from dataclasses import dataclass
+from datetime import tzinfo
+from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union
+
+if sys.version_info < (3, 8):
+    from typing_extensions import Protocol, runtime_checkable
+else:
+    from typing import Protocol, runtime_checkable
+
+if sys.version_info < (3, 9):
+    from typing_extensions import Annotated, Literal
+else:
+    from typing import Annotated, Literal
+
+if sys.version_info < (3, 10):
+    EllipsisType = type(Ellipsis)
+    KW_ONLY = {}
+    SLOTS = {}
+else:
+    from types import EllipsisType
+
+    KW_ONLY = {"kw_only": True}
+    SLOTS = {"slots": True}
+
+
+__all__ = (
+    'BaseMetadata',
+    'GroupedMetadata',
+    'Gt',
+    'Ge',
+    'Lt',
+    'Le',
+    'Interval',
+    'MultipleOf',
+    'MinLen',
+    'MaxLen',
+    'Len',
+    'Timezone',
+    'Predicate',
+    'LowerCase',
+    'UpperCase',
+    'IsDigits',
+    'IsFinite',
+    'IsNotFinite',
+    'IsNan',
+    'IsNotNan',
+    'IsInfinite',
+    'IsNotInfinite',
+    'doc',
+    'DocInfo',
+    '__version__',
+)
+
+__version__ = '0.7.0'
+
+
+T = TypeVar('T')
+
+
+# arguments that start with __ are considered
+# positional only
+# see https://peps.python.org/pep-0484/#positional-only-arguments
+
+
+class SupportsGt(Protocol):
+    def __gt__(self: T, __other: T) -> bool:
+        ...
+
+
+class SupportsGe(Protocol):
+    def __ge__(self: T, __other: T) -> bool:
+        ...
+
+
+class SupportsLt(Protocol):
+    def __lt__(self: T, __other: T) -> bool:
+        ...
+
+
+class SupportsLe(Protocol):
+    def __le__(self: T, __other: T) -> bool:
+        ...
+
+
+class SupportsMod(Protocol):
+    def __mod__(self: T, __other: T) -> T:
+        ...
+
+
+class SupportsDiv(Protocol):
+    def __div__(self: T, __other: T) -> T:
+        ...
+
+
+class BaseMetadata:
+    """Base class for all metadata.
+
+    This exists mainly so that implementers
+    can do `isinstance(..., BaseMetadata)` while traversing field annotations.
+    """
+
+    __slots__ = ()
+
+
+@dataclass(frozen=True, **SLOTS)
+class Gt(BaseMetadata):
+    """Gt(gt=x) implies that the value must be greater than x.
+
+    It can be used with any type that supports the ``>`` operator,
+    including numbers, dates and times, strings, sets, and so on.
+    """
+
+    gt: SupportsGt
+
+
+@dataclass(frozen=True, **SLOTS)
+class Ge(BaseMetadata):
+    """Ge(ge=x) implies that the value must be greater than or equal to x.
+
+    It can be used with any type that supports the ``>=`` operator,
+    including numbers, dates and times, strings, sets, and so on.
+    """
+
+    ge: SupportsGe
+
+
+@dataclass(frozen=True, **SLOTS)
+class Lt(BaseMetadata):
+    """Lt(lt=x) implies that the value must be less than x.
+
+    It can be used with any type that supports the ``<`` operator,
+    including numbers, dates and times, strings, sets, and so on.
+    """
+
+    lt: SupportsLt
+
+
+@dataclass(frozen=True, **SLOTS)
+class Le(BaseMetadata):
+    """Le(le=x) implies that the value must be less than or equal to x.
+
+    It can be used with any type that supports the ``<=`` operator,
+    including numbers, dates and times, strings, sets, and so on.
+    """
+
+    le: SupportsLe
+
+
+@runtime_checkable
+class GroupedMetadata(Protocol):
+    """A grouping of multiple objects, like typing.Unpack.
+
+    `GroupedMetadata` on its own is not metadata and has no meaning.
+    All of the constraints and metadata should be fully expressable
+    in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
+
+    Concrete implementations should override `GroupedMetadata.__iter__()`
+    to add their own metadata.
+    For example:
+
+    >>> @dataclass
+    >>> class Field(GroupedMetadata):
+    >>>     gt: float | None = None
+    >>>     description: str | None = None
+    ...
+    >>>     def __iter__(self) -> Iterable[object]:
+    >>>         if self.gt is not None:
+    >>>             yield Gt(self.gt)
+    >>>         if self.description is not None:
+    >>>             yield Description(self.gt)
+
+    Also see the implementation of `Interval` below for an example.
+
+    Parsers should recognize this and unpack it so that it can be used
+    both with and without unpacking:
+
+    - `Annotated[int, Field(...)]` (parser must unpack Field)
+    - `Annotated[int, *Field(...)]` (PEP-646)
+    """  # noqa: trailing-whitespace
+
+    @property
+    def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
+        return True
+
+    def __iter__(self) -> Iterator[object]:
+        ...
+
+    if not TYPE_CHECKING:
+        __slots__ = ()  # allow subclasses to use slots
+
+        def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
+            # Basic ABC like functionality without the complexity of an ABC
+            super().__init_subclass__(*args, **kwargs)
+            if cls.__iter__ is GroupedMetadata.__iter__:
+                raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
+
+        def __iter__(self) -> Iterator[object]:  # noqa: F811
+            raise NotImplementedError  # more helpful than "None has no attribute..." type errors
+
+
+@dataclass(frozen=True, **KW_ONLY, **SLOTS)
+class Interval(GroupedMetadata):
+    """Interval can express inclusive or exclusive bounds with a single object.
+
+    It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
+    are interpreted the same way as the single-bound constraints.
+    """
+
+    gt: Union[SupportsGt, None] = None
+    ge: Union[SupportsGe, None] = None
+    lt: Union[SupportsLt, None] = None
+    le: Union[SupportsLe, None] = None
+
+    def __iter__(self) -> Iterator[BaseMetadata]:
+        """Unpack an Interval into zero or more single-bounds."""
+        if self.gt is not None:
+            yield Gt(self.gt)
+        if self.ge is not None:
+            yield Ge(self.ge)
+        if self.lt is not None:
+            yield Lt(self.lt)
+        if self.le is not None:
+            yield Le(self.le)
+
+
+@dataclass(frozen=True, **SLOTS)
+class MultipleOf(BaseMetadata):
+    """MultipleOf(multiple_of=x) might be interpreted in two ways:
+
+    1. Python semantics, implying ``value % multiple_of == 0``, or
+    2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
+
+    We encourage users to be aware of these two common interpretations,
+    and libraries to carefully document which they implement.
+    """
+
+    multiple_of: Union[SupportsDiv, SupportsMod]
+
+
+@dataclass(frozen=True, **SLOTS)
+class MinLen(BaseMetadata):
+    """
+    MinLen() implies minimum inclusive length,
+    e.g. ``len(value) >= min_length``.
+    """
+
+    min_length: Annotated[int, Ge(0)]
+
+
+@dataclass(frozen=True, **SLOTS)
+class MaxLen(BaseMetadata):
+    """
+    MaxLen() implies maximum inclusive length,
+    e.g. ``len(value) <= max_length``.
+    """
+
+    max_length: Annotated[int, Ge(0)]
+
+
+@dataclass(frozen=True, **SLOTS)
+class Len(GroupedMetadata):
+    """
+    Len() implies that ``min_length <= len(value) <= max_length``.
+
+    Upper bound may be omitted or ``None`` to indicate no upper length bound.
+    """
+
+    min_length: Annotated[int, Ge(0)] = 0
+    max_length: Optional[Annotated[int, Ge(0)]] = None
+
+    def __iter__(self) -> Iterator[BaseMetadata]:
+        """Unpack a Len into zone or more single-bounds."""
+        if self.min_length > 0:
+            yield MinLen(self.min_length)
+        if self.max_length is not None:
+            yield MaxLen(self.max_length)
+
+
+@dataclass(frozen=True, **SLOTS)
+class Timezone(BaseMetadata):
+    """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
+
+    ``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
+    ``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
+    tz-aware but any timezone is allowed.
+
+    You may also pass a specific timezone string or tzinfo object such as
+    ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
+    you only allow a specific timezone, though we note that this is often
+    a symptom of poor design.
+    """
+
+    tz: Union[str, tzinfo, EllipsisType, None]
+
+
+@dataclass(frozen=True, **SLOTS)
+class Unit(BaseMetadata):
+    """Indicates that the value is a physical quantity with the specified unit.
+
+    It is intended for usage with numeric types, where the value represents the
+    magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
+    or ``speed: Annotated[float, Unit('m/s')]``.
+
+    Interpretation of the unit string is left to the discretion of the consumer.
+    It is suggested to follow conventions established by python libraries that work
+    with physical quantities, such as
+
+    - ``pint`` : <https://pint.readthedocs.io/en/stable/>
+    - ``astropy.units``: <https://docs.astropy.org/en/stable/units/>
+
+    For indicating a quantity with a certain dimensionality but without a specific unit
+    it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
+    Note, however, ``annotated_types`` itself makes no use of the unit string.
+    """
+
+    unit: str
+
+
+@dataclass(frozen=True, **SLOTS)
+class Predicate(BaseMetadata):
+    """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
+
+    Users should prefer statically inspectable metadata, but if you need the full
+    power and flexibility of arbitrary runtime predicates... here it is.
+
+    We provide a few predefined predicates for common string constraints:
+    ``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
+    ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
+    can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
+
+    Some libraries might have special logic to handle certain predicates, e.g. by
+    checking for `str.isdigit` and using its presence to both call custom logic to
+    enforce digit-only strings, and customise some generated external schema.
+
+    We do not specify what behaviour should be expected for predicates that raise
+    an exception.  For example `Annotated[int, Predicate(str.isdigit)]` might silently
+    skip invalid constraints, or statically raise an error; or it might try calling it
+    and then propagate or discard the resulting exception.
+    """
+
+    func: Callable[[Any], bool]
+
+    def __repr__(self) -> str:
+        if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
+            return f"{self.__class__.__name__}({self.func!r})"
+        if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
+            namespace := getattr(self.func.__self__, "__name__", None)
+        ):
+            return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
+        if isinstance(self.func, type(str.isascii)):  # method descriptor
+            return f"{self.__class__.__name__}({self.func.__qualname__})"
+        return f"{self.__class__.__name__}({self.func.__name__})"
+
+
+@dataclass
+class Not:
+    func: Callable[[Any], bool]
+
+    def __call__(self, __v: Any) -> bool:
+        return not self.func(__v)
+
+
+_StrType = TypeVar("_StrType", bound=str)
+
+LowerCase = Annotated[_StrType, Predicate(str.islower)]
+"""
+Return True if the string is a lowercase string, False otherwise.
+
+A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
+"""  # noqa: E501
+UpperCase = Annotated[_StrType, Predicate(str.isupper)]
+"""
+Return True if the string is an uppercase string, False otherwise.
+
+A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
+"""  # noqa: E501
+IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
+IsDigits = IsDigit  # type: ignore  # plural for backwards compatibility, see #63
+"""
+Return True if the string is a digit string, False otherwise.
+
+A string is a digit string if all characters in the string are digits and there is at least one character in the string.
+"""  # noqa: E501
+IsAscii = Annotated[_StrType, Predicate(str.isascii)]
+"""
+Return True if all characters in the string are ASCII, False otherwise.
+
+ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
+"""
+
+_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
+IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
+"""Return True if x is neither an infinity nor a NaN, and False otherwise."""
+IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
+"""Return True if x is one of infinity or NaN, and False otherwise"""
+IsNan = Annotated[_NumericType, Predicate(math.isnan)]
+"""Return True if x is a NaN (not a number), and False otherwise."""
+IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
+"""Return True if x is anything but NaN (not a number), and False otherwise."""
+IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
+"""Return True if x is a positive or negative infinity, and False otherwise."""
+IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
+"""Return True if x is neither a positive or negative infinity, and False otherwise."""
+
+try:
+    from typing_extensions import DocInfo, doc  # type: ignore [attr-defined]
+except ImportError:
+
+    @dataclass(frozen=True, **SLOTS)
+    class DocInfo:  # type: ignore [no-redef]
+        """ "
+        The return value of doc(), mainly to be used by tools that want to extract the
+        Annotated documentation at runtime.
+        """
+
+        documentation: str
+        """The documentation string passed to doc()."""
+
+    def doc(
+        documentation: str,
+    ) -> DocInfo:
+        """
+        Add documentation to a type annotation inside of Annotated.
+
+        For example:
+
+        >>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ...
+        """
+        return DocInfo(documentation)
diff --git a/.venv/lib/python3.12/site-packages/annotated_types/py.typed b/.venv/lib/python3.12/site-packages/annotated_types/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/annotated_types/py.typed
diff --git a/.venv/lib/python3.12/site-packages/annotated_types/test_cases.py b/.venv/lib/python3.12/site-packages/annotated_types/test_cases.py
new file mode 100644
index 00000000..d9164d68
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/annotated_types/test_cases.py
@@ -0,0 +1,151 @@
+import math
+import sys
+from datetime import date, datetime, timedelta, timezone
+from decimal import Decimal
+from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple
+
+if sys.version_info < (3, 9):
+    from typing_extensions import Annotated
+else:
+    from typing import Annotated
+
+import annotated_types as at
+
+
+class Case(NamedTuple):
+    """
+    A test case for `annotated_types`.
+    """
+
+    annotation: Any
+    valid_cases: Iterable[Any]
+    invalid_cases: Iterable[Any]
+
+
+def cases() -> Iterable[Case]:
+    # Gt, Ge, Lt, Le
+    yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
+    yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
+    yield Case(
+        Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
+        [datetime(2000, 1, 2), datetime(2000, 1, 3)],
+        [datetime(2000, 1, 1), datetime(1999, 12, 31)],
+    )
+    yield Case(
+        Annotated[datetime, at.Gt(date(2000, 1, 1))],
+        [date(2000, 1, 2), date(2000, 1, 3)],
+        [date(2000, 1, 1), date(1999, 12, 31)],
+    )
+    yield Case(
+        Annotated[datetime, at.Gt(Decimal('1.123'))],
+        [Decimal('1.1231'), Decimal('123')],
+        [Decimal('1.123'), Decimal('0')],
+    )
+
+    yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
+    yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
+    yield Case(
+        Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
+        [datetime(2000, 1, 2), datetime(2000, 1, 3)],
+        [datetime(1998, 1, 1), datetime(1999, 12, 31)],
+    )
+
+    yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
+    yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
+    yield Case(
+        Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
+        [datetime(1999, 12, 31), datetime(1999, 12, 31)],
+        [datetime(2000, 1, 2), datetime(2000, 1, 3)],
+    )
+
+    yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
+    yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
+    yield Case(
+        Annotated[datetime, at.Le(datetime(2000, 1, 1))],
+        [datetime(2000, 1, 1), datetime(1999, 12, 31)],
+        [datetime(2000, 1, 2), datetime(2000, 1, 3)],
+    )
+
+    # Interval
+    yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
+    yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
+    yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
+    yield Case(
+        Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
+        [datetime(2000, 1, 2), datetime(2000, 1, 3)],
+        [datetime(2000, 1, 1), datetime(2000, 1, 4)],
+    )
+
+    yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
+    yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
+
+    # lengths
+
+    yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
+    yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
+    yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
+    yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
+
+    yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
+    yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
+    yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
+    yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
+
+    yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
+    yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
+
+    yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
+    yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
+    yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
+
+    # Timezone
+
+    yield Case(
+        Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
+    )
+    yield Case(
+        Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
+    )
+    yield Case(
+        Annotated[datetime, at.Timezone(timezone.utc)],
+        [datetime(2000, 1, 1, tzinfo=timezone.utc)],
+        [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
+    )
+    yield Case(
+        Annotated[datetime, at.Timezone('Europe/London')],
+        [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
+        [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
+    )
+
+    # Quantity
+
+    yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
+
+    # predicate types
+
+    yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
+    yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
+    yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
+    yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
+
+    yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
+
+    yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
+    yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
+    yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
+    yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
+    yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
+    yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
+
+    # check stacked predicates
+    yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
+
+    # doc
+    yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
+
+    # custom GroupedMetadata
+    class MyCustomGroupedMetadata(at.GroupedMetadata):
+        def __iter__(self) -> Iterator[at.Predicate]:
+            yield at.Predicate(lambda x: float(x).is_integer())
+
+    yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])