1import functools
2import sys
3
4
5PY3K = sys.version_info >= (3,)
6
7
8methods = set([
9    "__iter__",
10    "__len__",
11    "__contains__",
12    "__getitem__",
13    "__setitem__",
14    "__delitem__",
15
16    "__enter__",
17    "__exit__",
18
19    "__lt__",
20    "__le__",
21    "__eq__",
22    "__ne__",
23    "__gt__",
24    "__ge__",
25
26    "__add__",
27    "__and__",
28    "__divmod__",
29    "__floordiv__",
30    "__lshift__",
31    "__mod__",
32    "__mul__",
33    "__or__",
34    "__pow__",
35    "__rshift__",
36    "__sub__",
37    "__truediv__",
38    "__xor__",
39
40    "__call__",
41    "__repr__",
42])
43if PY3K:
44    methods.add("__next__")
45    methods.add("__bool__")
46else:
47    methods.add("__div__")
48    methods.add("__nonzero__")
49MAGIC_METHODS = frozenset(methods)
50del methods
51
52
53def _build_magic_dispatcher(method):
54    def inner(self, *args, **kwargs):
55        return self.__dict__[method](*args, **kwargs)
56    inner.__name__ = method
57    return inner
58
59
60class stub(object):
61    _classes_cache = {}
62
63    def __new__(cls, **kwargs):
64        magic_methods_present = MAGIC_METHODS.intersection(kwargs)
65        if magic_methods_present not in cls._classes_cache:
66            attrs = dict(
67                (method, _build_magic_dispatcher(method))
68                for method in magic_methods_present
69            )
70            attrs["__module__"] = cls.__module__
71            cls._classes_cache[magic_methods_present] = (
72                type("stub", (cls,), attrs)
73            )
74        new_cls = cls._classes_cache[magic_methods_present]
75        return super(stub, new_cls).__new__(new_cls)
76
77    def __init__(self, **kwargs):
78        self.__dict__.update(kwargs)
79
80    def __repr__(self):
81        return '<stub(%s)>' % ', '.join([
82            '%s=%r' % (key, val)
83            for key, val in self.__dict__.items()
84        ])
85
86
87def raiser(exc):
88    if (
89        not (
90            isinstance(exc, BaseException) or
91            isinstance(exc, type) and issubclass(exc, BaseException)
92        )
93    ):
94        raise TypeError("exc must be either an exception instance or class.")
95
96    def inner(*args, **kwargs):
97        raise exc
98    return inner
99
100
101class call(object):
102    def __init__(self, *args, **kwargs):
103        self.args = args
104        self.kwargs = kwargs
105
106    def __eq__(self, other):
107        if not isinstance(other, call):
108            return NotImplemented
109        return self.args == other.args and self.kwargs == other.kwargs
110
111    def __ne__(self, other):
112        return not (self == other)
113
114    def __hash__(self):
115        return hash((
116            self.args,
117            frozenset(self.kwargs.items())
118        ))
119
120    def __repr__(self):
121        args = ", ".join(map(repr, self.args))
122        kwargs = ", ".join("%s=%r" % (k, v) for k, v in self.kwargs.items())
123        comma = ", " if args and kwargs else ""
124        return "<call(%s%s%s)>" % (args, comma, kwargs)
125
126
127def call_recorder(func):
128    @functools.wraps(func)
129    def inner(*args, **kwargs):
130        inner.calls.append(call(*args, **kwargs))
131        return func(*args, **kwargs)
132    inner.calls = []
133    return inner
134