#!python3 class TypedDict(dict): keytype = valtype = keymap = valmap = valid_keys = typemap = None def __init__(self, mapping=None, **kwargs): if self.typemap is not None and self.valid_keys is None: self.valid_keys = set(self.typemap) super(TypedDict, self).__init__() if mapping is None: mapping = kwargs.items() elif isinstance(mapping, dict): mapping = mapping.items() for k, v in mapping: self[k] = v def __setitem__(self, key, val): if self.keytype is not None and type(key) is not self.keytype: if self.keymap is not None and type(key) in self.keymap: key = self.keymap[type(key)](key) else: try: key = self.keytype(key) except TypeError: raise TypeError('Keys must be of type {!r}'.format( self.keytype)) if self.valtype is not None and type(val) is not self.valtype: if self.valmap is not None and type(val) in self.valmap: val = self.valmap[type(val)](val) else: try: val = self.valtype(val) except TypeError: raise TypeError('Values must be of type {!r}'.format( self.valtype)) if self.typemap is not None and key in self.typemap: val = self.typemap[key](val) if self.valid_keys is not None and key not in self.valid_keys: raise KeyError('Invalid key') super(TypedDict, self).__setitem__(key, val) class BytesIntMap(TypedDict): keytype = bytes valtype = int keymap = {str: str.encode} class ExampleModel(TypedDict): class File(TypedDict): class Permission(TypedDict): valid_keys = {'r', 'w', 'x'} valtype = bool keytype = str typemap = {'mtime': int, 'perm': Permission} class User(TypedDict): keytype = str valid_keys = {'groups'} valtype = list typemap = {'files': File, 'users': User} model = ExampleModel( files={'/home': {'mtime': 0, 'perm': {'r': True, 'w': False}}, '/tmp': {'mtime': 0, 'perm': {'r': True, 'w': True}}}, users={'root': {'groups': ['root', 'wheel']}})