From 0e6d4c0f1a4fbcba511bae2393b4f30eafb408fd Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Mon, 11 Mar 2024 16:34:10 -0700 Subject: [PATCH 1/4] `Field.number(kind=...)` supports any function that returns a number For example, in our internal codebase, we have a function: ```python def decimal_from_str(value: str, parens_negate: bool = False) -> Decimal: ... amount = Field.number(kind=decimal_from_str) ``` --- src/klein/_form.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/klein/_form.py b/src/klein/_form.py index 8f09de91..da93a332 100644 --- a/src/klein/_form.py +++ b/src/klein/_form.py @@ -12,7 +12,7 @@ NoReturn, Optional, Sequence, - Type, + TypeVar, cast, ) @@ -29,6 +29,7 @@ from ._app import KleinRenderable, _call from ._decorators import bindable +from ._typing_compat import Protocol from .interfaces import ( EarlyExit, IDependencyInjector, @@ -41,6 +42,20 @@ ) +_T = TypeVar("_T", contravariant=True) + + +class _Numeric(Protocol[_T]): + def __float__(self) -> float: + ... + + def __lt__(self, other: _T) -> bool: + ... + + def __gt__(self, other: _T) -> bool: + ... + + class CrossSiteRequestForgery(Resource): """ Cross site request forgery detected. Request aborted. @@ -258,9 +273,9 @@ def hidden(cls, name: str, value: str, **kw: Any) -> "Field": @classmethod def number( cls, - minimum: Optional[int] = None, - maximum: Optional[int] = None, - kind: Type = float, + minimum: Optional[_Numeric] = None, + maximum: Optional[_Numeric] = None, + kind: Callable[[str], _Numeric] = float, **kw: Any, ) -> "Field": """ From e058d4955e1d7afd512f16bcc9434b71251e3dea Mon Sep 17 00:00:00 2001 From: Glyph Date: Mon, 11 Mar 2024 17:29:28 -0700 Subject: [PATCH 2/4] de-genericize Numeric type --- src/klein/_form.py | 51 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/klein/_form.py b/src/klein/_form.py index da93a332..92295e2a 100644 --- a/src/klein/_form.py +++ b/src/klein/_form.py @@ -14,6 +14,7 @@ Sequence, TypeVar, cast, + overload, ) import attr @@ -43,19 +44,23 @@ _T = TypeVar("_T", contravariant=True) +_Self = TypeVar("_Self") -class _Numeric(Protocol[_T]): +class _Numeric(Protocol): def __float__(self) -> float: ... - def __lt__(self, other: _T) -> bool: + def __lt__(self: _Self, other: _Self) -> bool: ... - def __gt__(self, other: _T) -> bool: + def __gt__(self: _Self, other: _Self) -> bool: ... +_N = TypeVar("_N", bound=_Numeric) + + class CrossSiteRequestForgery(Resource): """ Cross site request forgery detected. Request aborted. @@ -270,12 +275,46 @@ def hidden(cls, name: str, value: str, **kw: Any) -> "Field": **kw, ).maybeNamed(name) + @overload + @classmethod + def number( + cls, + minimum: Optional[float] = None, + maximum: Optional[float] = None, + kind: Callable[[str], float] = float, + **kw: Any, + ) -> "Field": + ... + + @overload + @classmethod + def number( + cls, + minimum: Optional[_N] = None, + maximum: Optional[_N] = None, + *, + kind: Callable[[str], _N], + **kw: Any, + ) -> "Field": + ... + + @overload + @classmethod + def number( + cls, + minimum: _N, + maximum: _N, + kind: Callable[[str], _N], + **kw: Any, + ) -> "Field": + ... + @classmethod def number( cls, - minimum: Optional[_Numeric] = None, - maximum: Optional[_Numeric] = None, - kind: Callable[[str], _Numeric] = float, + minimum: Optional[_N] = None, + maximum: Optional[_N] = None, + kind: Callable[[str], _N] = float, # type:ignore[assignment] **kw: Any, ) -> "Field": """ From 090cfb2332a6e357c7ef1015a2021d5db41b9e75 Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Tue, 12 Mar 2024 08:23:56 -0700 Subject: [PATCH 3/4] Exclude `...` lines from coverage https://github.com/twisted/twisted/blob/dabf4622f0ecdd4f0587ae103b23982c4c83bd77/.coveragerc#L21 --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 93f23067..8049d33a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -15,3 +15,4 @@ source= exclude_lines = pragma: no cover if TYPE_CHECKING: + \s*\.\.\.$ From 030e6044aaecb0d951e4cf8a4e0cf09a6b053792 Mon Sep 17 00:00:00 2001 From: Glyph Date: Tue, 12 Mar 2024 11:34:16 -0700 Subject: [PATCH 4/4] clean up now-unused TypeVar _T --- src/klein/_form.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/klein/_form.py b/src/klein/_form.py index 92295e2a..3bcc6183 100644 --- a/src/klein/_form.py +++ b/src/klein/_form.py @@ -43,7 +43,6 @@ ) -_T = TypeVar("_T", contravariant=True) _Self = TypeVar("_Self")