Skip to content

Instantly share code, notes, and snippets.

@rmorshea
Last active January 17, 2019 21:14
Show Gist options
  • Save rmorshea/a1c6eaae65d3ae9b2becefeb88a25285 to your computer and use it in GitHub Desktop.
Save rmorshea/a1c6eaae65d3ae9b2becefeb88a25285 to your computer and use it in GitHub Desktop.
Voluptuous sphinx documentation model.
# Copyright (c) 2019 by Cisco Systems, Inc.
from voluptuous import Schema, All
from collections import Mapping
from abc import ABCMeta
from typing import Dict, Any
class MetaModel(ABCMeta):
def __init__(self, name, bases, attrs):
attr_validators = self._attribute_validators.copy()
for k, v in attrs.items():
if not k.startswith("_"):
if isinstance(v, Pair):
k, v = v.make(k)
attr_validators[k] = v
self._attribute_validators = attr_validators
validators = attr_validators.copy()
anno_validators = self._annotation_validators.copy()
for k, v in getattr(self, "__annotations__", {}).items():
if not k.startswith("_"):
if k not in anno_validators or anno_validators[k] != v:
anno_validators[k] = v
if k in validators:
validators[k] = All(v, validators[k])
else:
validators[k] = v
self._annotation_validators = anno_validators
self._strict_schema = Schema(validators, **self._strict_config)
self._loose_schema = self._strict_schema.extend({}, **self._loose_config)
def __getitem__(self, marker):
return Pair(marker)
def __dir__(self):
return map(str, super().__dir__())
def __matmul__(self, reconfig):
strict_config = self._strict_config.copy()
strict_config.update(reconfig)
loose_config = dict(strict_config, required=False)
attrs = {"_strict_config": strict_config, "_loose_config": loose_config}
return type("ConfiguredModel", (Model,), attrs)
class Model(Mapping, metaclass=MetaModel):
_strict_config = {"required": True} # type: Dict[str, Any]
_loose_config = {"required": False} # type: Dict[str, Any]
_captured_changes = {} # type: Dict[int, Dict[str, Any]]
_attribute_validators = {} # type: Dict
_annotation_validators = {} # type: Dict
def __new__(cls, *args, **kwargs):
new = super().__new__(cls)
new.__init__(*args, **kwargs)
return new.__dict__
def __init__(self, *args, **kwargs):
value = dict(*args, **kwargs)
self.__dict__.update(self._strict_schema(value))
changes = self._captured_changes[id(self)] = {}
try:
self._validate()
if changes:
self.__dict__.update(self._loose_schema(changes))
finally:
del self._captured_changes[id(self)]
def __len__(self):
return len(self.__dict__)
def __iter__(self):
return iter(self.__dict__)
def __contains__(self, key):
return key in self.__dict__
def __getitem__(self, key):
return self.__dict__[key]
def _validate(self):
pass
def __setattr__(self, name, value):
self_id = id(self)
if self_id in self._captured_changes:
self._captured_changes[self_id][name] = value
else:
name = type(self).__name__
raise RuntimeError("%r object is immutable." % name)
def __getattr__(self, name):
try:
return getattr(self.__dict__, name)
except AttributeError:
raise AttributeError(name)
def __repr__(self):
data = ", ".join("%s=%r" % i for i in dict(self).items())
return "%s(%s)" % (type(self).__name__, data)
class Pair:
def __init__(self, marker):
self._marker = marker
self._args = ()
self._kwargs = {}
def __call__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
return self
def __matmul__(self, value):
self._value = value
return self
def make(self, key):
return self._marker(key, *self._args, **self._kwargs), self._value
def __repr__(self):
return repr(self._value)
def __getattr__(self, name):
return getattr(self._value, name)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment