2a380bf10SimaromThis module contains fixups for using nose under different versions of Python.
4a380bf10Simaromimport sys
5a380bf10Simaromimport os
6a380bf10Simaromimport traceback
7a380bf10Simaromimport types
8a380bf10Simaromimport inspect
9a380bf10Simaromimport nose.util
11a380bf10Simarom__all__ = ['make_instancemethod', 'cmp_to_key', 'sort_list', 'ClassType',
12a380bf10Simarom           'TypeType', 'UNICODE_STRINGS', 'unbound_method', 'ismethod',
13a380bf10Simarom           'bytes_', 'is_base_exception', 'force_unicode', 'exc_to_unicode',
14a380bf10Simarom           'format_exception']
16a380bf10Simarom# In Python 3.x, all strings are unicode (the call to 'unicode()' in the 2.x
17a380bf10Simarom# source will be replaced with 'str()' when running 2to3, so this test will
18a380bf10Simarom# then become true)
19a380bf10SimaromUNICODE_STRINGS = (type(str()) == type(str()))
21a380bf10Simaromif sys.version_info[:2] < (3, 0):
22a380bf10Simarom    def force_unicode(s, encoding='UTF-8'):
23a380bf10Simarom        try:
24a380bf10Simarom            s = str(s)
25a380bf10Simarom        except UnicodeDecodeError:
26a380bf10Simarom            s = str(s).decode(encoding, 'replace')
28a380bf10Simarom        return s
30a380bf10Simarom    def force_unicode(s, encoding='UTF-8'):
31a380bf10Simarom        return str(s)
33a380bf10Simarom# new.instancemethod() is obsolete for new-style classes (Python 3.x)
34a380bf10Simarom# We need to use descriptor methods instead.
36a380bf10Simarom    import new
37a380bf10Simarom    def make_instancemethod(function, instance):
38a380bf10Simarom        return new.instancemethod(function.__func__, instance,
39a380bf10Simarom                                  instance.__class__)
40a380bf10Simaromexcept ImportError:
41a380bf10Simarom    def make_instancemethod(function, instance):
42a380bf10Simarom        return function.__get__(instance, instance.__class__)
44a380bf10Simarom# To be forward-compatible, we do all list sorts using keys instead of cmp
45a380bf10Simarom# functions.  However, part of the unittest.TestLoader API involves a
46a380bf10Simarom# user-provideable cmp function, so we need some way to convert that.
47a380bf10Simaromdef cmp_to_key(mycmp):
48a380bf10Simarom    'Convert a cmp= function into a key= function'
49a380bf10Simarom    class Key(object):
50a380bf10Simarom        def __init__(self, obj):
51a380bf10Simarom            self.obj = obj
52a380bf10Simarom        def __lt__(self, other):
53a380bf10Simarom            return mycmp(self.obj, other.obj) < 0
54a380bf10Simarom        def __gt__(self, other):
55a380bf10Simarom            return mycmp(self.obj, other.obj) > 0
56a380bf10Simarom        def __eq__(self, other):
57a380bf10Simarom            return mycmp(self.obj, other.obj) == 0
58a380bf10Simarom    return Key
60a380bf10Simarom# Python 2.3 also does not support list-sorting by key, so we need to convert
61a380bf10Simarom# keys to cmp functions if we're running on old Python..
62a380bf10Simaromif sys.version_info < (2, 4):
63a380bf10Simarom    def sort_list(l, key, reverse=False):
64a380bf10Simarom        if reverse:
65a380bf10Simarom            return l.sort(lambda a, b: cmp(key(b), key(a)))
66a380bf10Simarom        else:
67a380bf10Simarom            return l.sort(lambda a, b: cmp(key(a), key(b)))
69a380bf10Simarom    def sort_list(l, key, reverse=False):
70a380bf10Simarom        return l.sort(key=key, reverse=reverse)
72a380bf10Simarom# In Python 3.x, all objects are "new style" objects descended from 'type', and
73a380bf10Simarom# thus types.ClassType and types.TypeType don't exist anymore.  For
74a380bf10Simarom# compatibility, we make sure they still work.
75a380bf10Simaromif hasattr(types, 'ClassType'):
76a380bf10Simarom    ClassType = type
77a380bf10Simarom    TypeType = type
79a380bf10Simarom    ClassType = type
80a380bf10Simarom    TypeType = type
82a380bf10Simarom# The following emulates the behavior (we need) of an 'unbound method' under
83a380bf10Simarom# Python 3.x (namely, the ability to have a class associated with a function
84a380bf10Simarom# definition so that things can do stuff based on its associated class)
85a380bf10Simaromclass UnboundMethod:
86a380bf10Simarom    def __init__(self, cls, func):
87a380bf10Simarom        # Make sure we have all the same attributes as the original function,
88a380bf10Simarom        # so that the AttributeSelector plugin will work correctly...
89a380bf10Simarom        self.__dict__ = func.__dict__.copy()
90a380bf10Simarom        self._func = func
91a380bf10Simarom        self.__self__ = UnboundSelf(cls)
92a380bf10Simarom        if sys.version_info < (3, 0):
93a380bf10Simarom            self.__self__.__class__ = cls
95a380bf10Simarom    def address(self):
96a380bf10Simarom        cls = self.__self__.cls
97a380bf10Simarom        modname = cls.__module__
98a380bf10Simarom        module = sys.modules[modname]
99a380bf10Simarom        filename = getattr(module, '__file__', None)
100a380bf10Simarom        if filename is not None:
101a380bf10Simarom            filename = os.path.abspath(filename)
102a380bf10Simarom        return (nose.util.src(filename), modname, "%s.%s" % (cls.__name__,
103a380bf10Simarom                                                        self._func.__name__))
105a380bf10Simarom    def __call__(self, *args, **kwargs):
106a380bf10Simarom        return self._func(*args, **kwargs)
108a380bf10Simarom    def __getattr__(self, attr):
109a380bf10Simarom        return getattr(self._func, attr)
111a380bf10Simarom    def __repr__(self):
112a380bf10Simarom        return '<unbound method %s.%s>' % (self.__self__.cls.__name__,
113a380bf10Simarom                                           self._func.__name__)
115a380bf10Simaromclass UnboundSelf:
116a380bf10Simarom    def __init__(self, cls):
117a380bf10Simarom        self.cls = cls
119a380bf10Simarom    # We have to do this hackery because Python won't let us override the
120a380bf10Simarom    # __class__ attribute...
121a380bf10Simarom    def __getattribute__(self, attr):
122a380bf10Simarom        if attr == '__class__':
123a380bf10Simarom            return self.cls
124a380bf10Simarom        else:
125a380bf10Simarom            return object.__getattribute__(self, attr)
127a380bf10Simaromdef unbound_method(cls, func):
128a380bf10Simarom    if inspect.ismethod(func):
129a380bf10Simarom        return func
130a380bf10Simarom    if not inspect.isfunction(func):
131a380bf10Simarom        raise TypeError('%s is not a function' % (repr(func),))
132a380bf10Simarom    return UnboundMethod(cls, func)
134a380bf10Simaromdef ismethod(obj):
135a380bf10Simarom    return inspect.ismethod(obj) or isinstance(obj, UnboundMethod)
138a380bf10Simarom# Make a pseudo-bytes function that can be called without the encoding arg:
139a380bf10Simaromif sys.version_info >= (3, 0):
140a380bf10Simarom    def bytes_(s, encoding='utf8'):
141a380bf10Simarom        if isinstance(s, bytes):
142a380bf10Simarom            return s
143a380bf10Simarom        return bytes(s, encoding)
145a380bf10Simarom    def bytes_(s, encoding=None):
146a380bf10Simarom        return str(s)
149a380bf10Simaromif sys.version_info[:2] >= (2, 6):
150a380bf10Simarom    def isgenerator(o):
151a380bf10Simarom        if isinstance(o, UnboundMethod):
152a380bf10Simarom            o = o._func
153a380bf10Simarom        return inspect.isgeneratorfunction(o) or inspect.isgenerator(o)
155a380bf10Simarom    try:
156a380bf10Simarom        from compiler.consts import CO_GENERATOR
157a380bf10Simarom    except ImportError:
158a380bf10Simarom        # IronPython doesn't have a complier module
159a380bf10Simarom        CO_GENERATOR=0x20
161a380bf10Simarom    def isgenerator(func):
162a380bf10Simarom        try:
163a380bf10Simarom            return func.__code__.co_flags & CO_GENERATOR != 0
164a380bf10Simarom        except AttributeError:
165a380bf10Simarom            return False
167a380bf10Simarom# Make a function to help check if an exception is derived from BaseException.
168a380bf10Simarom# In Python 2.4, we just use Exception instead.
169a380bf10Simaromif sys.version_info[:2] < (2, 5):
170a380bf10Simarom    def is_base_exception(exc):
171a380bf10Simarom        return isinstance(exc, Exception)
173a380bf10Simarom    def is_base_exception(exc):
174a380bf10Simarom        return isinstance(exc, BaseException)
176a380bf10Simaromif sys.version_info[:2] < (3, 0):
177a380bf10Simarom    def exc_to_unicode(ev, encoding='utf-8'):
178a380bf10Simarom        if is_base_exception(ev):
179a380bf10Simarom            if not hasattr(ev, '__unicode__'):
180a380bf10Simarom                # 2.5-
181a380bf10Simarom                if not hasattr(ev, 'message'):
182a380bf10Simarom                    # 2.4
183a380bf10Simarom                    msg = len(ev.args) and ev.args[0] or ''
184a380bf10Simarom                else:
185a380bf10Simarom                    msg = ev.message
186a380bf10Simarom                msg = force_unicode(msg, encoding=encoding)
187a380bf10Simarom                clsname = force_unicode(ev.__class__.__name__,
188a380bf10Simarom                        encoding=encoding)
189a380bf10Simarom                ev = '%s: %s' % (clsname, msg)
190a380bf10Simarom        elif not isinstance(ev, str):
191a380bf10Simarom            ev = repr(ev)
193a380bf10Simarom        return force_unicode(ev, encoding=encoding)
195a380bf10Simarom    def exc_to_unicode(ev, encoding='utf-8'):
196a380bf10Simarom        return str(ev)
198a380bf10Simaromdef format_exception(exc_info, encoding='UTF-8'):
199a380bf10Simarom    ec, ev, tb = exc_info
201a380bf10Simarom    # Our exception object may have been turned into a string, and Python 3's
202a380bf10Simarom    # traceback.format_exception() doesn't take kindly to that (it expects an
203a380bf10Simarom    # actual exception object).  So we work around it, by doing the work
204a380bf10Simarom    # ourselves if ev is not an exception object.
205a380bf10Simarom    if not is_base_exception(ev):
206a380bf10Simarom        tb_data = force_unicode(
207a380bf10Simarom                ''.join(traceback.format_tb(tb)),
208a380bf10Simarom                encoding)
209a380bf10Simarom        ev = exc_to_unicode(ev)
210a380bf10Simarom        return tb_data + ev
211a380bf10Simarom    else:
212a380bf10Simarom        return force_unicode(
213a380bf10Simarom                ''.join(traceback.format_exception(*exc_info)),
214a380bf10Simarom                encoding)