Daniel Kats
Daniel Kats

Reputation: 5554

Specify keys for mypy in python dictionary

Suppose I have some code like

def get_x(d: dict) -> int:
    d["x"]

However, I want to tell mypy that d should only contain certain keys (only the "x" key for example). That way, if I make a mistake lower in the code trying to reference an invalid key of d, mypy will trigger an error.

My question is:

  1. Is this possible? Can mypy validate dictionary keys?
  2. If yes, how is this done? If no, is there a preferred workaround?

Upvotes: 15

Views: 4763

Answers (2)

kevlarr
kevlarr

Reputation: 1162

Ran into this problem too when trying to put some typing around AWS API Gateway/Lambda integration.

As pointed out by @Michael0x2a in the comments, TypedDict would seem to be the way to go, especially because it does not require any conversions yourself before passing into a function (a requirement for API responses, like you mentioned).

from mypy_extensions import TypedDict

DictWithOnlyX = TypedDict('DictWithOnlyX', {"x": str})

# error: Extra key 'y' for TypedDict "DictWithOnlyX@58"
dx1: DictWithOnlyX = { "y": 123 }

# error: Incompatible types (expression has type "int",
#        TypedDict item "x" has type "str")
dx2: DictWithOnlyX = { "x": 123 }

# GOOD!
dx3: DictWithOnlyX = { "x": "123" }

Should also note that, if dependencies are segregated between production and dev/test, then this makes mypy a production dependency.

Upvotes: 15

drum
drum

Reputation: 5651

There are a few ways and assuming that your data structure must only have the same attributes, but they don't use dicts.

Instead of passing in a dict, you can use a namedtuple. Using a namedtuple, you can treat it as an object, but no new fields can be added either.

from collections import namedtuple

A = namedtuple("A", ["x", "y", "z"])

def get_x(d: A) -> int:
    d.x

Another way is to create a class and use the __slot__ attribute. This also makes it so no new attributes can be accidentally inserted.

class A:
    __slot__ = ["x", "y", "z"]

    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

Using the above methods, you must define the fields at the very beginning.

Upvotes: 1

Related Questions