-
-
Notifications
You must be signed in to change notification settings - Fork 864
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
add generics type parameters to ImmutableMultiDict #1449
add generics type parameters to ImmutableMultiDict #1449
Conversation
In the trade-off between readability vs. type-strictness I'm personally happy enough to stick with the under-defined "Any" cases, because I just find it so awkwardly complicated to read through the annotations. (And because I don't have strong objections to it, but just gonna pitch that one out there. |
Yeah, I see that side of this as well. What for me makes this worth it is that:
So we do give up some readability in Starlette's codebase, but we will be improving legibility for downstream classes and consumers of the codebase. But that's just my thought process / personal bias, I'll understand if others are not of the same opinion. |
As a motivating example of what this can do for users of Starlette and downstream packages: from starlette.requests import Request
async def func(req: Request) -> str:
form = await req.form()
file_or_string = form["field"]
if isinstance(file_or_string, str):
return file_or_string
return (await file_or_string.read()).decode() Running mypy on this:
Because mypy thinks that And from an IDE's perspective: I get no auto-complete for On the other hand, this is what it looks like on #1403 (which this current PR is working towards): IDE examples are on VSCode + Pylance (which is what a large percentage of devs use nowadays) |
Always appreciate seeing a good user-centred "this is why we might choose to make this change". 😎 |
starlette/datastructures.py
Outdated
typing.List[typing.Tuple[typing.Any, typing.Any]], | ||
"ImmutableMultiDict[_KT, _VT_co]", | ||
typing.Mapping[_KT, _VT_co], | ||
typing.Sequence[typing.Tuple[_KT, _VT_co]], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for reference about the "why": https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance
@@ -254,7 +261,7 @@ def __init__( | |||
if kwargs: | |||
value = ( | |||
ImmutableMultiDict(value).multi_items() | |||
+ ImmutableMultiDict(kwargs).multi_items() | |||
+ ImmutableMultiDict(kwargs).multi_items() # type: ignore[operator] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't know the returned value of the operator +
when there's a sum of two ImmutableMultiDict
. That's why we ignore this error here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is clear to me, and after I've start using a more strict type checking environment, I do feel @adriangb 's pain.
I'm not sure if _T
, _KT
and _VT_co
are the best names for those, even if this convention is spreadly used... Maybe DefaultType
, KeyType
and ValueType
can improve the readability, and @tomchristie will see it with better eyes (?).
I'm 👍 on this, happy to make the change or accept suggestions from either of you. |
Rename done |
starlette/datastructures.py
Outdated
) -> typing.Union[_ValueType, _DefaultType]: | ||
... # pragma: no cover | ||
|
||
def get(self, key: _KeyType, default: typing.Any = None) -> typing.Any: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes
I'm not quite sure if there are other benefits to implementing mixin methods like .get
. MutableMapping
already implements them. And they are already of the correct type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we can just delete get
here! Great point. This makes this a PR that (maybe) changes behavior instead of just typing, but I think that's worth it 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank again for this, great catch!
assert len(q.get("abc")) == 0 | ||
val = q.get("abc") | ||
assert val is not None | ||
assert len(val) == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just to make mypy happy
@@ -382,7 +382,7 @@ def update( | |||
self._dict.update(value) | |||
|
|||
|
|||
class QueryParams(ImmutableMultiDict): | |||
class QueryParams(ImmutableMultiDict[str, str]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make MyPy happy, previously it was picking up str
from ImmutableMultiDict
but now that ImmutableMultiDict
is generic it was complaining about an unknown type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also means we now get QueryParams(...).get(...)
to be a str
, rather than Any
. Which is neat and correct, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! There's a lot of downstream work from this (like actually fixing Form
), but I wanted to keep this PR as narrowly scoped as possible. Hopefully we can approve and merge those downstream PRs quicker once the basic structure for stronger typing is added via this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like this as well. I had already found myself in cases when the default typing on Form
required me to do unecessary assert
or isinstance
checks to benefit from typing.
Co-authored-by: Florimond Manca <florimond.manca@protonmail.com>
Co-authored-by: Florimond Manca <florimond.manca@protonmail.com>
Slice of #1403
The goal here is to make these data structures more reusable and fix some currently under specified type annotations that make type checkers unhappy downstream