Skip to content

Instantly share code, notes, and snippets.

@benmoose
Created December 16, 2018 22:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benmoose/4fc2434b9a70fc8a8a08c59a8dc95c5b to your computer and use it in GitHub Desktop.
Save benmoose/4fc2434b9a70fc8a8a08c59a8dc95c5b to your computer and use it in GitHub Desktop.
Decorator which helps model external input to APIs
import attr
def api_model(cls):
"""
API Model decorator for representing input from external sources.
It is primarily responsible for
- encoding incoming data in a class (for easy use in application code)
- providing methods to check the validity of data.
To activate validity checking, classes using this decorator should add the `get_validation_errors`
method.
"""
cls._from_dict = classmethod(api_model_from_dict)
if not hasattr(cls, "from_dict"):
cls.from_dict = classmethod(api_model_from_dict)
cls._from_db_model = classmethod(api_model_from_db_model)
if not hasattr(cls, "from_db_model"):
cls.from_db_model = classmethod(api_model_from_db_model)
cls._to_dict = api_model_to_dict
if not hasattr(cls, "to_dict"):
cls.to_dict = api_model_to_dict
if not hasattr(cls, "get_validation_errors"):
cls.get_validation_errors = lambda _: []
cls._is_valid = property(api_model_is_valid)
if not hasattr(cls, "is_valid"):
cls.is_valid = property(api_model_is_valid)
cls.__attrs_post_init__ = api_model_post_init
return attr.s(
cls,
these=api_model_get_ib_fields(cls.__slots__),
slots=True,
weakref_slot=False,
)
def api_model_post_init(self):
self.validation_errors = self.get_validation_errors()
if hasattr(self, "__post_init__"):
self.__post_init__()
def api_model_get_ib_fields(fields):
mapping = {field: attr.ib(default=None) for field in fields}
mapping["validation_errors"] = attr.ib(factory=list, repr=False)
return mapping
def api_model_to_dict(self, keep_empty_fields=False):
return attr.asdict(
self, filter=lambda k, v: _filter_attributes(k, v, keep_empty_fields)
)
def api_model_from_dict(cls, data):
args = {}
for attribute in cls.__slots__:
value = data.get(attribute)
if value is not None:
args[attribute] = value
return cls(**args)
def api_model_from_db_model(cls, db_model):
args = {}
for attribute in cls.__slots__:
value = getattr(db_model, attribute, None)
if value is not None:
args[attribute] = value
return cls(**args)
def api_model_is_valid(self):
return not bool(self.validation_errors)
def _filter_attributes(attribute, value):
return attribute.repr and value is not None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment