13

Basically a distilled down version of this (as yet unanswered) question.

I want to state that a variable should only take on values that are keys in a TypedDict.

At present I'm defining a separate Literal type to represent the keys, for example:

from typing import Literal, TypedDict


class MyTD(TypedDict):
    a: int
    b: int


mytd = MyTD(a=1, b=2)

key = "a"

mytd[key]  # error: TypedDict key must be a string literal; expected one of ('a', 'b')

MyTDKeyT = Literal["a", "b"]

typed_key: MyTDKeyT = "b"

mytd[typed_key]  # no error

I would like to be able to replace the Literal definition for all the usual reasons of wanting to minimize duplicated code.

Pseudo-code:

key: Keys[MyTD] = "a"
mytd[key]  # would be no error
not_key: Keys[MyTD] = "z"  # error

Is there a way to achieve this?

To clarify, given that mypy can tell me that the key type needs to be a literal of "a" or "b", I'm hoping there might be a less error prone way to annotate a variable to that type, rather than having to maintain two separate lists of keys side-by-side, once in the TypedDict definition, once in the Literal definition.

SuperShoot
  • 9,880
  • 2
  • 38
  • 55
  • 2
    https://github.com/python/mypy/issues/7178 seems like a known issue – Iain Shelvington Jul 06 '20 at 04:01
  • 1
    @IainShelvington I don't think that's the OP's issue. They want to be able to replace the literal type definition with something from the `TypedDict` type... `key = 'a'` has to infer `str`, which mypy correctly identifies as wrong. That was [fixed here](https://github.com/python/mypy/pull/7645), indeed, we can see the fixed behavior above – juanpa.arrivillaga Jul 06 '20 at 04:13
  • @IainShelvington That issue seems to be about having mypy recognize that a string literal stored to a variable should be allowed as a key to retrieve a value. It evolves to suggesting the exact use-case that I currently use (defining the distinct `Literal` or `Final` type). – SuperShoot Jul 06 '20 at 04:19
  • I have same issue. I would like to define a function which takes some TypedDict and a valid key of it, but figured out there is now way to do it. Something like this: https://www.typescriptlang.org/docs/handbook/2/keyof-types.html – Witold Szczerba Jul 26 '21 at 09:36
  • This is a valid code in [**pyright**](https://github.com/microsoft/pyright). Probably the best way to handle this case in type checking via **mypy** is to either cast it to a `Literal`, or use a `# type: ignore` comment. – Paweł Rubin Feb 15 '22 at 20:49

1 Answers1

2

Using MyPy, I don't think this is possible. I ran this experiment:

from typing import TypedDict

class MyTD(TypedDict):
    a: str
    b: int

d = MyTD(a='x', b=2)
reveal_type(list(d))

The MyPy output was:

Revealed type is "builtins.list[builtins.str]"

This indicates that internally it is not tracking the keys as literals. Otherwise, we would expect:

Revealed type is "builtins.list[Literal['A', 'B']]" 

Also, this errors out in MyPy, so __required_keys__ isn't even inspectable:

reveal_type(MyTD.__required_keys__)
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • 2
    Thanks for giving this your time, Raymond - that makes sense. It feels like a missed opportunity - but you could write a whole static type system with what I don't know. Given that it isn't rejected as an idea in PEP 589, I'll live in hope. – SuperShoot May 23 '22 at 03:20