Skip to content

Guidance needed: how to narrow unions of TypedDicts? #18543

Closed as not planned
@magicmark

Description

@magicmark

The docs give very clear guidance on how to write code using unions of TypedDicts:

https://mypy.readthedocs.io/en/stable/typed_dict.html#unions-of-typeddicts

This makes sense!

However, a common use case (for me) of a TypedDict is to allow external code (e.g. REST API input) to be deserialized into a dict and refined at runtime into a TypedDict -- which implies the the design requirement to have a branded field (e.g. tag) must now exist in our public facing API.

This could work in some cases, but consider something like this:

from __future__ import annotations
from typing import List, TypedDict

class Policy(TypedDict):
    name: str

class CombinedORPolicy(TypedDict):
    OR: List[Policy]

class CombinedANDPolicy(TypedDict):
    AND: List[Policy]


def print_policy(policy: CombinedANDPolicy | CombinedORPolicy | Policy) -> None:
    if 'OR' in policy:
        allowed_policy_names = ' '.join([p['name'] for p in policy['OR']])
        print(f"Can match _any_ of the following: {allowed_policy_names}")
    elif 'AND' in policy:
        allowed_policy_names = ' '.join([p['name'] for p in policy['AND']])
        print(f"Must match _all_ the following: {allowed_policy_names}")
    else:
        print(f"Must be exactly {policy['name']}")


# pretend this is an API endpoint or something
print_policy({'name': 'role:is_admin'})

#13838 being closed seems to imply this is possible but i'm still having trouble.

Here's the current mypy (1.14.1) output:

test.py: note: In function "print_policy":
test.py:16:68: error: TypedDict "CombinedANDPolicy" has no key "OR"  [typeddict-item]
test.py:16:68: error: TypedDict "Policy" has no key "OR"  [typeddict-item]
test.py:19:68: error: TypedDict "Policy" has no key "AND"  [typeddict-item]
Found 3 errors in 1 file (checked 1 source file)

The solution as I understand is either:

  • force an API change (e.g. print_policy({'name': 'role:is_admin', 'tag': 'root-policy'}))
  • use cast (e.g. policy = cast(CombinedANDPolicy, policy)

Is there anything more idiomatic i'm missing?

(I searched the tracker but wasn't able to find discission of this specifically, but I think this is related: #7981 #12098 #11080)

Is there a stance on this yet? Is this is a goal or non-goal of mypy? Thanks!

FWIW -- here's a version of this working in

thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions