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 becauseRequiredalready exists. So we could either:- Use a different naming (
Total[...]andNonTotal[...]for instance). This would allow the following:
class SomeTD(TypedDict, total=False): f1: Required[OtherTD] f2: Total[OtherTD]- Use attributes:
SomeTD.partial/SomeTD.requiredas suggested in this thread. The benefit is that we don’t need to introduce new members to thetypingmodule, the downside is that it can’t be used for anything else than typed dictionaries (leaving structural types aside). Protocols were mentioned, for instance.
- Use a different naming (
-
It would also maybe make sense to have an equivalent to TypeScript’s
OmitandPick. 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]: ...
We could argue that only having a partial implementation is enough, as
SomeTDandPartial[SomeTD]gives you both variants, but you could also haveSomeTDdefined with some fields as required and some not required. ↩︎Converters are an example of something not supported by stdlib dataclasses but supported by dataclass transforms. ↩︎