aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/annotated_types
diff options
context:
space:
mode:
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])