"""Utilities
.. autosummary::
   :toctree:
   immutable
"""
import functools
import inspect
import attr
import hypothesis.strategies as st
[docs]def immutable(maybe_cls=None, eq=False, repr=False, **kwargs):
    return attr.s(
        maybe_cls=maybe_cls,
        frozen=True,
        eq=eq,
        order=False,
        hash=False,
        str=False,
        repr=repr,
        **kwargs
    ) 
def singleton(C):
    return C()
class decorator():
    """Base class for various decorators"""
    def __init__(self, f):
        self.f = f
        self.__doc__ = f.__doc__
        self.__name__ = f.__name__
        self.__module__ = f.__module__
        self.__defaults__ = f.__defaults__
        self.__kwdefaults__ = f.__kwdefaults__
        self.__annotations__ = f.__annotations__
        return
    def __call__(self, *args, **kwargs):
        return self.f(*args, **kwargs)
    @property
    def __code__(self):
        return self.f.__code__
    @property
    def __signature__(self):
        return inspect.signature(self.f)
    # The following properties are needed so that Sphinx recognizes (class)
    # methods. Note that these properties don't exist for normal functions.
    @property
    def __self__(self):
        return self.f.__self__
    @property
    def __func__(self):
        return self.f.__func__
class class_function(decorator):
    """Class method that isn't a method of the instances"""
    def __get__(self, obj, cls):
        if obj is None:
            return self.f.__get__(cls, type(cls))
        else:
            raise AttributeError(
                "'{0}' object has no attribute '{1}'".format(
                    cls.__name__,
                    self.f.__name__,
                )
            )
class class_property(decorator):
    """Class attribute that isn't an attribute of the instances
    To access the docstring, use ``__dict__`` as
    ``SomeClass.__dict__["some_attribute"].__doc__``
    """
    def __get__(self, obj, cls):
        if obj is None:
            return self.f.__get__(obj, cls)(cls)
        else:
            raise AttributeError(
                "'{0}' object has no attribute '{1}'".format(
                    cls.__name__,
                    self.f.__name__,
                )
            )
class abstract_function(decorator):
    """Function that has no implementation yet"""
    def __call__(self, *args, **kwargs):
        raise NotImplementedError(
            "'{0}' function is abstract".format(self.f.__name__)
        )
    def __get__(self, obj, cls):
        return abstract_function(self.f.__get__(obj, cls))
    @property
    def __code__(self):
        return self.f.__code__
    @property
    def __signature__(self):
        return inspect.signature(self.f)
class abstract_property(decorator):
    """Property that has no implementation yet
    To access the property ``abstract_property`` object without raising
    ``NotImplementedError``, use ``__dict__``. For instance, to access the
    docstring:
    .. code-block:: python
        class Foo():
            @abstract_property
            def bar(self):
                '''My docstring'''
        Foo.__dict__["bar"].__doc__
        isinstance(Foo.__dict__["bar"], abstract_property)
    """
    def __get__(self, obj, cls):
        self.f.__get__(obj, cls)
        raise NotImplementedError(
            "'{0}' attribute of type object '{1}' is abstract".format(
                self.f.__name__,
                cls.__name__,
            )
            if obj is None else
            "'{0}' attribute of object '{1}' is abstract".format(
                self.f.__name__,
                cls.__name__,
            )
        )
def abstract_class_property(f):
    return abstract_property(class_function(f))
def abstract_class_function(f):
    # Wrap the result gain with class_function so that we can recognize the
    # result as a class function when building the documentation.. A bit ugly
    # hack.. Probably there's a better way..
    return abstract_function(class_function(f))
@immutable
class nonexisting_function():
    """Mark method non-existing
    This is a workaround for Python forcefully creating some methods. One
    cannot create objects that don't have ``__eq__``, ``__ge__``, ``__gt__``
    and many other methods. They are there and it's not possible to delete
    them. With this wrapper you can override those methods so that they won't
    show up in ``__dir__`` listing and if accessed in any way,
    ``AttributeError`` is raised. Note that it just hides the methods, one can
    still access them as ``object.__getattribute__(obj, "__eq__")``.
    """
    method = attr.ib()
    cls = attr.ib(default=None)
    def __call__(self, *args, **kwargs):
        name = self.method.__name__
        # The method doesn't exist
        raise TypeError(
            "No {0} function".format(name)
            if self.cls is None else
            "Class {0} has no {1} method".format(self.cls.__name__, name)
        )
    def __get__(self, obj, objtype):
        # Bind the method to a class
        return nonexisting_function(self.method, cls=objtype)
def update_argspec(spec, args, kwargs):
    # TODO: Instead of running getfullargspec after every partial evaluation,
    # it might be faster to use the existing argspec and update that based on
    # args and kwargs. However, the speed gains might be quite small and one
    # needs to be very careful to implement exactly the same logic that Python
    # itself uses. It is possible that this logic changes from one Python
    # version to another, so it might become a maintenance nightmare. Still,
    # perhaps worth at least checking.
    #
    # Below is just some sketching.
    no_varargs = spec.varargs is None
    nargs_takes = len(spec.args)
    nargs_given = len(args)
    nargs_remain = nargs_takes - nargs_given
    if no_varargs and nargs_remain < 0:
        raise TypeError(
            "function takes {takes} positional argument but {given} were given"
            .format(
                name=name,
                takes=nargs_takes,
                given=nargs_given,
            )
        )
    new_args = spec.args[nargs_given:]
    new_defaults = spec.defaults[-nargs_remain:] if nargs_remain > 0 else None
    # FIXME:
    new_kwonlyargs = spec.kwonlyargs
    new_kwonlydefaults = spec.kwonlydefaults
    return inspect.FullArgSpec(
        args=new_args,
        varargs=spec.varargs,
        varkw=spec.varkw,
        defaults=new_defaults,
        kwonlyargs=new_kwonlyargs,
        kwonlydefaults=new_kwonlydefaults,
        # FIXME: What to do with this?
        annotations=spec.annotations,
    )
    pass
def count_required_arguments(argspec):
    # Positional arguments without defaults provided
    n_args = len(argspec.args) - (
        0 if argspec.defaults is None else
        len(argspec.defaults)
    )
    # Positional required arguments may get into required keyword
    # argument position if some positional arguments before them are
    # given as keyword arguments. For instance:
    #
    #   curry(lambda x, y: x - y)(x=5)
    #
    # Now, y becomes a keyword argument but it's still required as it
    # doesn't have any default value. Handle this by looking at
    # kwonlyargs that don't have a value in kwonlydefaults.
    defaults = (
        set() if argspec.kwonlydefaults is None else
        set(argspec.kwonlydefaults.keys())
    )
    kws = set(argspec.kwonlyargs)
    n_kw = len(kws.difference(defaults))
    return n_args + n_kw
@immutable
class Wrapped():
    """Original function that provides metainformation"""
    __unwrapped = attr.ib()
    """Wrapped function that is actually called"""
    __wrapped = attr.ib()
    def __call__(self, *args, **kwargs):
        return self.__wrapped(*args, **kwargs)
    def __repr__(self):
        return repr(self.__wrapped)
    @property
    def __module__(self):
        return self.__unwrapped.__module__
    @property
    def __signature__(self):
        return inspect.signature(self.__unwrapped)
    @property
    def __doc__(self):
        return self.__unwrapped.__doc__
def wraps(f):
    """Simple wrapping function similar to functools.wraps
    Aims to be a bit simpler and faster, but not sure about it. Experimenting
    at the moment.
    """
    def wrap(g):
        return Wrapped(f, g)
    return wrap
def identity(x):
    """a -> a"""
    return x
class PerformanceWarning(Warning):
    pass
@st.composite
def draw_args(draw, f, *args):
    return f(*(draw(a) for a in args))
@st.composite
def sample_type(draw, types, types1=[], types2=[]):
    if len(types) == 0:
        raise ValueError("Must provide at least one concrete type")
    arg = st.deferred(lambda: sample_type(types, types1, types2))
    return draw(
        st.one_of(
            # Concrete types
            *[st.just(t) for t in types],
            # One-argument type constructors
            *[
                draw_args(t1, arg)
                for t1 in types1
            ],
            # Two-argument type constructors
            *[
                draw_args(t2, arg, arg)
                for t2 in types2
            ],
        )
    )
def sample_sized(e, size=None):
    return (
        e if size is None else
        st.tuples(*(size * [e]))
    )
def eq_test(x, y, data, **kwargs):
    eq = getattr(x, "__eq_test__", lambda v, *_, **__: x == v)
    return eq(y, data, **kwargs)
def assert_output(f):
    """Assert that the output pair elements are equal"""
    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        xs = f(*args)
        x0 = xs[0]
        for xi in xs[1:]:
            assert eq_test(x0, xi, **kwargs)
        return
    return wrapped
@singleton
@immutable
class universal_set():
    """Universal set is a set that contains all objects"""
    def __contains__(self, _):
        return True
def make_test_class(C, typeclasses=universal_set):
    """Create a PyTest-compatible test class
    In order to test only some typeclass laws, give the set of typeclasses as
    ``typeclasses`` argument.
    When PyTest runs tests on a class, it creates an instance of the class and
    tests the instance. The class shouldn't have __init__ method. However, we
    want to test class methods, so there's no need to create an instance in the
    first place and it's ok to have any kind of __init__ method. To work around
    this PyTest limitation, we crete a class which doesn't have __init__ and
    when you call it's constructor, you actually don't get an instance of that
    class but instead the class that we wanted to test in the first place.
    PyTest thinks it got an instance of the class but actually we just gave it
    a class.
    So, to run the class method tests for a class SomeClass, add the following
    to some ``test_`` prefixed module:
    .. code-block:: python
        TestSomeClass = make_test_class(SomeClass)
    """
    classes = {cls for cls in C.mro() if cls in typeclasses}
    test_methods = {
        method
        for cls in classes
        for method in cls.__dict__.keys()
        if method.startswith("test_")
    }
    dct = {
        name: getattr(C, name)
        for name in dir(C)
        if name in test_methods
    }
    class MetaTestClass(type):
        __dict__ = dct
    class TestClass(metaclass=MetaTestClass):
        __dict__ = dct
        def __getattr__(self, name):
            return getattr(C, name)
    return TestClass