Introduce Partial for TypedDict

Together with @samuelcolvin and Sydney Runkle, we’d like to try pushing this forward. Here are a couple thoughts I had:

  • The idea would be to introduce a partial equivalent to the one defined in TypeScript. However, TypeScript also defines a Required<Type> counterpart. I think it would be beneficial to have both implemented in Python [1].

  • If the partial implementation is implemented using a new Partial[...] form and we want to support a required implementation as well, this causes issues because Required already exists. So we could either:

    • Use a different naming (Total[...] and NonTotal[...] for instance). This would allow the following:
    class SomeTD(TypedDict, total=False):
        f1: Required[OtherTD]
        f2: Total[OtherTD]
    
    • Use attributes: SomeTD.partial / SomeTD.required as suggested in this thread. The benefit is that we don’t need to introduce new members to the typing module, the downside is that it can’t be used for anything else than typed dictionaries (leaving structural types aside). Protocols were mentioned, for instance.
  • It would also maybe make sense to have an equivalent to TypeScript’s Omit and Pick. If we were to use the same form with brackets, how should this be spelled? Pick[TD, 'k1', 'k2'] / Pick[TD, ('k1', 'k2')]? What about as a attribute?

  • Regarding support for structural types – mainly dataclass-like types – I agree we shouldn’t mix the two. One other reason not to do so imo is that it can be ambiguous (using attrs):

    from attrs import NOTHING, define
    
    @define
    class A:
        a: int
        b: int | None
    
    pA = Partial[A]
    # `a` is 'int | None = None', or 'int | NOTHING = NOTHING'?
    # `b` is 'int | None = None', or 'int | None | NOTHING = NOTHING'?
    # Let's go with `None` for now..
    
    rA = Required[A]  # name TBD
    # `a` is 'int', or 'int | None'?
    # `b` is 'int', or 'int | None'?
    

    I believe we could make use of Annotated (cross ref this comment) or tweak the @dataclass_transform() spec [2] to do so. Quick API sketch:

     from typing import PartialSpec
    
    partial_spec = PartialSpec(
        default=None,  # Or MISSING, etc.
    )
    
    @dataclass_transform(...)
    class BaseModel:
        @classmethod
         def as_partial(cls) -> Annotated[type[Self], partial_spec]: ...
    

  1. We could argue that only having a partial implementation is enough, as SomeTD and Partial[SomeTD] gives you both variants, but you could also have SomeTD defined with some fields as required and some not required. ↩︎

  2. Converters are an example of something not supported by stdlib dataclasses but supported by dataclass transforms. ↩︎

3 Likes