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 becauseRequired
already 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.required
as suggested in this thread. The benefit is that we don’t need to introduce new members to thetyping
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.
- Use a different naming (
-
It would also maybe make sense to have an equivalent to TypeScript’s
Omit
andPick
. 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
SomeTD
andPartial[SomeTD]
gives you both variants, but you could also haveSomeTD
defined 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. ↩︎