Source code for pyjackson.generics

import inspect
import sys
from abc import abstractmethod
from functools import lru_cache, wraps
from typing import Hashable, Type, Union

from pyjackson.core import TYPE_FIELD_NAME_FIELD_NAME
from pyjackson.utils import flat_dict_repr, is_descriptor, turn_args_to_kwargs

SERIALIZER_MAPPING = dict()

_pv_major, _pv_minor = sys.version_info[:2]


def _register_serializer(cls, real_type):
    """Register cls as serializer for real_type"""
    if real_type is not None:
        if isinstance(real_type, Hashable) and real_type != list and real_type != dict:
            SERIALIZER_MAPPING[real_type] = cls


class _SerializerMetaMeta(type):
    """Metaclass for :class:`_SerializerMeta`

    Mostly needed to support correct isinstance, issubclass and == behaviour in some cases
    """

    def __eq__(self, other):
        if issubclass(other, self) and other._is_dynamic:
            return True
        return super(_SerializerMetaMeta, self).__eq__(other)

    def __hash__(self):
        return hash(self.__name__)

    def __subclasscheck__(self, subclass):
        return super(_SerializerMetaMeta, self).__subclasscheck__(subclass)

    def __instancecheck__(self, instance):
        return super(_SerializerMetaMeta, self).__instancecheck__(instance)

    @property
    def _is_dynamic(cls):
        return getattr(cls, '_dynamic', False)


class _SerializerMeta(type, metaclass=_SerializerMetaMeta):
    """
    Metaclass for :class:`Serializer`

    Adds serializer's real_type as it's base class and implements custom type and comparison logic.
    It is needed to support correct isinstance, issubclass and == behaviour in some cases.
    """

    def __new__(mcs, name, bases, namespace):
        real_type = namespace.get('real_type')
        if real_type is not None and real_type not in bases:
            bases = bases + (real_type,)
        return super(_SerializerMeta, mcs).__new__(mcs, name, bases, namespace)

    def __hash__(self):
        return hash(self.__name__)

    def __eq__(self, other):
        if other is self._metaclass:
            return True
        if isinstance(other, _SerializerMeta) and (self._is_dynamic and
                                                   other._is_dynamic) and self._class is other._class:
            return all(getattr(self, f) == getattr(other, f) for f in self._fields)
        return super(_SerializerMeta, self).__eq__(other)

    def __subclasscheck__(self, subclass):
        if self._is_dynamic:
            raise TypeError('Parameterized generics cannot be used with class or instance checks')
        if self.real_type is not None and self.real_type == subclass:
            return True
        return super(_SerializerMeta, self).__subclasscheck__(subclass)

    def __instancecheck__(self, instance):
        if self._is_dynamic:
            raise TypeError('Parameterized generics cannot be used with class or instance checks')
        if self.real_type is not None and isinstance(instance, self.real_type):
            return True
        return super(_SerializerMeta, self).__instancecheck__(instance)

    @property
    def _is_dynamic(cls):
        return getattr(cls, '_dynamic', False)

    @property
    def _metaclass(cls):
        return getattr(cls, '_parent_metaclass', type(cls))

    @property
    def _class(cls):
        return getattr(cls, '_parent_class', cls)

    @property
    def _fields(cls):
        return getattr(cls, '_init_args', tuple())


def _type_cache(func):
    cached = lru_cache(None)(func)

    @wraps(func)
    def inner(cls, *args, **kwargs):
        kwargs = turn_args_to_kwargs(cls.__init__, args, kwargs)
        try:
            return cached(cls, **kwargs)
        except TypeError:
            pass  # All real errors (not unhashable args) are raised below.
        return func(cls, **kwargs)

    return inner


def _get_defining_class(cls, method, method_name=None, mro=None, stop_class=None):
    mro = mro or inspect.getmro(cls)
    method_name = method_name or method.__name__
    for parent in mro:
        m = parent.__dict__.get(method_name)
        if m is not None and (m is method or getattr(m, '__func__', None) is method):
            return parent
        if stop_class is not None and parent is stop_class:
            return stop_class


class _class_no_data_descriptor:
    def __init__(self, cls, no_data_descriptor):
        self.cls = cls
        self.no_data_descriptor = no_data_descriptor

    def __get__(self, instance, owner):
        if instance is not None:
            raise AttributeError('You must have been initialized generic type {}. Don\'t do that ;)'.format(self.cls))
        return self.no_data_descriptor.__get__(owner, owner)


def _transform_to_class_methods(cls):
    metaclass = type(cls)
    mro = inspect.getmro(cls)
    ignore = {'__class__', '__dict__', '__bases__', '__name__', '__qualname__',
              '__mro__', '__subclasses__', '__init_subclass__', '__subclasshook__',
              '__instancecheck__', '__subclasscheck__', '__weakref__',
              '__new__', '__init__', '__getattribute__', '__setattr__',
              '__eq__'}
    metaclass_attrs = {}
    class_attrs = {}
    for name in dir(cls):
        if name in ignore:
            continue
        attr = getattr(cls, name)
        if callable(attr):
            if isinstance(attr, type):
                continue
            if inspect.ismethod(attr):
                # skip static and class methods
                # unbound methods are not methods, they are functions
                continue
            defining_class = _get_defining_class(cls, attr, name, mro, cls.real_type)
            if defining_class is cls.real_type:
                continue

            if name.startswith('__'):
                metaclass_attrs[name] = attr
            elif isinstance(defining_class.__dict__[name], staticmethod):
                class_attrs[name] = attr
            else:
                class_attrs[name] = classmethod(attr)
        elif inspect.isdatadescriptor(attr):
            metaclass_attrs[name] = attr
        elif is_descriptor(attr):
            class_attrs[name] = _class_no_data_descriptor(cls, attr)

    for name, attr in class_attrs.items():
        setattr(cls, name, attr)

    for name, attr in metaclass_attrs.items():
        setattr(metaclass, name, attr)


[docs]class Serializer(metaclass=_SerializerMeta): """ A base for defining custom serializers. # TODO definitely more docs here """ real_type = None # type: Type @_type_cache def __new__(cls, **kwargs): has_init = issubclass(_get_defining_class(cls, cls.__init__), Serializer) if getattr(cls, '_dynamic', False): if has_init: cls.__init__(cls, **kwargs) _transform_to_class_methods(cls) return cls if has_init: kwargs_str = flat_dict_repr(kwargs, func_order=cls.__init__) else: kwargs_str = '' metaclass = type(cls) metaclass_name = '{}Metaclass[{}]'.format(cls.__name__, kwargs_str) new_metaclass = type(metaclass_name, (metaclass,), {'_dynamic': True, '_parent_metaclass': metaclass}) type_name = '{}[{}]'.format(cls.__name__, kwargs_str) __dict__ = {'_dynamic': True, '_parent_class': cls, '_init_args': tuple(kwargs.keys())} if hasattr(cls, TYPE_FIELD_NAME_FIELD_NAME): type_field_name = getattr(cls, TYPE_FIELD_NAME_FIELD_NAME) type_field_value = getattr(cls, type_field_name) __dict__[type_field_name] = type_field_value new_type = new_metaclass(type_name, (cls,), __dict__) instance = new_type(**kwargs) return instance
[docs] @abstractmethod def deserialize(self, obj: dict) -> object: pass
[docs] @abstractmethod def serialize(self, instance: object) -> dict: pass
def __init_subclass__(cls, **kwargs): super(Serializer, cls).__init_subclass__(**kwargs) _register_serializer(cls, cls.real_type) def __eq__(self, other): if isinstance(other, _SerializerMeta): return False print('lolo' * 10) return super().__eq__(other) def __subclasscheck__(self, subclass): raise TypeError('Parameterized generics cannot be used with class or instance checks') def __instancecheck__(self, instance): raise TypeError('Parameterized generics cannot be used with class or instance checks')
# TODO def validate(self, obj):
[docs]class StaticSerializer(Serializer): """ An easier way to define a serializer if it has no 'generic' arguments. """
[docs] @classmethod @abstractmethod def deserialize(cls, obj: dict) -> object: pass
[docs] @classmethod @abstractmethod def serialize(cls, instance: object) -> dict: pass
SerializerType = Union[Type, Serializer]