add generics type parameters to ImmutableMultiDict#1449
add generics type parameters to ImmutableMultiDict#1449adriangb merged 26 commits intoKludex:masterfrom
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.
Just for reference about the "why": https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance
| value = ( | ||
| ImmutableMultiDict(value).multi_items() | ||
| + ImmutableMultiDict(kwargs).multi_items() | ||
| + ImmutableMultiDict(kwargs).multi_items() # type: ignore[operator] |
There was a problem hiding this comment.
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.
Kludex
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
This is just to make mypy happy
|
|
||
|
|
||
| class QueryParams(ImmutableMultiDict): | ||
| class QueryParams(ImmutableMultiDict[str, str]): |
There was a problem hiding this comment.
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.
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.
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.
florimondmanca
left a comment
There was a problem hiding this comment.
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