Source code for gentools.types

"""base classes and interfaces"""
import abc
import inspect
import sys
import typing as t
from collections import OrderedDict
from itertools import starmap

from .utils import PY2, CallableAsMethod

__all__ = [
    'Generable',
    'GeneratorCallable',
    'ReusableGenerator',
]

T_yield = t.TypeVar('T_yield')
T_send = t.TypeVar('T_send')
T_return = t.TypeVar('T_return')

if PY2:  # pragma: no cover
    from funcsigs import _empty, _VAR_POSITIONAL, _VAR_KEYWORD

    def __():
        yield
    GeneratorType = type(__())
    del __
else:
    from inspect import _empty, _VAR_POSITIONAL, _VAR_KEYWORD
    from types import GeneratorType


# adapted from BoundArguments.apply_defaults from python3.5
if sys.version_info < (3, 5):  # pragma: no cover
    def _apply_defaults(bound_sig):
        arguments = bound_sig.arguments
        new_arguments = []
        for name, param in bound_sig._signature.parameters.items():
            try:
                new_arguments.append((name, arguments[name]))
            except KeyError:
                if param.default is not _empty:
                    val = param.default
                elif param.kind is _VAR_POSITIONAL:
                    val = ()
                elif param.kind is _VAR_KEYWORD:
                    val = {}
                else:
                    # This BoundArguments was likely produced by
                    # Signature.bind_partial().
                    continue
                new_arguments.append((name, val))
        bound_sig.arguments = OrderedDict(new_arguments)
else:  # pragma: no cover
    _apply_defaults = inspect.BoundArguments.apply_defaults


[docs]class Generable(t.Generic[T_yield, T_send, T_return], t.Iterable[T_yield]): """ABC for generable objects. Any object where :meth:`~object.__iter__` returns a generator implements it. """
[docs] @abc.abstractmethod def __iter__(self): """ Returns ------- ~typing.Generator[T_yield, T_send, T_return] the generator iterator """ raise NotImplementedError()
Generable.register(GeneratorType)
[docs]class GeneratorCallable(t.Generic[T_yield, T_send, T_return]): """ABC for callables which return a generator. Note that :term:`generator functions <generator>` already implement this. """
[docs] def __call__(self, *args, **kwargs): """ Returns ------- ~typing.Generator[T_yield, T_send, T_return] the resulting generator """ raise NotImplementedError()
class ReusableGeneratorMeta(CallableAsMethod, type(Generable)): pass # from ``six.add_metaclass`` def _add_metaclass(metaclass): # pragma: no cover """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper
[docs]@_add_metaclass(ReusableGeneratorMeta) class ReusableGenerator(Generable[T_yield, T_send, T_return]): """base class for reusable generator functions Warning ------- * Do not subclass directly. Subclasses are created with the :func:`~gentools.core.reusable` decorator. * Instances if this class are only picklable on python 3.5+ """
[docs] def __init__(self, *args, **kwargs): self._bound_args = self.__signature__.bind(*args, **kwargs) _apply_defaults(self._bound_args)
[docs] def __iter__(self): return self.__wrapped__(*self._bound_args.args, **self._bound_args.kwargs)
[docs] def __eq__(self, other): if isinstance(other, self.__class__): return self._bound_args.arguments == other._bound_args.arguments return NotImplemented
[docs] def __ne__(self, other): if isinstance(other, self.__class__): return not self == other return NotImplemented
[docs] def __repr__(self): fields = starmap('{}={!r}'.format, self._bound_args.arguments.items()) return '{}({})'.format(self.__class__.__qualname__, ', '.join(fields))
[docs] def __hash__(self): return hash((self._bound_args.args, tuple(self._bound_args.kwargs.items())))
[docs] def replace(self, **kwargs): """create a new instance with certain fields replaced Parameters ---------- **kwargs fields to replace Returns ------- ReusableGenerator a copy with replaced fields """ copied = self.__signature__.bind(*self._bound_args.args, **self._bound_args.kwargs) copied.arguments.update(**kwargs) return self.__class__(*copied.args, **copied.kwargs)