How to cast a typing.Union to one of its subtypes in Python?

Although I think casts are probably the right option to use in your case, I just briefly want to mention one additional option which might be applicable in similar scenarios, to round things out:

It's actually possible to type your dict more precisely using the new, experimental TypedDict feature, which is available latest versions of mypy (if you clone from the github repo) and will likely be available in the next pypi release.

In order to use TypedDict, you'll need to install the mypy_extensions from pypi by running pip install mypy_extensions.

TypedDict lets you assign individual types to each item in your dict:

from mypy_extensions import TypedDict

Foo = NewType("Foo", str)
Bar = NewType("Bar", int)

FooBarData = TypedDict('FooBarData', {
    'foo': Foo,
    'bar': Bar,
})

You can also define FooBarData using a class-based syntax in Python 3.6+:

from mypy_extensions import TypedDict

Foo = NewType("Foo", str)
Bar = NewType("Bar", int)

class FooBarData(TypedDict):
    foo: Foo
    bar: Bar

You also mentioned that your dict can have a dynamic number of elements. If it genuinely is dynamic, then TypedDict won't help for the same reasons that NamedTuple won't help, but if your TypedDict will ultimately have a finite and number of elements, and you're just progressively adding items to it instead of all at once, you can try using non-total TypedDicts, or try constructing TypeDicts that mix required and non-required items.

It's also worth noting that unlike pretty much every other type, TypedDicts are checked using structural typing, rather then nominal typing. This means that if you define a completely unrelated TypedDict named, say, QuxData that also has foo and bar fields with the same type as FooBarData, then QuxData will actually be a valid subtype of FooBarData. This may open up some interesting possibilities with a little bit of cleverness.


You'd have to use cast():

process(cast(Foo, d["foo"]), cast(Bar, d["bar"]))

From the Casts section of PEP 484:

Occasionally the type checker may need a different kind of hint: the programmer may know that an expression is of a more constrained type than a type checker may be able to infer.

There is no way to spell what specific types of value go with what specific value of a dictionary key. You may want to consider returning a named tuple instead, which can be typed per key:

from typing import Dict, Union, NewType, NamedTuple

Foo = NewType("Foo", str)
Bar = NewType("Bar", int)

class FooBarData(NamedTuple):
    foo: Foo
    bar: Bar

def get_data() -> FooBarData:
    return FooBarData(foo=Foo("one"), bar=Bar(2))

Now the type hinter knows exactly what each attribute type is:

d = get_data()
process(d.foo, d.bar)

Or you could use a dataclass:

from dataclasses import dataclass

@dataclass
class FooBarData:
    foo: Foo
    bar: Bar

which makes it easier to add optional attributes as well as control other behaviour (such as equality testing or ordering).

I prefer either over typing.TypedDict, which is more meant to be used with legacy codebases and (JSON) serialisations.