python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.28k stars 2.8k forks source link

TypedDict keys reuse? #4128

Open jstasiak opened 6 years ago

jstasiak commented 6 years ago

I may be in a need of the following - two TypeDicts with the same keys, one of them created with total=False:

from mypy_extensions import TypedDict

Details = TypedDict('Details', {'name': str, 'age': int, 'address': str})
DetailsSubset = TypedDict('DetailsSubset', {'name': str, 'age': int, 'address': str}, total=False)

Is there a way to reuse the keys instead of having to repeat the {'name': str, 'age': int, address: str} fragment twice?

I can't do

from mypy_extensions import TypedDict

keys = {'name': str, 'age': int, 'address': str}
Details = TypedDict('Details', keys)
DetailsSubset = TypedDict('DetailsSubset', keys, total=False)

Because

% mypy code.py
code.py:4: error: TypedDict() expects a dictionary literal as the second argument
code.py:5: error: TypedDict() expects a dictionary literal as the second argument

(which is quite reasonable, tried that on the off chance it'd work)

ilevkivskyi commented 6 years ago

This is currently not possible. We will probably need the Const type modifier for this to work, see https://github.com/python/mypy/issues/1214

JukkaL commented 5 years ago

We now have a Final modified but that isn't enough, since we never implicitly infer TypedDict types from dict expressions.

ilevkivskyi commented 4 years ago

Another4 request for this appeared, see https://github.com/python/mypy/issues/8186, so raising priority to normal.

ilevkivskyi commented 4 years ago

(Just to clarify, we can start from only supporting final names for individual keys, which should be pretty easy.)

cwhy commented 4 years ago

@ilevkivskyi I agree that we can just support final names for keys first, at least for NamedTuple, as stated at the end of PEP-591:

Type checkers should treat uses of a final name that was initialized with a literal as if it was replaced by the literal. For example, the following should be allowed:


X: Final = "x"
Y: Final = "y"
N = NamedTuple("N", [(X, int), (Y, int)])

It is sometimes useful to have different types on the same label as well:

X: Final = "x"
Y: Final = "y"
Position = NamedTuple("Position", [(X, int), (Y, int)])
DimensionName = NamedTuple("DimensionName", [(X, str), (Y, str)])
tommyjcarpenter commented 2 years ago

is there any update on this issue when Final is used? This seems last updated about two years ago

I would expect this to be legal:

In [3]: from typing import TypedDict, List, Final

In [6]: X: Final = "namespaces_read"
In [7]: Y: Final = "namespaces_write"

In [4]: Permissions = TypedDict(
   ...:     "Permissions", {X: List[str], Y: List[str]}
   ...: )

In [8]: Z: Permissions = {X: [], Y: []}
tony commented 2 years ago

Let me know if you'd like me to remove this and create a separate issue if it's off scope. This falls under the canopy of TypedDict keys reuse, but it looks like this issue came to be about subsets, when I want reuse across keyed structures

Allow all "typed fields" / keyed data structures to reuse their typings.

e.g. typing.TypedDict, typing.NamedTuple and dataclasses.dataclass.

Scenario: Typed exports from dataclass

Assume my case, I want to offer users ability to export a typed tuple and dict from my Details dataclass, dataclasses.astuple and dataclasses.asdict

#!/usr/bin/env python
import dataclasses
from typing import NamedTuple, TypedDict, get_type_hints

class DetailsDict(TypedDict):
    name: str
    age: int
    address: str

class DetailsTuple(NamedTuple):
    name: str
    age: int
    address: str

@dataclasses.dataclass
class Details:
    name: str
    age: int
    address: str

    def to_dict(self) -> DetailsDict:
        # return dataclasses.asdict(self, dict_factory=DetailsDict)
        return DetailsDict(**dataclasses.asdict(self))

    def to_tuple(self) -> DetailsTuple:
        # return dataclasses.astuple(self, tuple_factory=DetailsTuple)
        return DetailsTuple(*dataclasses.astuple(self))

john = Details(name="John", age=25, address="123 Address St")
print(john)
print(john.to_dict())
print(john.to_tuple())
print(get_type_hints(john))
print(get_type_hints(john.to_dict))
print(get_type_hints(john.to_tuple))

Output:

Details(name='John', age=25, address='123 Address St')
{'name': 'John', 'age': 25, 'address': '123 Address St'}
DetailsTuple(name='John', age=25, address='123 Address St')
{'name': <class 'str'>, 'age': <class 'int'>, 'address': <class 'str'>}
{'return': <class '__main__.DetailsDict'>}
{'return': <class '__main__.DetailsTuple'>

Why would a user want to reuse dict/tuple/etc?

Problem

We have to redeclare the same field annotations for dict, tuple, and dataclass

Solution

We need a way to reuse the field typings across the different keyed data structures.

nitedani commented 2 years ago

Why would a user want to reuse dict/tuple/etc?

class CreateArgs(TypedDict):
    first_name: str
    last_name: str
    external_id: str
    address: str
    city: str
    phone: str
    zip: str
    email: str
    state: str

class UpdateArgs(TypedDict, total=False):
    first_name: str
    last_name: str
    external_id: str
    address: str
    city: str
    phone: str
    zip: str
    email: str
    state: str
type CreateArgs = {
  first_name: string;
  last_name: string;
  external_id: string;
  address: string;
  city: string;
  phone: string;
  zip: string;
  email: string;
  state: string;
};

type UpdateArgs = Partial<CreateArgs>;

For me it's not about compliance, neither an open-source library. It's code repetition. I would like to define the keys only once.