Introduce Partial for TypedDict

Bit more thought and comparison with typescripts functions. Assuming it were possible to constrain a type parameter to a Literal type, then with a mapping class like the below and typing to match, the desired functionality could be done as described at the end

from typing import Any, assert_type, Literal

type KVT = tuple[Literal[str], Any]

class Mapping[KvT: KVT]:
    def __getitem__[Kt: Literal[str], Vt, OtherT: KVT](
        self: Mapping[tuple[Kt, Vt] | OtherT],
        key: Kt
    ) -> Vt:
        ...
type IntKey = Literal["a", "b", "c"]
type IntKV = tuple[IntKey, int]
type StrKey = Literal["d", "e", "f"]
type StrKV = tuple[StrKey, str]
type StrIntKey = Literal["g", "h", "i"]
type StrIntKV = tuple[StrIntKey, str | int]

type MyMapping = Mapping[IntKV | StrKV | StrIntKV]

def inspect_mapping(mapping: MyMapping):
    int_a_val = mapping["a"]
    assert_type(int_a_val, int)
    str_d_val = mapping["d"]
    assert_type(str_d_val, str)
    intstr_g_val = mapping["g"]
    assert_type(str_d_val, str | int)

There has been some discussion about how to implement for example typescript’s Partial. This could be typed using typing.NotRequired applied to keys. The below wouldn’t type check but hopefully makes sense.

type Partial[M: Mapping[tuple[TKey, TVal]] =  Mapping[tuple[NotRequired[TKey], TVal]]
type MyPartialMapping = Partial[MyMapping]

and Pick

type Pick[M: Mapping[tuple[TPicked, TPVal] | TRest], TPicked: Literal[str]] = Mapping[tuple[TPicked, TPVal]]
type MyIntOnlyMapping = Pick[MyMapping, IntKey]
assert_type(MyIntOnlyMapping , Mapping[tuple[Literal["a", "b", "c"], int]])

Omit would be similar

type Omit[M: Mapping[tuple[TOmit, Any] | TKept], TOmit: Literal[str]] = Mapping[TKept]
type MyOmitNonIntMapping = Omit[MyMapping, StrKey | StrIntKey]
assert_type(MyOmitNonIntMapping, MyIntOnlyMapping)

Required is similar to Partial:

type Required[M: Mapping[tuple[TKey, TVal]] =  Mapping[tuple[Required[TKey], TVal]]
# Assumes the below
assert_type(Required[NotRequired[Literal["a"]]], Required[Literal["a"])

So far I’ve talked about string keys, but as tuple is a mapping the below would be an example of specifying tuple’s mapping interface

type MyTuple = tuple[int, int, str, int | str]
type MyTupleMapping = Mapping[tuple[Literal[0,1], int] | tuple[Literal[2], str] | tuple[Literal[3, int | str]]

And for one last extreme aside, a 2 by 2 array (numpy?)

type TwoByTwo = Mapping[tuple[Literal[0,1], Literal[0,1], int]]

Last update: With a little coersion pyright can as is use the above mapping type to infer value types from mapped literal keys.

class GetterFn[Tk]:
    @staticmethod
    def get[Tv, To](key: Tk, mapping: Mapping[tuple[Tk, Tv ] | To]) -> Tv:
        ...

def use_get_fn(mapping: IntStrMapping):
    int_val = GetterFn[IntKey].get("int_arg", mapping)
    reveal_type(int_val) # inferred type: int - correct
    str_val = GetterFn[StrKey].get("str_arg", mapping)
    reveal_type(str_val) # inferred type: str - correct

So correct mapping types could be achieved if pyright was less eagerly merging tuple types. If a TypedDict can be transformed to this mapped type representation and key/value types can be manipulated as above then the rest follows using existing typing.NotRequired and typing.Required.

Practically speaking what you’d need to manipulate these is Map, Unpack, GetN, and Zip. For comparison, if you had a tuple of tuples of int/str and wanted to pass the first value in each tuple to the str constructor, those are the operations you’d need. GetN to provide a type index operator. Union of types behaves like a set (actually a bidirectional mapping depending on which key you constrain the tuple type by), typevartuple behaves like a sequence. They’re both iterable, order only has meaning in one case. I think with all of those you could also specify a lot of panda’s type interface if it was given a basic column name to column type mapping. The distinction between a mapping and ordered mapping would become important for projects like pandas though. Aim to manipulate the Literal[key] type rather than the Mapping type.

An alternative and simpler approach would be to allow a type statement to be templated, similar to c++. Essentially the syntax I gave above.

Taking this to another extreme, having a way to convert classes to this typed mapping and back would mean it’s possible for dataclass_transform operators to type the changes they make to the class. ORMs? How about typed database queries?