PEP 692: Using TypedDict for more precise **kwargs typing

I’m trying to use Unpack, but I’m finding it too limited for my use case. I asked about it on StackOverflow and it was suggested that I bring it up here, because PEP 692 isn’t yet final.

There are two ways to specify types for **kwargs:

  • The new way, with TypedDict and Unpack. This allows only predetermined argument names, and each can have a different type.
  • The normal way, like **kwargs: T, where T is some type. This allows any argument names, and they all have to be of type T.

The issue is that there are many practical use cases where you might want to do both. You might want to specify some arguments types with TypedDict, but also allow any other argument names, and for those arguments, they should all be of some type T.

For example, suppose I have this function:

def foo(*, x: bool, **kwargs: int) -> None:
    ...

And suppose it is called from many other functions, like this:

def f(a: str, b: str, x: bool, **kwargs: int) -> None:
    print(a + b)
    foo(x=x, **kwargs)

I would like it if I did not have to put x in f’s function signature, and also not explicitly pass x to foo().

I wish I could do something like this with TypedDict and Unpack, where _other_ represents any other argument names that are passed in:

class FooKwargs(TypedDict):
    x: bool
    _other_: int

def foo(**kwargs: Unpack[FooKwargs]) -> None:
    ...

def f(a: str, b: str, **kwargs: Unpack[FooKwargs]) -> None:
    print(a + b)
    foo(**kwargs)

It’s worth mentioning that, in a response to my SO question, @sterliakov suggested some other possible paths:

There was a sort of similar addition to dataclasses in 3.10: KW_ONLY constant that makes following attributes kw-only in constructor, so a precedent exists. It may also work as a metaclass argument (so that you do smth like class MyArgs(TypedDict, rest=int) ).