ty icon indicating copy to clipboard operation
ty copied to clipboard

support TypedDict (and Required, NotRequired, ReadOnly)

Open carljm opened this issue 9 months ago • 12 comments

  • [x] Introduce a new Type variant, basic support for class-based TypedDict syntax
    • [x] Parse a TypedDict specification from the class body
    • [x] Implement / test assignability to Mapping[str, object]
    • [x] Make sure that TypedDict-based objects do not behave like instances of that class. For example, attribute access should not work.
    • [x] Implement fallback to _typeshed._type_checker_internals.TypedDictFallback
    • [ ] Add TypedDict samples to property tests
  • [x] Type inference for key accesses (movie['directory'] -> str)
  • [x] Validation of writes to keys (synthesized __setitem__ method with key: Literal["key1"], value: ValueType1?)
  • [x] Validation of constructor calls (Movie(name="Blade Runner", year=1982))
  • [x] Validation of {…} literals to TypedDict types
  • [x] Totality
    • [x] Required and NotRequired
  • [x] Implement / write tests for generic TypedDicts
  • [x] Implement / write tests for recursive TypedDicts
  • [x] Support type inference for instance.get("known_key")?
  • [x] PEP 705 support (ReadOnly)
  • [x] #1387
  • [x] Implement disjointness for TypedDict.
  • [ ] Support TypedDicts in all contexts (return position, function call arguments, etc) => #168
  • [ ] PEP 728 support ("openness", extra_items)
  • [ ] Functional syntax (Movie = TypedDict('Movie', {'name': str, 'year': int}))
  • [ ] Support for Unpack
  • [ ] Overriding of fields in inheritance
  • [ ] Correctness, validation
    • [ ] Implement checks based on supported and unsupported operations
    • [ ] TypedDict type objects cannot be used in isinstance checks
    • [ ] Only item definitions in the class body (no methods, for example)
    • [ ] Specifying a metaclass is not allowed
    • [ ] Can only subclass TypedDict, TypedDict- based types and Generic
    • [ ] Implement / write tests for subclassing of TypedDict-based classes including multiple inheritance
  • [ ] Performance
    • [ ] Try out the optimization proposed in https://github.com/astral-sh/ruff/pull/19763#discussion_r2256719857
    • [ ] Add a micro-benchmark for large TypedDicts
  • [ ] Implement .normalize() for TypeDictType.
  • [ ] Investigate Pydantic-specific performance problems introduced in #21467.
  • [ ] Add TypedDict to property tests.
  • [ ] Submit a correction to the upstream assignability rules => https://github.com/python/typing/pull/2119

carljm avatar Mar 26 '25 18:03 carljm

And total=False

tylerlaprade avatar May 20 '25 18:05 tylerlaprade

TypedDict has a few interesting properties that may affect core concepts in the type system, so I'll be interested to see how you handle them.

The spec says that a TypedDict must be an instance of dict itself, not a subclass. I think that means these test cases should pass:

from typing import Mapping, TypedDict, assert_type

class TD(TypedDict):
    a: int

class SubDict[KT, VT](dict[KT, VT]):
    pass

def f(x: TD | dict[int, str]):
    if isinstance(x, SubDict):
        assert_type(x, SubDict[int, str])
    else:
        assert_type(x, TD | dict[int, str])

def g(x: TD | Mapping[int, str]):
    if isinstance(x, dict):
        assert_type(x, TD | dict[int, str])
    else:
        assert_type(x, Mapping[int, str])

(Neither mypy nor pyright currently pass all of this, mostly because they put Any/Unknown in type parameters. I'm not sure there's a good reason for that.)

However, a TypedDict type is not assignable to dict[T, U] for any values of T and U, not even to dict[Any, Any] (spec: https://typing.python.org/en/latest/spec/typeddict.html#assignability). This will have interesting consequences for the issues in #456: it means that narrowing from isinstance(x, dict) can't be implemented by intersecting with some version of forall K, V. dict[K, V].

JelleZijlstra avatar Jun 14 '25 01:06 JelleZijlstra

another piece of functionality that would eventually be nice here is the Unpack keyword

refs

  1. https://typing.python.org/en/latest/spec/callables.html#unpack-kwargs
  2. https://peps.python.org/pep-0692/

it's basically a way to use TypedDict's to type the **kwargs of a function

this ends up being very useful in practice for balancing python's flexibility with strict type checking

sslivkoff avatar Jul 14 '25 21:07 sslivkoff

Moving this to GA because we feel the current feature-set is adequate for beta (though it would be nice to add a few more things if we have time).

carljm avatar Aug 15 '25 14:08 carljm

Moving this to GA because we feel the current feature-set is adequate for beta (though it would be nice to add a few more things if we have time).

(Note that there are many dictionaries that we do not infer as TypedDict instances right now despite them being annotated as such, but that's really part of https://github.com/astral-sh/ty/issues/168 rather than this issue -- that issue should definitely be a beta priority!)

AlexWaygood avatar Aug 15 '25 14:08 AlexWaygood

https://github.com/python/typing/pull/2072 rewrites the TypedDict spec to be much clearer and more concise, which may be useful to anyone working on further features here.

carljm avatar Aug 19 '25 21:08 carljm

Specifically a good place to focus on would be the full structure of TypedDict as laid out in the introduction to the proposed spec change (a list of items consisting of key, value type, requiredness, mutability; whether there are extra items; and whether or not the extra items are read-only). The internal representation of TypedDict types likely should follow that structure closely. Then you'll also have to write code to recognize the syntactic forms that create these TypedDicts (e.g., keyword args to the TypedDict constructor; the Required/NotRequired/ReadOnly qualifiers; maybe inheritance, though that can wait). Implementing this structure should provide a good basis for implementing other operations on TypedDicts.

JelleZijlstra avatar Aug 25 '25 04:08 JelleZijlstra

Currently this type-checks on the playground (I don't know whether it also type-checks on main):

from typing import TypedDict

class TD(TypedDict):
    a: str

x: TD = 1

Even if full bidirectional type checking isn't implemented yet, wouldn't it be possible to at least check whether the right-hand side is a dictionary?

tmke8 avatar Oct 07 '25 21:10 tmke8

Currently this type-checks on the playground (I don't know whether it also type-checks on main):

(The ty playground automatically gets a fresh deploy on each new commit to main, so it's usually <10 minutes behind main)

AlexWaygood avatar Oct 07 '25 21:10 AlexWaygood

anyway: we're working on TypedDict features actively, and we will get an improved TypedDict assignability implementation delivered to you as soon as we can 😄

AlexWaygood avatar Oct 07 '25 21:10 AlexWaygood

Moving this ticket to GA, but #1387 into the Beta

sharkdp avatar Oct 17 '25 14:10 sharkdp

Added a checkbox at the top for "Implement .normalize() for TypeDictType."

oconnor663 avatar Nov 14 '25 23:11 oconnor663

I'm working on the functional syntax piece.

charliermarsh avatar Jan 01 '26 21:01 charliermarsh