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