1import py
2import sys
3from inspect import CO_VARARGS, CO_VARKEYWORDS, isclass
4
5builtin_repr = repr
6
7reprlib = py.builtin._tryimport('repr', 'reprlib')
8
9if sys.version_info[0] >= 3:
10    from traceback import format_exception_only
11else:
12    from py._code._py2traceback import format_exception_only
13
14import traceback
15
16
17class Code(object):
18    """ wrapper around Python code objects """
19    def __init__(self, rawcode):
20        if not hasattr(rawcode, "co_filename"):
21            rawcode = py.code.getrawcode(rawcode)
22        try:
23            self.filename = rawcode.co_filename
24            self.firstlineno = rawcode.co_firstlineno - 1
25            self.name = rawcode.co_name
26        except AttributeError:
27            raise TypeError("not a code object: %r" % (rawcode,))
28        self.raw = rawcode
29
30    def __eq__(self, other):
31        return self.raw == other.raw
32
33    def __ne__(self, other):
34        return not self == other
35
36    @property
37    def path(self):
38        """ return a path object pointing to source code (note that it
39        might not point to an actually existing file). """
40        p = py.path.local(self.raw.co_filename)
41        # maybe don't try this checking
42        if not p.check():
43            # XXX maybe try harder like the weird logic
44            # in the standard lib [linecache.updatecache] does?
45            p = self.raw.co_filename
46        return p
47
48    @property
49    def fullsource(self):
50        """ return a py.code.Source object for the full source file of the code
51        """
52        from py._code import source
53        full, _ = source.findsource(self.raw)
54        return full
55
56    def source(self):
57        """ return a py.code.Source object for the code object's source only
58        """
59        # return source only for that part of code
60        return py.code.Source(self.raw)
61
62    def getargs(self, var=False):
63        """ return a tuple with the argument names for the code object
64
65            if 'var' is set True also return the names of the variable and
66            keyword arguments when present
67        """
68        # handfull shortcut for getting args
69        raw = self.raw
70        argcount = raw.co_argcount
71        if var:
72            argcount += raw.co_flags & CO_VARARGS
73            argcount += raw.co_flags & CO_VARKEYWORDS
74        return raw.co_varnames[:argcount]
75
76class Frame(object):
77    """Wrapper around a Python frame holding f_locals and f_globals
78    in which expressions can be evaluated."""
79
80    def __init__(self, frame):
81        self.lineno = frame.f_lineno - 1
82        self.f_globals = frame.f_globals
83        self.f_locals = frame.f_locals
84        self.raw = frame
85        self.code = py.code.Code(frame.f_code)
86
87    @property
88    def statement(self):
89        """ statement this frame is at """
90        if self.code.fullsource is None:
91            return py.code.Source("")
92        return self.code.fullsource.getstatement(self.lineno)
93
94    def eval(self, code, **vars):
95        """ evaluate 'code' in the frame
96
97            'vars' are optional additional local variables
98
99            returns the result of the evaluation
100        """
101        f_locals = self.f_locals.copy()
102        f_locals.update(vars)
103        return eval(code, self.f_globals, f_locals)
104
105    def exec_(self, code, **vars):
106        """ exec 'code' in the frame
107
108            'vars' are optiona; additional local variables
109        """
110        f_locals = self.f_locals.copy()
111        f_locals.update(vars)
112        py.builtin.exec_(code, self.f_globals, f_locals)
113
114    def repr(self, object):
115        """ return a 'safe' (non-recursive, one-line) string repr for 'object'
116        """
117        return py.io.saferepr(object)
118
119    def is_true(self, object):
120        return object
121
122    def getargs(self, var=False):
123        """ return a list of tuples (name, value) for all arguments
124
125            if 'var' is set True also include the variable and keyword
126            arguments when present
127        """
128        retval = []
129        for arg in self.code.getargs(var):
130            try:
131                retval.append((arg, self.f_locals[arg]))
132            except KeyError:
133                pass     # this can occur when using Psyco
134        return retval
135
136
137class TracebackEntry(object):
138    """ a single entry in a traceback """
139
140    _repr_style = None
141    exprinfo = None
142
143    def __init__(self, rawentry):
144        self._rawentry = rawentry
145        self.lineno = rawentry.tb_lineno - 1
146
147    def set_repr_style(self, mode):
148        assert mode in ("short", "long")
149        self._repr_style = mode
150
151    @property
152    def frame(self):
153        return py.code.Frame(self._rawentry.tb_frame)
154
155    @property
156    def relline(self):
157        return self.lineno - self.frame.code.firstlineno
158
159    def __repr__(self):
160        return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno+1)
161
162    @property
163    def statement(self):
164        """ py.code.Source object for the current statement """
165        source = self.frame.code.fullsource
166        return source.getstatement(self.lineno)
167
168    @property
169    def path(self):
170        """ path to the source code """
171        return self.frame.code.path
172
173    def getlocals(self):
174        return self.frame.f_locals
175    locals = property(getlocals, None, None, "locals of underlaying frame")
176
177    def reinterpret(self):
178        """Reinterpret the failing statement and returns a detailed information
179           about what operations are performed."""
180        if self.exprinfo is None:
181            source = str(self.statement).strip()
182            x = py.code._reinterpret(source, self.frame, should_fail=True)
183            if not isinstance(x, str):
184                raise TypeError("interpret returned non-string %r" % (x,))
185            self.exprinfo = x
186        return self.exprinfo
187
188    def getfirstlinesource(self):
189        # on Jython this firstlineno can be -1 apparently
190        return max(self.frame.code.firstlineno, 0)
191
192    def getsource(self, astcache=None):
193        """ return failing source code. """
194        # we use the passed in astcache to not reparse asttrees
195        # within exception info printing
196        from py._code.source import getstatementrange_ast
197        source = self.frame.code.fullsource
198        if source is None:
199            return None
200        key = astnode = None
201        if astcache is not None:
202            key = self.frame.code.path
203            if key is not None:
204                astnode = astcache.get(key, None)
205        start = self.getfirstlinesource()
206        try:
207            astnode, _, end = getstatementrange_ast(self.lineno, source,
208                                                    astnode=astnode)
209        except SyntaxError:
210            end = self.lineno + 1
211        else:
212            if key is not None:
213                astcache[key] = astnode
214        return source[start:end]
215
216    source = property(getsource)
217
218    def ishidden(self):
219        """ return True if the current frame has a var __tracebackhide__
220            resolving to True
221
222            mostly for internal use
223        """
224        try:
225            return self.frame.f_locals['__tracebackhide__']
226        except KeyError:
227            try:
228                return self.frame.f_globals['__tracebackhide__']
229            except KeyError:
230                return False
231
232    def __str__(self):
233        try:
234            fn = str(self.path)
235        except py.error.Error:
236            fn = '???'
237        name = self.frame.code.name
238        try:
239            line = str(self.statement).lstrip()
240        except KeyboardInterrupt:
241            raise
242        except:
243            line = "???"
244        return "  File %r:%d in %s\n  %s\n" % (fn, self.lineno+1, name, line)
245
246    def name(self):
247        return self.frame.code.raw.co_name
248    name = property(name, None, None, "co_name of underlaying code")
249
250
251class Traceback(list):
252    """ Traceback objects encapsulate and offer higher level
253        access to Traceback entries.
254    """
255    Entry = TracebackEntry
256
257    def __init__(self, tb):
258        """ initialize from given python traceback object. """
259        if hasattr(tb, 'tb_next'):
260            def f(cur):
261                while cur is not None:
262                    yield self.Entry(cur)
263                    cur = cur.tb_next
264            list.__init__(self, f(tb))
265        else:
266            list.__init__(self, tb)
267
268    def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
269        """ return a Traceback instance wrapping part of this Traceback
270
271            by provding any combination of path, lineno and firstlineno, the
272            first frame to start the to-be-returned traceback is determined
273
274            this allows cutting the first part of a Traceback instance e.g.
275            for formatting reasons (removing some uninteresting bits that deal
276            with handling of the exception/traceback)
277        """
278        for x in self:
279            code = x.frame.code
280            codepath = code.path
281            if ((path is None or codepath == path) and
282                (excludepath is None or not hasattr(codepath, 'relto') or
283                 not codepath.relto(excludepath)) and
284                (lineno is None or x.lineno == lineno) and
285                (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
286                return Traceback(x._rawentry)
287        return self
288
289    def __getitem__(self, key):
290        val = super(Traceback, self).__getitem__(key)
291        if isinstance(key, type(slice(0))):
292            val = self.__class__(val)
293        return val
294
295    def filter(self, fn=lambda x: not x.ishidden()):
296        """ return a Traceback instance with certain items removed
297
298            fn is a function that gets a single argument, a TracebackItem
299            instance, and should return True when the item should be added
300            to the Traceback, False when not
301
302            by default this removes all the TracebackItems which are hidden
303            (see ishidden() above)
304        """
305        return Traceback(filter(fn, self))
306
307    def getcrashentry(self):
308        """ return last non-hidden traceback entry that lead
309        to the exception of a traceback.
310        """
311        for i in range(-1, -len(self)-1, -1):
312            entry = self[i]
313            if not entry.ishidden():
314                return entry
315        return self[-1]
316
317    def recursionindex(self):
318        """ return the index of the frame/TracebackItem where recursion
319            originates if appropriate, None if no recursion occurred
320        """
321        cache = {}
322        for i, entry in enumerate(self):
323            # id for the code.raw is needed to work around
324            # the strange metaprogramming in the decorator lib from pypi
325            # which generates code objects that have hash/value equality
326            #XXX needs a test
327            key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
328            #print "checking for recursion at", key
329            l = cache.setdefault(key, [])
330            if l:
331                f = entry.frame
332                loc = f.f_locals
333                for otherloc in l:
334                    if f.is_true(f.eval(co_equal,
335                        __recursioncache_locals_1=loc,
336                        __recursioncache_locals_2=otherloc)):
337                        return i
338            l.append(entry.frame.f_locals)
339        return None
340
341co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
342                   '?', 'eval')
343
344class ExceptionInfo(object):
345    """ wraps sys.exc_info() objects and offers
346        help for navigating the traceback.
347    """
348    _striptext = ''
349    def __init__(self, tup=None, exprinfo=None):
350        if tup is None:
351            tup = sys.exc_info()
352            if exprinfo is None and isinstance(tup[1], AssertionError):
353                exprinfo = getattr(tup[1], 'msg', None)
354                if exprinfo is None:
355                    exprinfo = str(tup[1])
356                if exprinfo and exprinfo.startswith('assert '):
357                    self._striptext = 'AssertionError: '
358        self._excinfo = tup
359        #: the exception class
360        self.type = tup[0]
361        #: the exception instance
362        self.value = tup[1]
363        #: the exception raw traceback
364        self.tb = tup[2]
365        #: the exception type name
366        self.typename = self.type.__name__
367        #: the exception traceback (py.code.Traceback instance)
368        self.traceback = py.code.Traceback(self.tb)
369
370    def __repr__(self):
371        return "<ExceptionInfo %s tblen=%d>" % (
372            self.typename, len(self.traceback))
373
374    def exconly(self, tryshort=False):
375        """ return the exception as a string
376
377            when 'tryshort' resolves to True, and the exception is a
378            py.code._AssertionError, only the actual exception part of
379            the exception representation is returned (so 'AssertionError: ' is
380            removed from the beginning)
381        """
382        lines = format_exception_only(self.type, self.value)
383        text = ''.join(lines)
384        text = text.rstrip()
385        if tryshort:
386            if text.startswith(self._striptext):
387                text = text[len(self._striptext):]
388        return text
389
390    def errisinstance(self, exc):
391        """ return True if the exception is an instance of exc """
392        return isinstance(self.value, exc)
393
394    def _getreprcrash(self):
395        exconly = self.exconly(tryshort=True)
396        entry = self.traceback.getcrashentry()
397        path, lineno = entry.frame.code.raw.co_filename, entry.lineno
398        return ReprFileLocation(path, lineno+1, exconly)
399
400    def getrepr(self, showlocals=False, style="long",
401                abspath=False, tbfilter=True, funcargs=False):
402        """ return str()able representation of this exception info.
403            showlocals: show locals per traceback entry
404            style: long|short|no|native traceback style
405            tbfilter: hide entries (where __tracebackhide__ is true)
406
407            in case of style==native, tbfilter and showlocals is ignored.
408        """
409        if style == 'native':
410            return ReprExceptionInfo(ReprTracebackNative(
411                traceback.format_exception(
412                    self.type,
413                    self.value,
414                    self.traceback[0]._rawentry,
415                )), self._getreprcrash())
416
417        fmt = FormattedExcinfo(
418            showlocals=showlocals, style=style,
419            abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
420        return fmt.repr_excinfo(self)
421
422    def __str__(self):
423        entry = self.traceback[-1]
424        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
425        return str(loc)
426
427    def __unicode__(self):
428        entry = self.traceback[-1]
429        loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
430        return loc.__unicode__()
431
432
433class FormattedExcinfo(object):
434    """ presenting information about failing Functions and Generators. """
435    # for traceback entries
436    flow_marker = ">"
437    fail_marker = "E"
438
439    def __init__(self, showlocals=False, style="long",
440                 abspath=True, tbfilter=True, funcargs=False):
441        self.showlocals = showlocals
442        self.style = style
443        self.tbfilter = tbfilter
444        self.funcargs = funcargs
445        self.abspath = abspath
446        self.astcache = {}
447
448    def _getindent(self, source):
449        # figure out indent for given source
450        try:
451            s = str(source.getstatement(len(source)-1))
452        except KeyboardInterrupt:
453            raise
454        except:
455            try:
456                s = str(source[-1])
457            except KeyboardInterrupt:
458                raise
459            except:
460                return 0
461        return 4 + (len(s) - len(s.lstrip()))
462
463    def _getentrysource(self, entry):
464        source = entry.getsource(self.astcache)
465        if source is not None:
466            source = source.deindent()
467        return source
468
469    def _saferepr(self, obj):
470        return py.io.saferepr(obj)
471
472    def repr_args(self, entry):
473        if self.funcargs:
474            args = []
475            for argname, argvalue in entry.frame.getargs(var=True):
476                args.append((argname, self._saferepr(argvalue)))
477            return ReprFuncArgs(args)
478
479    def get_source(self, source, line_index=-1, excinfo=None, short=False):
480        """ return formatted and marked up source lines. """
481        lines = []
482        if source is None or line_index >= len(source.lines):
483            source = py.code.Source("???")
484            line_index = 0
485        if line_index < 0:
486            line_index += len(source)
487        space_prefix = "    "
488        if short:
489            lines.append(space_prefix + source.lines[line_index].strip())
490        else:
491            for line in source.lines[:line_index]:
492                lines.append(space_prefix + line)
493            lines.append(self.flow_marker + "   " + source.lines[line_index])
494            for line in source.lines[line_index+1:]:
495                lines.append(space_prefix + line)
496        if excinfo is not None:
497            indent = 4 if short else self._getindent(source)
498            lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
499        return lines
500
501    def get_exconly(self, excinfo, indent=4, markall=False):
502        lines = []
503        indent = " " * indent
504        # get the real exception information out
505        exlines = excinfo.exconly(tryshort=True).split('\n')
506        failindent = self.fail_marker + indent[1:]
507        for line in exlines:
508            lines.append(failindent + line)
509            if not markall:
510                failindent = indent
511        return lines
512
513    def repr_locals(self, locals):
514        if self.showlocals:
515            lines = []
516            keys = [loc for loc in locals if loc[0] != "@"]
517            keys.sort()
518            for name in keys:
519                value = locals[name]
520                if name == '__builtins__':
521                    lines.append("__builtins__ = <builtins>")
522                else:
523                    # This formatting could all be handled by the
524                    # _repr() function, which is only reprlib.Repr in
525                    # disguise, so is very configurable.
526                    str_repr = self._saferepr(value)
527                    #if len(str_repr) < 70 or not isinstance(value,
528                    #                            (list, tuple, dict)):
529                    lines.append("%-10s = %s" %(name, str_repr))
530                    #else:
531                    #    self._line("%-10s =\\" % (name,))
532                    #    # XXX
533                    #    pprint.pprint(value, stream=self.excinfowriter)
534            return ReprLocals(lines)
535
536    def repr_traceback_entry(self, entry, excinfo=None):
537        source = self._getentrysource(entry)
538        if source is None:
539            source = py.code.Source("???")
540            line_index = 0
541        else:
542            # entry.getfirstlinesource() can be -1, should be 0 on jython
543            line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
544
545        lines = []
546        style = entry._repr_style
547        if style is None:
548            style = self.style
549        if style in ("short", "long"):
550            short = style == "short"
551            reprargs = self.repr_args(entry) if not short else None
552            s = self.get_source(source, line_index, excinfo, short=short)
553            lines.extend(s)
554            if short:
555                message = "in %s" %(entry.name)
556            else:
557                message = excinfo and excinfo.typename or ""
558            path = self._makepath(entry.path)
559            filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
560            localsrepr = None
561            if not short:
562                localsrepr =  self.repr_locals(entry.locals)
563            return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
564        if excinfo:
565            lines.extend(self.get_exconly(excinfo, indent=4))
566        return ReprEntry(lines, None, None, None, style)
567
568    def _makepath(self, path):
569        if not self.abspath:
570            try:
571                np = py.path.local().bestrelpath(path)
572            except OSError:
573                return path
574            if len(np) < len(str(path)):
575                path = np
576        return path
577
578    def repr_traceback(self, excinfo):
579        traceback = excinfo.traceback
580        if self.tbfilter:
581            traceback = traceback.filter()
582        recursionindex = None
583        if excinfo.errisinstance(RuntimeError):
584            if "maximum recursion depth exceeded" in str(excinfo.value):
585                recursionindex = traceback.recursionindex()
586        last = traceback[-1]
587        entries = []
588        extraline = None
589        for index, entry in enumerate(traceback):
590            einfo = (last == entry) and excinfo or None
591            reprentry = self.repr_traceback_entry(entry, einfo)
592            entries.append(reprentry)
593            if index == recursionindex:
594                extraline = "!!! Recursion detected (same locals & position)"
595                break
596        return ReprTraceback(entries, extraline, style=self.style)
597
598    def repr_excinfo(self, excinfo):
599        reprtraceback = self.repr_traceback(excinfo)
600        reprcrash = excinfo._getreprcrash()
601        return ReprExceptionInfo(reprtraceback, reprcrash)
602
603class TerminalRepr:
604    def __str__(self):
605        s = self.__unicode__()
606        if sys.version_info[0] < 3:
607            s = s.encode('utf-8')
608        return s
609
610    def __unicode__(self):
611        # FYI this is called from pytest-xdist's serialization of exception
612        # information.
613        io = py.io.TextIO()
614        tw = py.io.TerminalWriter(file=io)
615        self.toterminal(tw)
616        return io.getvalue().strip()
617
618    def __repr__(self):
619        return "<%s instance at %0x>" %(self.__class__, id(self))
620
621
622class ReprExceptionInfo(TerminalRepr):
623    def __init__(self, reprtraceback, reprcrash):
624        self.reprtraceback = reprtraceback
625        self.reprcrash = reprcrash
626        self.sections = []
627
628    def addsection(self, name, content, sep="-"):
629        self.sections.append((name, content, sep))
630
631    def toterminal(self, tw):
632        self.reprtraceback.toterminal(tw)
633        for name, content, sep in self.sections:
634            tw.sep(sep, name)
635            tw.line(content)
636
637class ReprTraceback(TerminalRepr):
638    entrysep = "_ "
639
640    def __init__(self, reprentries, extraline, style):
641        self.reprentries = reprentries
642        self.extraline = extraline
643        self.style = style
644
645    def toterminal(self, tw):
646        # the entries might have different styles
647        last_style = None
648        for i, entry in enumerate(self.reprentries):
649            if entry.style == "long":
650                tw.line("")
651            entry.toterminal(tw)
652            if i < len(self.reprentries) - 1:
653                next_entry = self.reprentries[i+1]
654                if entry.style == "long" or \
655                   entry.style == "short" and next_entry.style == "long":
656                    tw.sep(self.entrysep)
657
658        if self.extraline:
659            tw.line(self.extraline)
660
661class ReprTracebackNative(ReprTraceback):
662    def __init__(self, tblines):
663        self.style = "native"
664        self.reprentries = [ReprEntryNative(tblines)]
665        self.extraline = None
666
667class ReprEntryNative(TerminalRepr):
668    style = "native"
669
670    def __init__(self, tblines):
671        self.lines = tblines
672
673    def toterminal(self, tw):
674        tw.write("".join(self.lines))
675
676class ReprEntry(TerminalRepr):
677    localssep = "_ "
678
679    def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
680        self.lines = lines
681        self.reprfuncargs = reprfuncargs
682        self.reprlocals = reprlocals
683        self.reprfileloc = filelocrepr
684        self.style = style
685
686    def toterminal(self, tw):
687        if self.style == "short":
688            self.reprfileloc.toterminal(tw)
689            for line in self.lines:
690                red = line.startswith("E   ")
691                tw.line(line, bold=True, red=red)
692            #tw.line("")
693            return
694        if self.reprfuncargs:
695            self.reprfuncargs.toterminal(tw)
696        for line in self.lines:
697            red = line.startswith("E   ")
698            tw.line(line, bold=True, red=red)
699        if self.reprlocals:
700            #tw.sep(self.localssep, "Locals")
701            tw.line("")
702            self.reprlocals.toterminal(tw)
703        if self.reprfileloc:
704            if self.lines:
705                tw.line("")
706            self.reprfileloc.toterminal(tw)
707
708    def __str__(self):
709        return "%s\n%s\n%s" % ("\n".join(self.lines),
710                               self.reprlocals,
711                               self.reprfileloc)
712
713class ReprFileLocation(TerminalRepr):
714    def __init__(self, path, lineno, message):
715        self.path = str(path)
716        self.lineno = lineno
717        self.message = message
718
719    def toterminal(self, tw):
720        # filename and lineno output for each entry,
721        # using an output format that most editors unterstand
722        msg = self.message
723        i = msg.find("\n")
724        if i != -1:
725            msg = msg[:i]
726        tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
727
728class ReprLocals(TerminalRepr):
729    def __init__(self, lines):
730        self.lines = lines
731
732    def toterminal(self, tw):
733        for line in self.lines:
734            tw.line(line)
735
736class ReprFuncArgs(TerminalRepr):
737    def __init__(self, args):
738        self.args = args
739
740    def toterminal(self, tw):
741        if self.args:
742            linesofar = ""
743            for name, value in self.args:
744                ns = "%s = %s" %(name, value)
745                if len(ns) + len(linesofar) + 2 > tw.fullwidth:
746                    if linesofar:
747                        tw.line(linesofar)
748                    linesofar =  ns
749                else:
750                    if linesofar:
751                        linesofar += ", " + ns
752                    else:
753                        linesofar = ns
754            if linesofar:
755                tw.line(linesofar)
756            tw.line("")
757
758
759
760oldbuiltins = {}
761
762def patch_builtins(assertion=True, compile=True):
763    """ put compile and AssertionError builtins to Python's builtins. """
764    if assertion:
765        from py._code import assertion
766        l = oldbuiltins.setdefault('AssertionError', [])
767        l.append(py.builtin.builtins.AssertionError)
768        py.builtin.builtins.AssertionError = assertion.AssertionError
769    if compile:
770        l = oldbuiltins.setdefault('compile', [])
771        l.append(py.builtin.builtins.compile)
772        py.builtin.builtins.compile = py.code.compile
773
774def unpatch_builtins(assertion=True, compile=True):
775    """ remove compile and AssertionError builtins from Python builtins. """
776    if assertion:
777        py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
778    if compile:
779        py.builtin.builtins.compile = oldbuiltins['compile'].pop()
780
781def getrawcode(obj, trycall=True):
782    """ return code object for given function. """
783    try:
784        return obj.__code__
785    except AttributeError:
786        obj = getattr(obj, 'im_func', obj)
787        obj = getattr(obj, 'func_code', obj)
788        obj = getattr(obj, 'f_code', obj)
789        obj = getattr(obj, '__code__', obj)
790        if trycall and not hasattr(obj, 'co_firstlineno'):
791            if hasattr(obj, '__call__') and not isclass(obj):
792                x = getrawcode(obj.__call__, trycall=False)
793                if hasattr(x, 'co_firstlineno'):
794                    return x
795        return obj
796
797