From f7dead4f46f6c70f9f505e1f5f4ebeff1fc7728f Mon Sep 17 00:00:00 2001 From: Anton Agestam Date: Sat, 22 Jan 2022 20:57:43 +0100 Subject: [PATCH] Integrate numerary to gain support for numeric tower --- .pre-commit-config.yaml | 1 + src/phantom/interval.py | 19 +++++++++---------- src/phantom/predicates/interval.py | 18 ++++++++++-------- src/phantom/predicates/numeric.py | 30 ++++++++++++++++-------------- src/phantom/sized.py | 6 ++++-- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5edb40f..bf3201f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,6 +61,7 @@ repos: - phonenumbers>=8.12.41 - pydantic - types-setuptools + - numerary - repo: https://github.com/mgedmin/check-manifest rev: "0.47" hooks: diff --git a/src/phantom/interval.py b/src/phantom/interval.py index 7d1adca..e851ed1 100644 --- a/src/phantom/interval.py +++ b/src/phantom/interval.py @@ -25,8 +25,9 @@ def take_portion(portion: Portion, whole: Natural) -> float: from typing import Any from typing import TypeVar -from typing import Union +from numerary.types import IntegralLike +from numerary.types import RealLike from typing_extensions import Final from typing_extensions import Protocol @@ -36,7 +37,7 @@ def take_portion(portion: Portion, whole: Natural) -> float: from .schema import Schema from .utils import resolve_class_attr -N = TypeVar("N", bound=float) +N = TypeVar("N", bound=RealLike[IntegralLike[int]]) Derived = TypeVar("Derived", bound="Interval") @@ -49,9 +50,7 @@ def __call__(self, a: N, b: N) -> Predicate[N]: neg_inf: Final = float("-inf") -# See issue as to why the numeric tower isn't used for kind here. -# https://github.com/python/mypy/issues/3186 -class Interval(Phantom[float], bound=Union[int, float], abstract=True): +class Interval(Phantom[float], bound=RealLike, abstract=True): """ Base class for all interval types, providing the following class arguments: @@ -63,14 +62,14 @@ class Interval(Phantom[float], bound=Union[int, float], abstract=True): """ __check__: IntervalCheck - __low__: float - __high__: float + __low__: RealLike + __high__: RealLike def __init_subclass__( cls, check: IntervalCheck | None = None, - low: float = neg_inf, - high: float = inf, + low: RealLike = neg_inf, + high: RealLike = inf, **kwargs: Any, ) -> None: resolve_class_attr(cls, "__low__", low) @@ -90,7 +89,7 @@ def parse(cls: type[Derived], instance: object) -> Derived: ) -def _format_limit(value: float) -> str: +def _format_limit(value: RealLike) -> str: if value == inf: return "∞" if value == neg_inf: diff --git a/src/phantom/predicates/interval.py b/src/phantom/predicates/interval.py index 5f9a50a..f2029a7 100644 --- a/src/phantom/predicates/interval.py +++ b/src/phantom/predicates/interval.py @@ -3,53 +3,55 @@ strictly between the upper and lower bounds. There are corresponding phantom types that use these predicates in :py:mod:`phantom.interval`. """ +from numerary.types import RealLike + from .base import Predicate from .utils import bind_name -def open(low: float, high: float) -> Predicate[float]: +def open(low: RealLike, high: RealLike) -> Predicate[RealLike]: """ Create a predicate that succeeds when its argument is in the range ``(low, high)``. """ @bind_name(open, low, high) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return low <= value <= high return check -def open_closed(low: float, high: float) -> Predicate[float]: +def open_closed(low: RealLike, high: RealLike) -> Predicate[RealLike]: """ Create a predicate that succeeds when its argument is in the range ``(low, high]``. """ @bind_name(open_closed, low, high) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return low <= value < high return check -def closed_open(low: float, high: float) -> Predicate[float]: +def closed_open(low: RealLike, high: RealLike) -> Predicate[RealLike]: """ Create a predicate that succeeds when its argument is in the range ``[low, high)``. """ @bind_name(closed_open, low, high) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return low < value <= high return check -def closed(low: float, high: float) -> Predicate[float]: +def closed(low: RealLike, high: RealLike) -> Predicate[RealLike]: """ Create a predicate that succeeds when its argument is in the range ``[low, high]``. """ @bind_name(closed, low, high) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return low < value < high return check diff --git a/src/phantom/predicates/numeric.py b/src/phantom/predicates/numeric.py index ce43a69..4e1f898 100644 --- a/src/phantom/predicates/numeric.py +++ b/src/phantom/predicates/numeric.py @@ -1,88 +1,90 @@ +from numerary.types import RealLike + from .base import Predicate from .boolean import negate from .generic import equal from .utils import bind_name -def less(n: float) -> Predicate[float]: +def less(n: RealLike) -> Predicate[RealLike]: """ Create a new predicate that succeeds when its argument is strictly less than ``n``. """ @bind_name(less, n) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return value < n return check -def le(n: float) -> Predicate[float]: +def le(n: RealLike) -> Predicate[RealLike]: """ Create a new predicate that succeeds when its argument is less than or equal to ``n``. """ @bind_name(le, n) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return value <= n return check -def greater(n: float) -> Predicate[float]: +def greater(n: RealLike) -> Predicate[RealLike]: """ Create a new predicate that succeeds when its argument is strictly greater than ``n``. """ @bind_name(greater, n) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return value > n return check -def ge(n: float) -> Predicate[float]: +def ge(n: RealLike) -> Predicate[RealLike]: """ Create a new predicate that succeeds when its argument is greater than or equal to ``n``. """ @bind_name(ge, n) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return value >= n return check -def positive(n: float) -> bool: +def positive(n: RealLike) -> bool: """Return :py:const:`True` when ``n`` is strictly greater than zero.""" return greater(0)(n) -def non_positive(n: float) -> bool: +def non_positive(n: RealLike) -> bool: """Return :py:const:`True` when ``n`` is less than or equal to zero.""" return le(0)(n) -def negative(n: float) -> bool: +def negative(n: RealLike) -> bool: """Return :py:const:`True` when ``n`` is strictly less than zero.""" return less(0)(n) -def non_negative(n: float) -> bool: +def non_negative(n: RealLike) -> bool: """Return :py:const:`True` when ``n`` is greater than or equal to zero.""" return ge(0)(n) -def modulo(n: float, p: Predicate[float]) -> Predicate[float]: +def modulo(n: RealLike, p: Predicate[RealLike]) -> Predicate[RealLike]: """ Create a new predicate that succeeds when its argument modulo ``n`` satisfies the given predicate ``p``. """ @bind_name(modulo, n, p) - def check(value: float) -> bool: + def check(value: RealLike) -> bool: return p(value % n) return check diff --git a/src/phantom/sized.py b/src/phantom/sized.py index 05855ab..5cdfd0c 100644 --- a/src/phantom/sized.py +++ b/src/phantom/sized.py @@ -24,6 +24,8 @@ class SpecificSize(PhantomSized[int], len=interval.open(5, 10)): from typing import Sized from typing import TypeVar +from numerary.types import RealLike + # We attempt to import _ProtocolMeta from typing_extensions to support Python 3.7 but # fall back the typing module to support Python 3.8+. This is the closest I could find # to documentation of _ProtocolMeta. @@ -77,9 +79,9 @@ class PhantomSized( bound=SizedIterable, abstract=True, ): - """Takes class argument ``len: Predicate[float]``.""" + """Takes class argument ``len: Predicate[RealLike]``.""" - def __init_subclass__(cls, len: Predicate[float], **kwargs: Any) -> None: + def __init_subclass__(cls, len: Predicate[RealLike], **kwargs: Any) -> None: super().__init_subclass__( predicate=boolean.both( boolean.negate(generic.of_type(mutable)),