aboutsummaryrefslogtreecommitdiff
blob: f8d8d9713f1bb58d46b9881bc020f7716a5cf635 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""
Function currying, generating a functor with a set of args/defaults pre bound.

:py:func:`pre_curry` and :py:func:`post_curry` return "normal" python functions
The difference between :py:func:`pre_curry` and :py:func:`functools.partial`
is this

>>> from functools import partial
>>> from snakeoil.currying import pre_curry
>>> def func(arg=None, self=None):
...   return arg, self
>>> curry = pre_curry(func, True)
>>> part = partial(func, True)
>>> class Test:
...   curry = pre_curry(func, True)
...   part = partial(func, True)
...   def __repr__(self):
...      return '<Test object>'
>>> curry()
(True, None)
>>> Test().curry()
(True, <Test object>)
>>> part()
(True, None)
>>> Test().part()
(True, None)

If your curried function is not used as a class attribute the results should be
identical. Because :py:func:`functools.partial` has an implementation in C
while :py:func:`pre_curry` is python you should use :py:func:`functools.partial`
if possible.
"""

import sys
from functools import partial

from .compatibility import IGNORED_EXCEPTIONS

__all__ = ("pre_curry", "post_curry", "pretty_docs")


def pre_curry(func, *args, **kwargs):
    """passed in args are prefixed, with further args appended

    Unlike partial, this is usable as an instancemethod.
    """

    if not kwargs:
        def callit(*moreargs, **morekwargs):
            return func(*(args + moreargs), **morekwargs)
    elif not args:
        def callit(*moreargs, **morekwargs):
            kw = kwargs.copy()
            kw.update(morekwargs)
            return func(*moreargs, **kw)
    else:
        def callit(*moreargs, **morekwargs):
            kw = kwargs.copy()
            kw.update(morekwargs)
            return func(*(args + moreargs), **kw)

    callit.func = func
    return callit


def post_curry(func, *args, **kwargs):
    """passed in args are appended to any further args supplied"""

    if not kwargs:
        def callit(*moreargs, **morekwargs):
            return func(*(moreargs + args), **morekwargs)
    elif not args:
        def callit(*moreargs, **morekwargs):
            kw = morekwargs.copy()
            kw.update(kwargs)
            return func(*moreargs, **kw)
    else:
        def callit(*moreargs, **morekwargs):
            kw = morekwargs.copy()
            kw.update(kwargs)
            return func(*(moreargs + args), **kw)

    callit.func = func
    return callit


def pretty_docs(wrapped, extradocs=None, name=None):
    """
    Modify wrapped, so that it 'looks' like what it's wrapping.

    This is primarily useful for introspection reasons- doc generators, direct
    user interaction with an object in the interpretter, etc.

    :param wrapped: functor to modify
    :param extradocs: ``__doc__`` override for wrapped; else it pulls from
        wrapped's target functor
    :param name: ``__name__`` override for wrapped; else it pulls from
        wrapped's target functor for the name.
    """
    wrapped.__module__ = wrapped.func.__module__
    doc = wrapped.func.__doc__
    if extradocs is None:
        wrapped.__doc__ = doc
    else:
        wrapped.__doc__ = extradocs
    if name:
        wrapped.__name__ = name
    return wrapped


def wrap_exception(recast_exception, *args, **kwds):
    # set this here so that 2to3 will rewrite it.
    try:
        if not issubclass(recast_exception, Exception):
            raise ValueError("recast_exception must be an %s derivative: got %r" %
                             (Exception, recast_exception))
    except TypeError as e:
        raise TypeError("recast_exception must be an %s derivative; got %r, failed %r",
                        (Exception.__name__, recast_exception, e))
    ignores = kwds.pop("ignores", (recast_exception,))
    pass_error = kwds.pop("pass_error", None)
    return wrap_exception_complex(partial(_simple_throw, recast_exception, args, kwds, pass_error), ignores)


def _simple_throw(recast_exception, recast_args, recast_kwds, pass_error,
                  exception, functor, args, kwds):
    if pass_error:
        recast_kwds[pass_error] = exception
    return recast_exception(*recast_args, **recast_kwds)


def wrap_exception_complex(creation_func, ignores):
    try:
        if not hasattr(ignores, '__iter__') and issubclass(ignores, Exception) or ignores is Exception:
            ignores = (ignores,)
        ignores = tuple(ignores)
    except TypeError as e:
        raise TypeError("ignores must be either a tuple of %s, or a %s: got %r, error %r"
                        % (Exception.__name__, Exception.__name__, ignores, e))
    if not all(issubclass(x, Exception) for x in ignores):
        raise TypeError("ignores has a non %s derivative in it: %r" %
                        (Exception.__name__, ignores))
    return partial(_inner_wrap_exception, creation_func, ignores)


def _inner_wrap_exception(exception_maker, ignores, functor):
    def _wrap_exception(*args, **kwargs):
        try:
            return functor(*args, **kwargs)
        except IGNORED_EXCEPTIONS:
            raise
        except ignores:
            raise
        except Exception as e:
            raise exception_maker(e, functor, args, kwargs) from e
    _wrap_exception.func = functor
    return pretty_docs(_wrap_exception, name=functor.__name__)