Skip to content

Instantly share code, notes, and snippets.

@jomido
Last active October 8, 2015 00:10
Show Gist options
  • Save jomido/3247456 to your computer and use it in GitHub Desktop.
Save jomido/3247456 to your computer and use it in GitHub Desktop.
Python Type Constraints (with Reserved Attributes)
class SchemaFailException(Exception):
def __init__(self, message, attr, valid_types):
Exception.__init__(self, message)
self.attr = attr
self.valid_types = valid_types
class ReservedAttributeException(Exception):
def __init__(self, message, attr):
Exception.__init__(self, message)
self.attr = attr
def _is_reserved_function(string_list):
def _is_reserved(key):
if key in string_list:
return True
return _is_reserved
def _fails_schema_function(schema_dict):
def _fails_schema(key, value):
if key not in schema_dict.keys():
return False
if type(value) not in schema_dict[key]:
return True
return _fails_schema
class Typed(type):
@classmethod
def __prepare__(metacls, name, bases, **kwds):
schema = {}
for base in bases:
if hasattr(base, "_schema"):
schema.update(base._schema)
if "schema" in kwds:
schema.update(kwds["schema"])
class base_obj_dict(dict):
def __init__(self, metacls, cls):
self._schema = schema
self.reserved = [
"_metacls",
"_schema",
"_cls",
"_reserved",
"_is_reserved",
"_fails_schema"
]
self._is_reserved = _is_reserved_function(self.reserved)
self._fails_schema = _fails_schema_function(schema)
super().__init__({
"_metacls": metacls,
"_cls": cls,
"_schema": schema,
"_reserved": self.reserved,
"_is_reserved": self._is_reserved,
"_fails_schema": self._fails_schema
})
def __setitem__(self, key, value):
if self._is_reserved(key):
msg = "The attribute '{}'' is reserved and cannot be "
"defined.".format(key)
raise ReservedAttributeException(msg, key)
if self._fails_schema(key, value):
valid = [str(t) for t in self._schema[key]]
msg = "The attribute '{}' can only be these types: {}.\n"
"It therefore cannot be defined as {}, which is of type "
"{}.".format(key, ', '.join(valid), value, type(value))
raise SchemaFailException(msg, key, self._schema[key])
super().__setitem__(key, value)
return base_obj_dict(metacls, name)
def __new__(cls, name, bases, attrs, **options):
# This is only necessary because type.__new__() doesn't know how to
# handle the extra arguments re:
# http://martyalchin.com/2011/jan/20/class-level-keyword-arguments/
# (same for __init__)
return type.__new__(cls, name, bases, attrs)
def __init__(cls, name, bases, attrs, **kwargs):
pass
def __setattr__(cls, key, value):
if cls._is_reserved(key):
msg = "The attribute '{}'' is reserved and cannot be set".format(
key)
raise ReservedAttributeException(msg, key)
if cls._fails_schema(key, value):
valid = [str(t) for t in cls._schema[key]]
msg = "The attribute '{}' can only be these types: {}.\n"
"It therefore cannot be set to {}, which is of type {}".format(
key, ', '.join(valid), value, type(value))
raise SchemaFailException(msg, key, cls._schema[key])
super().__setattr__(key, value)
class Typed(metaclass=Typed):
def __setattr__(self, key, value):
cls = self.__class__
if cls._is_reserved(key):
msg = "The attribute '{}'' is reserved and cannot be " \
"set.".format(key)
raise ReservedAttributeException(msg, key)
if cls._fails_schema(key, value):
valid = [str(t) for t in cls._schema[key]]
msg = "The attribute '{}' can only be these types: {}.\n"
"It therefore cannot be set to {}, which is of type {}.".format(
key, ', '.join(valid), value, type(value))
raise SchemaFailException(msg, key, cls._schema[key])
super().__setattr__(key, value)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment