'Make all keys in a typed dict not required

I have an existing TypedDict containing multiple entries:

from typing import TypedDict
class Params(TypedDict):
    param1:str
    param2:str
    param3:str

I want to create the exact same TypedDict but with all the keys being optional so that the user can specify only certain parameters. I know I can do something like:

class OptionalParams(TypedDict, total=False):
    param1:str
    param2:str
    param3:str

but the problem with this method is that I have to duplicate the code. Is there a way to inherit from Params by making the keys optional ? I tried to do

class OptionalParams(Params, total=False):
    pass

but the linter does not understand that the parameters are optional



Solution 1:[1]

What you ask for is not possible - at least if you use mypy - as you can read in the comments of Why can a Final dictionary not be used as a literal in TypedDict? and on mypy's github: TypedDict keys reuse?. Pycharm seems to have the same limitation, as tested in the two other "Failed attempts" answers to your question.

When trying to run this code:

from typing import TypeDict

params = {"a": str, "b": str}
Params = TypedDict("Params", params)

mypy will give error: TypedDict() expects a dictionary literal as the second argument, thrown here in the source code.

Solution 2:[2]

No, you cannot do this for the same set of fields. Quoting from PEP 589.

The totality flag only applies to items defined in the body of the TypedDict definition. Inherited items won’t be affected, and instead use totality of the TypedDict type where they were defined. This makes it possible to have a combination of required and non-required keys in a single TypedDict type.

It's possible to construct required and non required keys with a single TypedDict type.

>>> class Point2D(TypedDict, total=False):
...     x: int
...     y: int
...
>>> class Point3D(Point2D):
...     z: int
...
>>> Point3D.__required_keys__ == frozenset({'z'})
True
>>> Point3D.__optional_keys__ == frozenset({'x', 'y'})
True

But you have same set of keys(param1, param2, param3) that needs to be mandatory and optional. Hence that has be inherited from TypedDict separately with total=False

class OptionalParams(TypedDict, total=False):
    param1:str
    param2:str
    param3:str

Solution 3:[3]

Failed attempt

I tried to copy the class using inheritance, then reassigned the __total__ attribute:

class OptionalParams(Params):
    pass

OptionalParams.__total__ = False

If you check the implementation of TypeDict (I am using Python 3.8.12) you will find that total is only used to set the __total__ attribute, so it should be safe to reassign it. Below you have the relevant code snippet from the typing module.

class TypedDict(dict, metaclass=_TypedDictMeta):
    ...

class _TypedDictMeta(type):
    def __new__(cls, name, bases, ns, total=True):
        ...
        if not hasattr(tp_dict, '__total__'):
            tp_dict.__total__ = total
        return tp_dict

The code doesn't work anyway.

Solution 4:[4]

Failed attempt #2

From the TypedDict docstring

TypedDict supports two additional equivalent forms:

Point2D = TypedDict('Point2D', x=int, y=int, label=str)
Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})

It looks like you can use the second to create both Params and OptionalParams classes conveniently:

params = {"params1": str, "params2": str, "params3": str}

Params = TypedDict("Params", params)
OptionalParams = TypedDict("OptionalParams", params, total=False)

I have also tried the following:

class Params(TypedDict):
    param1:str
    param2:str
    param3:str

OptionalParams = TypedDict("OptionalParams", Params.__annotations__, total=False)

assert {"params1": str, "params2": str, "params3": str} == Params.__annotations__

These should work in principle, however when running the code through Mypy gives error: TypedDict() expects a dictionary literal as the second argument.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 edd313
Solution 2
Solution 3
Solution 4