Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field.number(kind=...) supports any function that returns a number #709

Merged
merged 5 commits into from
Mar 12, 2024

Conversation

rouge8
Copy link
Contributor

@rouge8 rouge8 commented Dec 21, 2023

For example, in our internal codebase, we have a function:

def decimal_from_str(value: str, parens_negate: bool = False) -> Decimal:
    ...

amount = Field.number(kind=decimal_from_str)

@rouge8 rouge8 requested a review from a team as a code owner December 21, 2023 19:15
Copy link
Member

@glyph glyph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SupportsFloat is a weird type for this (presumably in the context of Decimal it's quite intentionally not being float-ified), but it's more correct than Type given how it is used, so we should probably merge this for now.

@glyph glyph enabled auto-merge February 8, 2024 18:08
@glyph glyph self-requested a review February 8, 2024 18:13
@rouge8
Copy link
Contributor Author

rouge8 commented Feb 8, 2024

Hmm, I didn't notice mypy isn't happy with this. We might need our own protocol with __float__ and the comparison operators. @glyph does that sound reasonable?

Copy link
Member

@glyph glyph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, looks like the mypy failure here is real. @rouge8 looks like SupportsFloat is sufficiently weird that it's actually wrong. Can you pick a type here that at least makes mypy happy? The best option here would be to write a numeric protocol, set it as a bound on a TypeVar, then have minimum, maximum, and kind all reference that var so as to ensure consistency.

@glyph
Copy link
Member

glyph commented Feb 8, 2024

Hmm, I didn't notice mypy isn't happy with this. We might need our own protocol with __float__ and the comparison operators. @glyph does that sound reasonable?

Is __float__ really even necessary here? It feels like just the comparison would do it?

@rouge8
Copy link
Contributor Author

rouge8 commented Feb 8, 2024

Hmm, I didn't notice mypy isn't happy with this. We might need our own protocol with __float__ and the comparison operators. @glyph does that sound reasonable?

Is __float__ really even necessary here? It feels like just the comparison would do it?

That's a good point. The other mypy issue is str vs AnyStr, which I opened #708 for

@rouge8 rouge8 disabled auto-merge March 11, 2024 23:09
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)
```
Copy link

codecov bot commented Mar 11, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.07%. Comparing base (0f3597e) to head (030e604).

Additional details and impacted files
@@           Coverage Diff           @@
##            trunk     #709   +/-   ##
=======================================
  Coverage   99.07%   99.07%           
=======================================
  Files          46       46           
  Lines        3986     4002   +16     
  Branches      531      540    +9     
=======================================
+ Hits         3949     3965   +16     
  Misses         23       23           
  Partials       14       14           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@rouge8
Copy link
Contributor Author

rouge8 commented Mar 11, 2024

@glyph updated with a _Numeric protocol! I tried setting it as a bound on a TypeVar and using that for minimum, maximum, and kind, but mypy complains and I couldn't figure out how to make it happy:

diff --git a/src/klein/_form.py b/src/klein/_form.py
index da93a33..04de050 100644
--- a/src/klein/_form.py
+++ b/src/klein/_form.py
@@ -56,6 +56,9 @@ class _Numeric(Protocol[_T]):
         ...
 
 
+_N = TypeVar("_N", bound=_Numeric)
+
+
 class CrossSiteRequestForgery(Resource):
     """
     Cross site request forgery detected.  Request aborted.
@@ -273,9 +276,9 @@ class 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,
         **kw: Any,
     ) -> "Field":
         """
src/klein/_form.py:281:37: error: Incompatible default for argument "kind" (default has type "type[float]", argument has type
"Callable[[str], _N]")  [assignment]
            kind: Callable[[str], _N] = float,
                                        ^~~~~
Found 1 error in 1 file (checked 50 source files)

@glyph
Copy link
Member

glyph commented Mar 12, 2024

@rouge8 to try to better explain the structure I was looking for, I opened a PR against your PR: rouge8#1 . Numerics aren't generic, because a numeric has to work with itself and (in this context, at least) only itself.

The goofy verbose @overload is just due to the fact that mypy can't understand the influence of default parameters on type signatures, i.e. python/mypy#3737 .

@glyph
Copy link
Member

glyph commented Mar 12, 2024

Re: the coverage failure, I don't really have time to look at it now, but there should be a coverage ignore pattern you can grab for ... in protocols somewhere, in some twisted project, that should be applied to this so it's not getting dinged for not covering protocol implementations :)

@rouge8
Copy link
Contributor Author

rouge8 commented Mar 12, 2024

Ohhh, thanks for the fix! I'll add the coverage ignore tomorrow during the workday.

@@ -15,3 +15,4 @@ source=
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
\s*\.\.\.$
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rouge8 rouge8 requested a review from glyph March 12, 2024 18:10
Copy link
Member

@glyph glyph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I guess my change ended up being the change here :)

src/klein/_form.py Outdated Show resolved Hide resolved
@glyph glyph merged commit a8c9e69 into twisted:trunk Mar 12, 2024
25 checks passed
@rouge8 rouge8 deleted the field-number-kind branch March 12, 2024 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants