Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/annotated_types/__init__.py: 76%
104 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
1import sys
2from dataclasses import dataclass
3from datetime import timezone
4from typing import Any, Callable, Iterator, Optional, TypeVar, Union
6if sys.version_info < (3, 8):
7 from typing_extensions import Protocol
8else:
9 from typing import Protocol
11if sys.version_info < (3, 9):
12 from typing_extensions import Annotated
13else:
14 from typing import Annotated
16if sys.version_info < (3, 10):
17 EllipsisType = type(Ellipsis)
18 KW_ONLY = {}
19 SLOTS = {}
20else:
21 from types import EllipsisType
23 KW_ONLY = {"kw_only": True}
24 SLOTS = {"slots": True}
27__all__ = (
28 'BaseMetadata',
29 'GroupedMetadata',
30 'Gt',
31 'Ge',
32 'Lt',
33 'Le',
34 'Interval',
35 'MultipleOf',
36 'MinLen',
37 'MaxLen',
38 'Len',
39 'Timezone',
40 'Predicate',
41 'LowerCase',
42 'UpperCase',
43 'IsDigits',
44 '__version__',
45)
47__version__ = '0.4.0'
50T = TypeVar('T')
53# arguments that start with __ are considered
54# positional only
55# see https://peps.python.org/pep-0484/#positional-only-arguments
58class SupportsGt(Protocol):
59 def __gt__(self: T, __other: T) -> bool:
60 ...
63class SupportsGe(Protocol):
64 def __ge__(self: T, __other: T) -> bool:
65 ...
68class SupportsLt(Protocol):
69 def __lt__(self: T, __other: T) -> bool:
70 ...
73class SupportsLe(Protocol):
74 def __le__(self: T, __other: T) -> bool:
75 ...
78class SupportsMod(Protocol):
79 def __mod__(self: T, __other: T) -> T:
80 ...
83class SupportsDiv(Protocol):
84 def __div__(self: T, __other: T) -> T:
85 ...
88class BaseMetadata:
89 """Base class for all metadata.
91 This exists mainly so that implementers
92 can do `isinstance(..., BaseMetadata)` while traversing field annotations.
93 """
95 __slots__ = ()
98@dataclass(frozen=True, **SLOTS)
99class Gt(BaseMetadata):
100 """Gt(gt=x) implies that the value must be greater than x.
102 It can be used with any type that supports the ``>`` operator,
103 including numbers, dates and times, strings, sets, and so on.
104 """
106 gt: SupportsGt
109@dataclass(frozen=True, **SLOTS)
110class Ge(BaseMetadata):
111 """Ge(ge=x) implies that the value must be greater than or equal to x.
113 It can be used with any type that supports the ``>=`` operator,
114 including numbers, dates and times, strings, sets, and so on.
115 """
117 ge: SupportsGe
120@dataclass(frozen=True, **SLOTS)
121class Lt(BaseMetadata):
122 """Lt(lt=x) implies that the value must be less than x.
124 It can be used with any type that supports the ``<`` operator,
125 including numbers, dates and times, strings, sets, and so on.
126 """
128 lt: SupportsLt
131@dataclass(frozen=True, **SLOTS)
132class Le(BaseMetadata):
133 """Le(le=x) implies that the value must be less than or equal to x.
135 It can be used with any type that supports the ``<=`` operator,
136 including numbers, dates and times, strings, sets, and so on.
137 """
139 le: SupportsLe
142class GroupedMetadata:
143 """A grouping of multiple BaseMetadata objects.
145 `GroupedMetadata` on its own is not metadata and has no meaning.
146 All it the the constraint and metadata should be fully expressable
147 in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
149 Concrete implementations should override `GroupedMetadata.__iter__()`
150 to add their own metadata.
151 For example:
153 >>> @dataclass
154 >>> class Field(GroupedMetadata):
155 >>> gt: float | None = None
156 >>> description: str | None = None
157 ...
158 >>> def __iter__(self) -> Iterable[BaseMetadata]:
159 >>> if self.gt is not None:
160 >>> yield Gt(self.gt)
161 >>> if self.description is not None:
162 >>> yield Description(self.gt)
164 Also see the implementation of `Interval` below for an example.
166 Parsers should recognize this and unpack it so that it can be used
167 both with and without unpacking:
169 - `Annotated[int, Field(...)]` (parser must unpack Field)
170 - `Annotated[int, *Field(...)]` (PEP-646)
171 """ # noqa: trailing-whitespace
173 __slots__ = ()
175 def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
176 super().__init_subclass__(*args, **kwargs)
177 if cls.__iter__ is GroupedMetadata.__iter__:
178 raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
180 def __iter__(self) -> Iterator[BaseMetadata]:
181 raise NotImplementedError
184@dataclass(frozen=True, **KW_ONLY, **SLOTS)
185class Interval(GroupedMetadata):
186 """Interval can express inclusive or exclusive bounds with a single object.
188 It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
189 are interpreted the same way as the single-bound constraints.
190 """
192 gt: Union[SupportsGt, None] = None
193 ge: Union[SupportsGe, None] = None
194 lt: Union[SupportsLt, None] = None
195 le: Union[SupportsLe, None] = None
197 def __iter__(self) -> Iterator[BaseMetadata]:
198 """Unpack an Interval into zero or more single-bounds."""
199 if self.gt is not None:
200 yield Gt(self.gt)
201 if self.ge is not None:
202 yield Ge(self.ge)
203 if self.lt is not None:
204 yield Lt(self.lt)
205 if self.le is not None:
206 yield Le(self.le)
209@dataclass(frozen=True, **SLOTS)
210class MultipleOf(BaseMetadata):
211 """MultipleOf(multiple_of=x) might be interpreted in two ways:
213 1. Python semantics, implying ``value % multiple_of == 0``, or
214 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
216 We encourage users to be aware of these two common interpretations,
217 and libraries to carefully document which they implement.
218 """
220 multiple_of: Union[SupportsDiv, SupportsMod]
223@dataclass(frozen=True, **SLOTS)
224class MinLen(BaseMetadata):
225 """
226 MinLen() implies minimum inclusive length,
227 e.g. ``len(value) >= min_length``.
228 """
230 min_length: Annotated[int, Ge(0)]
233@dataclass(frozen=True, **SLOTS)
234class MaxLen(BaseMetadata):
235 """
236 MaxLen() implies maximum inclusive length,
237 e.g. ``len(value) <= max_length``.
238 """
240 max_length: Annotated[int, Ge(0)]
243@dataclass(frozen=True, **SLOTS)
244class Len(GroupedMetadata):
245 """
246 Len() implies that ``min_length <= len(value) <= max_length``.
248 Upper bound may be omitted or ``None`` to indicate no upper length bound.
249 """
251 min_length: Annotated[int, Ge(0)] = 0
252 max_length: Optional[Annotated[int, Ge(0)]] = None
254 def __iter__(self) -> Iterator[BaseMetadata]:
255 """Unpack a Len into zone or more single-bounds."""
256 if self.min_length > 0:
257 yield MinLen(self.min_length)
258 if self.max_length is not None:
259 yield MaxLen(self.max_length)
262@dataclass(frozen=True, **SLOTS)
263class Timezone(BaseMetadata):
264 """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
266 ``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
267 ``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
268 tz-aware bug any timezone is allowed.
270 You may also pass a specific timezone string or timezone object such as
271 ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
272 you only allow a specific timezone, though we note that this is often
273 a symptom of poor design.
274 """
276 tz: Union[str, timezone, EllipsisType, None]
279@dataclass(frozen=True, **SLOTS)
280class Predicate(BaseMetadata):
281 """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
283 Users should prefer statically inspectable metadata, but if you need the full
284 power and flexibility of arbitrary runtime predicates... here it is.
286 We provide a few predefined predicates for common string constraints:
287 ``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
288 ``IsDigit = Predicate(str.isdigit)``. Users are encouraged to use methods which
289 can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
291 Some libraries might have special logic to handle certain predicates, e.g. by
292 checking for `str.isdigit` and using its presence to both call custom logic to
293 enforce digit-only strings, and customise some generated external schema.
295 We do not specify what behaviour should be expected for predicates that raise
296 an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
297 skip invalid constraints, or statically raise an error; or it might try calling it
298 and then propogate or discard the resulting exception.
299 """
301 func: Callable[[Any], bool]
304StrType = TypeVar("StrType", bound=str)
306LowerCase = Annotated[StrType, Predicate(str.islower)]
307UpperCase = Annotated[StrType, Predicate(str.isupper)]
308IsDigits = Annotated[StrType, Predicate(str.isdigit)]
309IsAscii = Annotated[StrType, Predicate(str.isascii)]