Repository navigation
Allow using TypedDict for more precise typing of **kwds #4441
Description
Activity
The proposed syntax is problematic, as it could also mean a homogeneous
**optionswhere each value is anOptions. We could work around this with some extra syntax, but it could look messy. Here's an idea:from mypy_extensions import Expand ... def fun(x: int, *, **options: Expand[Options]) -> None: ...
Expand[...]could also be used for variadic type variables in case we add support for them (for example,Callable[[int, Expand[X]], None]ifXis a variadic type variable).I don't like the idea of implicitly assuming
total=False.Reacted by Ellis Breen, Neil Girdhar, Brenton Partridge, Byeonghoon Yoo, Lucas Menicucci, James, David C., Alex Rattray and Redowan DelowarYes, the syntax would be ambiguous. Introducing
Expandonly for this purpose may be not worth it, but using it also for variadic type variables is a good idea (and it feels natural).I don't like the idea of implicitly assuming
total=False.OK, let's then drop this. In any case it is easy to add it explicitly (I think it will be used often, since keyword arguments are often optional).
Raising priority to normal, since this is a very common request (and we have agreed on using
Expand[...]).Reacted by Marti Raudsepp and Carlos CastroReacted by Ellis Breen, Adam Mičuda, Nicholas Guyett, James, Carlos Castro, Sudip Roy and Redowan Delowar@ilevkivskyi @JukkaL I'm in pretty desperate need of this so I might be able to justify working on this to my employer. Do you have any ideas about how to implement this? If not, are there areas of the code-base that I should look at in order to understand how to approach the problem?
Reacted by Mattwmaster58 and Neil GirdharAlthough this is not super tricky feature, it may be not the best choice for the first contribution, unless you like to live dangerously :-)
You will need to add a new special form
Expand[...]tomypy_extensions(whereTypedDictlives). This should be allowed only as annotation to**kwars, and must have only one type parameter which is aTypedDict. Then look at all things that contain wordsformal_to_actualand/oractual_to_formal, they might need to be updated.Also it may be not the best time to do this, since we are in the middle of a big refactoring of semantic analyzer (one of the first steps in mypy logic, that binds names to symbols). It will take two-three more weeks to finish. Otherwise you would need to repeat some parts of logic twice: in
mypy/semanal.pyand inmypy/newsemanal/semanal.py.@ilevkivskyi, hmm maybe I'll hold off then. Let me know if there's any work a new contributor could provide in order to help out.
@rmorshea You can try one of these: good-first-issue (unless they are too easy/not interesting).
I got around to hacking something together in #7051. It's obviously very rough but hopefully I can build something off of that.
BTW, I think
Expandcompletely breaks the typesystem. What does this do?x: Expand[dict[str, int]] = 42
You could argue that this should work because having a type that just works for
**kwargsis ugly. You could also argue that this is nonsense and should not be allowed.I think
Expandcannot be called a type. IMO the proper solution would be to introduce a breaking change in the typesystem and make**kwargs: XexpandXautomatically. Code that looks like this right now:def foo(**kwargs: int): ...
would have to be changed to:
def foo(**kwargs: Dict[str, int]): ...
Reacted by Janka Uryga, Anton Agestam, herr kaste, Kyle Agronick, James Adams, MustNull, Charlie Denton, Bryan Helmig, Nils K, Thijs Damsma and 6 more25 remaining items
If you don't provide
baras a kwarg, it is an error becausebaris a required field in the TypedDict.Ah but we can specify totality like:
class MyKwargs(TypedDict, total=False): foo: str bar: intSo
baror any key is not required anymore. DoesUnpackstill report this as an error?Unpackdoes not supportUnion.I now realize it's not
Unpackthat should supportUnion, it should beTypedDict. AndTypedDictalready does.MyTypedDict | OtherTypedDictmeans either of the two, not combining them. Maybe what I meant was likeUnpack[MyTypedDict, OtherTypedDict]where it unpacks multipleTypedDicts. But I guess that would complicate things.Thank you!
I am interested in sponsoring work on support for
Unpack[T]in mypy if anyone is interested in putting up a PR for this and would find sponsorship helpful.Reacted by bruno messiasthis should also consider partial views on a class
for example if i had a type like
@dataclass class MyModel: id: uuid name: str hostname: str memory_size: int
and i want to declare helpers like
# bad names Magic = SubsetTypedDict(MyModel, exclude=["id"], total=False) def wait_for_update(model_or_id: Model|uuid, **kw: Magic) -> Model: ...
Reacted by Tony NarlockIf you don't provide
baras a kwarg, it is an error becausebaris a required field in the TypedDict.Unpackdoes not supportUnion.This works for me
from typing import Callable, TypedDict, Union from typing_extensions import NotRequired, TypedDict from typing_extensions import Unpack class MyFuncKwargs(TypedDict): a: NotRequired[str] b: NotRequired[int] def pint(b: int) -> None: print(b) def pstr(a: str) -> None: print(a) def my_func(f:Union[Callable[[str], None], Callable[[int], None]], **kwargs: Unpack[MyFuncKwargs]) -> None: print(kwargs) return my_func(pstr, a="a") my_func(pint, b=2) my_func(pint, c=2) my_func(pint, a=2)
/home/devmessias/phd/pkgs/pyastrx-proj/pyastrx-my/pyright.py /home/devmessias/phd/pkgs/pyastrx-proj/pyastrx-my/pyright.py:26:15 - error: No parameter named "c" (reportGeneralTypeIssues) /home/devmessias/phd/pkgs/pyastrx-proj/pyastrx-my/pyright.py:27:17 - error: Argument of type "Literal[2]" cannot be assigned to parameter "a" of type "str" in function "my_func" "Literal[2]" is incompatible with "str" (reportGeneralTypeIssues) 2 errors, 0 warnings, 0 informations Completed in 0.877sec
Are there any prospects to bring Unpack to mypy?
I am interested in sponsoring work on support for
Unpack[T]in mypy if anyone is interested in putting up a PR for this and would find sponsorship helpful.Would be great to have unpack available in mypy
Reacted by Tony Narlock, Arseniy Terekhin and Kevin Kirschethis should also consider partial views on a class
for example if i had a type like
@dataclass class MyModel: id: uuid name: str hostname: str memory_size: int
and i want to declare helpers like
# bad names Magic = SubsetTypedDict(MyModel, exclude=["id"], total=False) def wait_for_update(model_or_id: Model|uuid, **kw: Magic) -> Model: ...
@RonnyPfannschmidt Could you make this into an independent issue for this so it gets attention?
dataclasscan be unpacked to act as a TypedDictexcludefields- Being able to pass via
**kw: Magic(conformance with Allow using TypedDict for more precise typing of **kwds #4441
P.S. if I understand correctly, you want to be able to take dictionary data,, and hydrate models / dataclasses via
**kw. Is that an accurate depiction of what you're getting at?@tony i don't want to hydrate, i want to correctly type helper functions for querying, waiting and other actions that replicate all if not most fields of types
Reacted by bruno messiasIt seems
Unpackfromtyping_extensionsdoes more than whatPEP 646originally proposed (i.e. originally only for*args/TypeVarTuple, not also**kwargs/TypedDict). It now seems to also do whatExpandwas proposed for inPEP 589. This is perfectly fine by me (I love havingUnpack), but I have some questions:- Is
ExpandinPEP 646also going to be implemented? - Will
PEP 646be updated to indicate thatUnpackmay also be used for variadicTypeDicts?
I think ideally there should only be 1 name and it should work for both types. (It would be confusing, for instance, to have both
ExpandandUnpack).ASIDE:
-
If it is agreed there should be one name, my vote is for
Unpack, simply because it's already implemented and the terminology matches Python terminology (we speak of "packing"/"unpacking" variables, rather than "collapsing"/"expanding" variables). -
PEP 646appears to have implemented a grammar change to utilize starred expressions in indeces beginning in Python 3.11. Could this be expanded to also implement double-starred expressions? There've been discussions here that implementing a**syntax would require a grammar change. I'm wondering if expanding the grammar change for*would make this less work? This would be a nice-to-have (much like how the grammar was updated to support built-ins in the syntax directly rather than requiringfrom typing import UppercaseBuiltinand pipes forUnion).
- Is
There is a new PEP 692 underway for this functionality. It proposes to add
**syntax and useUnpackfor backward compatibility. The new PEP is under review, so if you want to comment on it, I'm sure the feedback would be welcome.Pyright has provisional support for PEP 692 if you want to play with it.
A few additional notes...
PEP 589 didn't specifyExpand. The only reference toExpandis in the rejected alternatives section. There are currently no plans to introduceExpandto thetypingmodule.You asked whether PEP 646 would be updated to cover new usage of
Unpack. That's not how PEPs work. They are intended to be point-in-time proposals, not live documentation of new functionality, so it wouldn't be appropriate to update PEP 646 to cover new uses ofUnpack. That will be covered in PEP 692.Reacted by Alex Rattray, Jongwook Choi, herr kaste, Steven Seguin, adam-grant-hendry, Noah and Spencer Phillip YoungThere is a new python/peps#2620 underway for this functionality. It proposes to add ** syntax and use Unpack for backward compatibility. The new PEP is under review, so if you want to comment on it, I'm sure the feedback would be welcome.
Pyright has provisional support for PEP 692 if you want to play with it.
Hurray! 🎉
I'll be sure to add some comments (would love to see dual support of
UnpackforTypeVarTupleandTypedDict).PEP 589 didn't specify Expand. The only reference to Expand is in the rejected alternatives section. There are currently no plans to introduce Expand to the typing module.
Yes, thank you for clarifying it was rejected (good catch). Thank you for the clarification!
You asked whether PEP 646 would be updated to cover new usage of Unpack. That's not how PEPs work. They are intended to be point-in-time proposals, not live documentation of new functionality, so it wouldn't be appropriate to update PEP 646 to cover new uses of Unpack. That will be covered in PEP 692.
Yes, another good catch. I wasn't aware of
PEP 692, which is why I was asking. However, I've seen "update" notes added to PEPs before. For example, PEP 484 has several:If type hinting proves useful in general, a syntax for typing variables may be provided in a future Python version. (UPDATE: This syntax was added in Python 3.6 through PEP 526.)
Could an update note be added to
PEP 484if and whenPEP 692is approved? This would help readers and add traceability between the two. Something as simple asUPDATE: Usage of
Unpackalso for**kwargsandTypedDictis proposed inPEP 692.would be great.
- added a commit that references this issue
on Aug 22, 2022 - added a commit that references this issue
on Sep 9, 2022 Since kwargs values can have defaults. Unpacking with defaults would be nice to support.
Is it not possible to Unpack a pydantic(BaseModel) ?
This would make integrations with libraries like strawberry easy.For example
class AllFilters(BaseModel): beg_age: int = 21 end_age: int = 55 beg_height: int = 120 end_height: int = 240 search_term: str = ' ' gender: str = 'female' @strawberry.type class Query: @strawberry.field async def get_filtered_users(self, **kwargs: Unpack[AllFilters]) -> List[User]: '''This function gets all users that match the filter criteria Strong default values are set to prevent empty results '''
There are some situations where a user wants to have more precisely typed
**kwds. Current syntax only allows homogeneous**kwds:However, in situations with many heterogeneous options listing all options in the signature could be verbose and will require rewriting some existing code. There is a vague idea to allow
TypedDictfor such situations. For example:Maybe for such cases the
TypedDictused should be automatically understood as defined withtotal=False. Also it is worth mentioning that this feature will allow reusing theTypedDicts in modules where several functions have same (or similar) option sets.