Source code for rest_easy.patterns

"""
This class defines generic bases for a few design / architectural patterns
required by django-rest-easy, namely singleton and register.
"""

from functools import wraps
from six import with_metaclass

from rest_easy.exceptions import RestEasyException

__all__ = ['SingletonCreator', 'SingletonBase', 'Singleton', 'BaseRegister', 'RegisteredCreator']

[docs]class SingletonCreator(type): """ This metaclass wraps __init__ method of created class with singleton_decorator. This ensures that it's impossible to mess up the instance for example by calling __init__ with getattr. """
[docs] @staticmethod def singleton_decorator(func): """ We embed given function into checking if the first (zeroth) parameter of its call shall be initialised. :param func: instantiating function (usually __init__). :returns: embedded function function. """ @wraps(func) def wrapper(*args, **kwargs): """ This inner function checks init property of given instance and depending on its value calls the function or not. """ if args[0].sl_init: return func(*args, **kwargs) return None return wrapper
def __new__(mcs, name, bases, attrs): """ Wraps are awesome. Sometimes. """ if not (len(bases) == 1 and object in bases): if '__init__' in attrs: attrs['__init__'] = mcs.singleton_decorator(attrs['__init__']) return super(SingletonCreator, mcs).__new__(mcs, name, bases, attrs)
[docs]class SingletonBase(object): # pylint: disable=too-few-public-methods """ This class implements the singleton pattern using a metaclass and overriding default __new__ magic method's behaviour. It works together with SingletonCreator metaclass to create a Singleton base class. sl_init property is reserved, you can't use it in inheriting classes. """ _instance = None def __new__(cls, *_): """ This magic method override makes sure that only one instance will be created. """ if not isinstance(cls._instance, cls): cls._instance = super(SingletonBase, cls).__new__(cls) cls._instance.sl_init = True else: cls._instance.sl_init = False return cls._instance
[docs]class Singleton(with_metaclass(SingletonCreator, SingletonBase)): # pylint: disable=too-few-public-methods """ This is a Singleton you can inherit from. It reserves sl_init instance attribute to work properly. """
[docs]class BaseRegister(Singleton): """ This class is a base register-type class. You should inherit from it to create particular registers. conflict_policy is a setting deciding what to do in case of name collision (registering another entity with the same name). It should be one of: * allow - replace old entry with new entry, return True, * deny - leave old entry, return False, * raise - raise RestEasyException. Default policy is raise. As this is a singleton, instantiating a particular children class in any place will yield the exact same data as the register instance used in RegisteredCreator(). """ conflict_policy = 'allow'
[docs] @classmethod def get_conflict_policy(cls): """ Obtain conflict policy from django settings or use default. Allowed settings are 'raise' and 'allow'. Default is 'raise'. """ from django.conf import settings return getattr(settings, 'REST_EASY_SERIALIZER_CONFLICT_POLICY', cls.conflict_policy)
def __init__(self): """ We create an empty model dict. """ self._entries = {} self.connect = lambda: None
[docs] def register(self, name, ref): """ Register an entry, shall we? :param name: entry name. :param ref: entry value (probably class). :returns: True if model was added just now, False if it was already in the register. """ if not self.lookup(name) or self.get_conflict_policy() == 'allow': self._entries[name] = ref return True raise RestEasyException('Entry named {} is already registered.'.format(name))
[docs] def lookup(self, name): """ I like to know if an entry is in the register, don't you? :param name: name to check. :returns: True if entry with given name is in the register, False otherwise. """ return self._entries.get(name, None)
[docs] def entries(self): """ Return an iterator over all registered entries. """ return self._entries.items()
[docs]class RegisteredCreator(type): """ This metaclass integrates classes with a BaseRegister subclass. It skips processing base/abstract classes, which have __abstract__ property evaluating to True. """ register = None required_fields = set() inherit_fields = False
[docs] @staticmethod def get_name(name, bases, attrs): # pylint: disable=unused-argument """ Get name to be used for class registration. """ return name
[docs] @staticmethod def get_fields_from_base(base): """ Obtains all fields from the base class. :param base: base class. :return: generator of (name, value) tuples. """ for item in dir(base): if not item.startswith('_'): value = getattr(base, item) if not callable(value): yield item, getattr(base, item)
[docs] @classmethod def process_required_field(mcs, missing, fields, name, value): """ Processes a single required field to check if it applies to constraints. """ try: if not hasattr(fields, name) and name not in fields: missing.append(name) return except TypeError: missing.append(name) return if value: if hasattr(fields, name): inner = getattr(fields, name) else: inner = fields[name] if callable(value): if not value(inner): missing += [name] else: missing += [name + '.' + item for item in mcs.get_missing_fields(value, inner)]
[docs] @classmethod def get_missing_fields(mcs, required_fields, fields): """ Lists required fields that are missing. Supports two formats of input of required fields: either a simple set {'a', 'b'} or a dict with several options:: { 'nested': { 'presence_check_only': None, 'functional_check': lambda value: isinstance(value, Model) }, 'flat_presence_check': None, 'flat_functional_check': lambda value: isinstance(value, Model) } Functional checks need to return true for field not to be marked as missing. Dict-format also supports both dict and attribute based accesses for fields (fields['a'] and fields.a). :param required_fields: set or dict of required fields. :param fields: dict or object of actual fields. :return: List of missing fields. """ if isinstance(required_fields, set): return [field for field in required_fields if field not in fields or not field] missing = [] for name, value in required_fields.items(): mcs.process_required_field(missing, fields, name, value) return missing
[docs] @classmethod def pre_register(mcs, name, bases, attrs): """ Pre-register hook. :param name: class name. :param bases: class bases. :param attrs: class attributes. :return: Modified tuple (name, bases, attrs) """ return name, bases, attrs
[docs] @classmethod def post_register(mcs, cls, name, bases, attrs): """ Post-register hook. :param cls: created class. :param name: class name. :param bases: class bases. :param attrs: class attributes. :return: None. """
def __new__(mcs, name, bases, attrs): """ This method creates and registers new class, if it's not already in the register. """ # Do not register the base classes, which actual classes inherit. if mcs.inherit_fields: for base in bases: for field, value in mcs.get_fields_from_base(base): if field not in attrs: attrs[field] = value if not attrs.get('__abstract__', False): missing = mcs.get_missing_fields(mcs.required_fields, attrs) if missing: raise RestEasyException( 'The following mandatory fields are missing from {} class definition: {}'.format( name, ', '.join(missing) ) ) name, bases, attrs = mcs.pre_register(name, bases, attrs) slug = mcs.get_name(name, bases, attrs) cls = super(RegisteredCreator, mcs).__new__(mcs, name, bases, attrs) mcs.register.register(slug, cls) mcs.post_register(cls, name, bases, attrs) else: cls = super(RegisteredCreator, mcs).__new__(mcs, name, bases, attrs) return cls