1# -*- coding: utf-8 -*-
2"""Tools for inspecting Python objects.
3
4This file was forked from the IPython project:
5
6* Copyright (c) 2008-2014, IPython Development Team
7* Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
8* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
9* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
10"""
11import os
12import io
13import sys
14import types
15import inspect
16import itertools
17import linecache
18import collections
19
20from xonsh.lazyasd import LazyObject
21from xonsh.tokenize import detect_encoding
22from xonsh.openpy import read_py_file
23from xonsh.tools import cast_unicode, safe_hasattr, indent, print_color, format_color
24from xonsh.platform import HAS_PYGMENTS, PYTHON_VERSION_INFO
25from xonsh.lazyimps import pygments, pyghooks
26from xonsh.style_tools import partial_color_tokenize
27
28
29# builtin docstrings to ignore
30_func_call_docstring = LazyObject(
31    lambda: types.FunctionType.__call__.__doc__, globals(), "_func_call_docstring"
32)
33_object_init_docstring = LazyObject(
34    lambda: object.__init__.__doc__, globals(), "_object_init_docstring"
35)
36_builtin_type_docstrings = LazyObject(
37    lambda: {
38        t.__doc__ for t in (types.ModuleType, types.MethodType, types.FunctionType)
39    },
40    globals(),
41    "_builtin_type_docstrings",
42)
43
44_builtin_func_type = LazyObject(lambda: type(all), globals(), "_builtin_func_type")
45# Bound methods have the same type as builtin functions
46_builtin_meth_type = LazyObject(
47    lambda: type(str.upper), globals(), "_builtin_meth_type"
48)
49
50info_fields = LazyObject(
51    lambda: [
52        "type_name",
53        "base_class",
54        "string_form",
55        "namespace",
56        "length",
57        "file",
58        "definition",
59        "docstring",
60        "source",
61        "init_definition",
62        "class_docstring",
63        "init_docstring",
64        "call_def",
65        "call_docstring",
66        # These won't be printed but will be used to determine how to
67        # format the object
68        "ismagic",
69        "isalias",
70        "isclass",
71        "argspec",
72        "found",
73        "name",
74    ],
75    globals(),
76    "info_fields",
77)
78
79
80def object_info(**kw):
81    """Make an object info dict with all fields present."""
82    infodict = dict(itertools.zip_longest(info_fields, [None]))
83    infodict.update(kw)
84    return infodict
85
86
87def get_encoding(obj):
88    """Get encoding for python source file defining obj
89
90    Returns None if obj is not defined in a sourcefile.
91    """
92    ofile = find_file(obj)
93    # run contents of file through pager starting at line where the object
94    # is defined, as long as the file isn't binary and is actually on the
95    # filesystem.
96    if ofile is None:
97        return None
98    elif ofile.endswith((".so", ".dll", ".pyd")):
99        return None
100    elif not os.path.isfile(ofile):
101        return None
102    else:
103        # Print only text files, not extension binaries.  Note that
104        # getsourcelines returns lineno with 1-offset and page() uses
105        # 0-offset, so we must adjust.
106        with io.open(ofile, "rb") as buf:  # Tweaked to use io.open for Python 2
107            encoding, _ = detect_encoding(buf.readline)
108        return encoding
109
110
111def getdoc(obj):
112    """Stable wrapper around inspect.getdoc.
113
114    This can't crash because of attribute problems.
115
116    It also attempts to call a getdoc() method on the given object.  This
117    allows objects which provide their docstrings via non-standard mechanisms
118    (like Pyro proxies) to still be inspected by ipython's ? system."""
119    # Allow objects to offer customized documentation via a getdoc method:
120    try:
121        ds = obj.getdoc()
122    except Exception:  # pylint:disable=broad-except
123        pass
124    else:
125        # if we get extra info, we add it to the normal docstring.
126        if isinstance(ds, str):
127            return inspect.cleandoc(ds)
128
129    try:
130        docstr = inspect.getdoc(obj)
131        encoding = get_encoding(obj)
132        return cast_unicode(docstr, encoding=encoding)
133    except Exception:  # pylint:disable=broad-except
134        # Harden against an inspect failure, which can occur with
135        # SWIG-wrapped extensions.
136        raise
137
138
139def getsource(obj, is_binary=False):
140    """Wrapper around inspect.getsource.
141
142    This can be modified by other projects to provide customized source
143    extraction.
144
145    Inputs:
146
147    - obj: an object whose source code we will attempt to extract.
148
149    Optional inputs:
150
151    - is_binary: whether the object is known to come from a binary source.
152      This implementation will skip returning any output for binary objects,
153      but custom extractors may know how to meaningfully process them."""
154
155    if is_binary:
156        return None
157    else:
158        # get source if obj was decorated with @decorator
159        if hasattr(obj, "__wrapped__"):
160            obj = obj.__wrapped__
161        try:
162            src = inspect.getsource(obj)
163        except TypeError:
164            if hasattr(obj, "__class__"):
165                src = inspect.getsource(obj.__class__)
166        encoding = get_encoding(obj)
167        return cast_unicode(src, encoding=encoding)
168
169
170def is_simple_callable(obj):
171    """True if obj is a function ()"""
172    return (
173        inspect.isfunction(obj)
174        or inspect.ismethod(obj)
175        or isinstance(obj, _builtin_func_type)
176        or isinstance(obj, _builtin_meth_type)
177    )
178
179
180def getargspec(obj):
181    """Wrapper around :func:`inspect.getfullargspec` on Python 3, and
182    :func:inspect.getargspec` on Python 2.
183
184    In addition to functions and methods, this can also handle objects with a
185    ``__call__`` attribute.
186    """
187    if safe_hasattr(obj, "__call__") and not is_simple_callable(obj):
188        obj = obj.__call__
189
190    return inspect.getfullargspec(obj)
191
192
193def format_argspec(argspec):
194    """Format argspect, convenience wrapper around inspect's.
195
196    This takes a dict instead of ordered arguments and calls
197    inspect.format_argspec with the arguments in the necessary order.
198    """
199    return inspect.formatargspec(
200        argspec["args"], argspec["varargs"], argspec["varkw"], argspec["defaults"]
201    )
202
203
204def call_tip(oinfo, format_call=True):
205    """Extract call tip data from an oinfo dict.
206
207    Parameters
208    ----------
209    oinfo : dict
210
211    format_call : bool, optional
212      If True, the call line is formatted and returned as a string.  If not, a
213      tuple of (name, argspec) is returned.
214
215    Returns
216    -------
217    call_info : None, str or (str, dict) tuple.
218      When format_call is True, the whole call information is formatted as a
219      single string.  Otherwise, the object's name and its argspec dict are
220      returned.  If no call information is available, None is returned.
221
222    docstring : str or None
223      The most relevant docstring for calling purposes is returned, if
224      available.  The priority is: call docstring for callable instances, then
225      constructor docstring for classes, then main object's docstring otherwise
226      (regular functions).
227    """
228    # Get call definition
229    argspec = oinfo.get("argspec")
230    if argspec is None:
231        call_line = None
232    else:
233        # Callable objects will have 'self' as their first argument, prune
234        # it out if it's there for clarity (since users do *not* pass an
235        # extra first argument explicitly).
236        try:
237            has_self = argspec["args"][0] == "self"
238        except (KeyError, IndexError):
239            pass
240        else:
241            if has_self:
242                argspec["args"] = argspec["args"][1:]
243
244        call_line = oinfo["name"] + format_argspec(argspec)
245
246    # Now get docstring.
247    # The priority is: call docstring, constructor docstring, main one.
248    doc = oinfo.get("call_docstring")
249    if doc is None:
250        doc = oinfo.get("init_docstring")
251    if doc is None:
252        doc = oinfo.get("docstring", "")
253
254    return call_line, doc
255
256
257def find_file(obj):
258    """Find the absolute path to the file where an object was defined.
259
260    This is essentially a robust wrapper around `inspect.getabsfile`.
261
262    Returns None if no file can be found.
263
264    Parameters
265    ----------
266    obj : any Python object
267
268    Returns
269    -------
270    fname : str
271      The absolute path to the file where the object was defined.
272    """
273    # get source if obj was decorated with @decorator
274    if safe_hasattr(obj, "__wrapped__"):
275        obj = obj.__wrapped__
276
277    fname = None
278    try:
279        fname = inspect.getabsfile(obj)
280    except TypeError:
281        # For an instance, the file that matters is where its class was
282        # declared.
283        if hasattr(obj, "__class__"):
284            try:
285                fname = inspect.getabsfile(obj.__class__)
286            except TypeError:
287                # Can happen for builtins
288                pass
289    except:  # pylint:disable=bare-except
290        pass
291    return cast_unicode(fname)
292
293
294def find_source_lines(obj):
295    """Find the line number in a file where an object was defined.
296
297    This is essentially a robust wrapper around `inspect.getsourcelines`.
298
299    Returns None if no file can be found.
300
301    Parameters
302    ----------
303    obj : any Python object
304
305    Returns
306    -------
307    lineno : int
308      The line number where the object definition starts.
309    """
310    # get source if obj was decorated with @decorator
311    if safe_hasattr(obj, "__wrapped__"):
312        obj = obj.__wrapped__
313
314    try:
315        try:
316            lineno = inspect.getsourcelines(obj)[1]
317        except TypeError:
318            # For instances, try the class object like getsource() does
319            if hasattr(obj, "__class__"):
320                lineno = inspect.getsourcelines(obj.__class__)[1]
321            else:
322                lineno = None
323    except:  # pylint:disable=bare-except
324        return None
325
326    return lineno
327
328
329if PYTHON_VERSION_INFO < (3, 5, 0):
330    FrameInfo = collections.namedtuple(
331        "FrameInfo",
332        ["frame", "filename", "lineno", "function", "code_context", "index"],
333    )
334
335    def getouterframes(frame, context=1):
336        """Wrapper for getouterframes so that it acts like the Python v3.5 version."""
337        return [FrameInfo(*f) for f in inspect.getouterframes(frame, context=context)]
338
339
340else:
341    getouterframes = inspect.getouterframes
342
343
344class Inspector(object):
345    """Inspects objects."""
346
347    def __init__(self, str_detail_level=0):
348        self.str_detail_level = str_detail_level
349
350    def _getdef(self, obj, oname=""):
351        """Return the call signature for any callable object.
352
353        If any exception is generated, None is returned instead and the
354        exception is suppressed.
355        """
356        try:
357            hdef = oname + inspect.formatargspec(*getargspec(obj))
358            return cast_unicode(hdef)
359        except:  # pylint:disable=bare-except
360            return None
361
362    def noinfo(self, msg, oname):
363        """Generic message when no information is found."""
364        print("No %s found" % msg, end=" ")
365        if oname:
366            print("for %s" % oname)
367        else:
368            print()
369
370    def pdef(self, obj, oname=""):
371        """Print the call signature for any callable object.
372
373        If the object is a class, print the constructor information.
374        """
375
376        if not callable(obj):
377            print("Object is not callable.")
378            return
379
380        header = ""
381
382        if inspect.isclass(obj):
383            header = self.__head("Class constructor information:\n")
384            obj = obj.__init__
385
386        output = self._getdef(obj, oname)
387        if output is None:
388            self.noinfo("definition header", oname)
389        else:
390            print(header, output, end=" ", file=sys.stdout)
391
392    def pdoc(self, obj, oname=""):
393        """Print the docstring for any object.
394
395        Optional
396
397        -formatter: a function to run the docstring through for specially
398        formatted docstrings.
399        """
400
401        head = self.__head  # For convenience
402        lines = []
403        ds = getdoc(obj)
404        if ds:
405            lines.append(head("Class docstring:"))
406            lines.append(indent(ds))
407        if inspect.isclass(obj) and hasattr(obj, "__init__"):
408            init_ds = getdoc(obj.__init__)
409            if init_ds is not None:
410                lines.append(head("Init docstring:"))
411                lines.append(indent(init_ds))
412        elif hasattr(obj, "__call__"):
413            call_ds = getdoc(obj.__call__)
414            if call_ds:
415                lines.append(head("Call docstring:"))
416                lines.append(indent(call_ds))
417
418        if not lines:
419            self.noinfo("documentation", oname)
420        else:
421            print("\n".join(lines))
422
423    def psource(self, obj, oname=""):
424        """Print the source code for an object."""
425        # Flush the source cache because inspect can return out-of-date source
426        linecache.checkcache()
427        try:
428            src = getsource(obj)
429        except:  # pylint:disable=bare-except
430            self.noinfo("source", oname)
431        else:
432            print(src)
433
434    def pfile(self, obj, oname=""):
435        """Show the whole file where an object was defined."""
436        lineno = find_source_lines(obj)
437        if lineno is None:
438            self.noinfo("file", oname)
439            return
440
441        ofile = find_file(obj)
442        # run contents of file through pager starting at line where the object
443        # is defined, as long as the file isn't binary and is actually on the
444        # filesystem.
445        if ofile.endswith((".so", ".dll", ".pyd")):
446            print("File %r is binary, not printing." % ofile)
447        elif not os.path.isfile(ofile):
448            print("File %r does not exist, not printing." % ofile)
449        else:
450            # Print only text files, not extension binaries.  Note that
451            # getsourcelines returns lineno with 1-offset and page() uses
452            # 0-offset, so we must adjust.
453            o = read_py_file(ofile, skip_encoding_cookie=False)
454            print(o, lineno - 1)
455
456    def _format_fields_str(self, fields, title_width=0):
457        """Formats a list of fields for display using color strings.
458
459        Parameters
460        ----------
461        fields : list
462          A list of 2-tuples: (field_title, field_content)
463        title_width : int
464          How many characters to pad titles to. Default to longest title.
465        """
466        out = []
467        if title_width == 0:
468            title_width = max(len(title) + 2 for title, _ in fields)
469        for title, content in fields:
470            title_len = len(title)
471            title = "{BOLD_RED}" + title + ":{NO_COLOR}"
472            if len(content.splitlines()) > 1:
473                title += "\n"
474            else:
475                title += " ".ljust(title_width - title_len)
476            out.append(cast_unicode(title) + cast_unicode(content))
477        return format_color("\n".join(out) + "\n")
478
479    def _format_fields_tokens(self, fields, title_width=0):
480        """Formats a list of fields for display using color tokens from
481        pygments.
482
483        Parameters
484        ----------
485        fields : list
486          A list of 2-tuples: (field_title, field_content)
487        title_width : int
488          How many characters to pad titles to. Default to longest title.
489        """
490        out = []
491        if title_width == 0:
492            title_width = max(len(title) + 2 for title, _ in fields)
493        for title, content in fields:
494            title_len = len(title)
495            title = "{BOLD_RED}" + title + ":{NO_COLOR}"
496            if not isinstance(content, str) or len(content.splitlines()) > 1:
497                title += "\n"
498            else:
499                title += " ".ljust(title_width - title_len)
500            out += partial_color_tokenize(title)
501            if isinstance(content, str):
502                out[-1] = (out[-1][0], out[-1][1] + content + "\n")
503            else:
504                out += content
505                out[-1] = (out[-1][0], out[-1][1] + "\n")
506        out[-1] = (out[-1][0], out[-1][1] + "\n")
507        return out
508
509    def _format_fields(self, fields, title_width=0):
510        """Formats a list of fields for display using color tokens from
511        pygments.
512
513        Parameters
514        ----------
515        fields : list
516          A list of 2-tuples: (field_title, field_content)
517        title_width : int
518          How many characters to pad titles to. Default to longest title.
519        """
520        if HAS_PYGMENTS:
521            rtn = self._format_fields_tokens(fields, title_width=title_width)
522        else:
523            rtn = self._format_fields_str(fields, title_width=title_width)
524        return rtn
525
526    # The fields to be displayed by pinfo: (fancy_name, key_in_info_dict)
527    pinfo_fields1 = [("Type", "type_name")]
528
529    pinfo_fields2 = [("String form", "string_form")]
530
531    pinfo_fields3 = [
532        ("Length", "length"),
533        ("File", "file"),
534        ("Definition", "definition"),
535    ]
536
537    pinfo_fields_obj = [
538        ("Class docstring", "class_docstring"),
539        ("Init docstring", "init_docstring"),
540        ("Call def", "call_def"),
541        ("Call docstring", "call_docstring"),
542    ]
543
544    def pinfo(self, obj, oname="", info=None, detail_level=0):
545        """Show detailed information about an object.
546
547        Parameters
548        ----------
549        obj : object
550        oname : str, optional
551            name of the variable pointing to the object.
552        info : dict, optional
553            a structure with some information fields which may have been
554            precomputed already.
555        detail_level : int, optional
556            if set to 1, more information is given.
557        """
558        info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
559        displayfields = []
560
561        def add_fields(fields):
562            for title, key in fields:
563                field = info[key]
564                if field is not None:
565                    displayfields.append((title, field.rstrip()))
566
567        add_fields(self.pinfo_fields1)
568        add_fields(self.pinfo_fields2)
569
570        # Namespace
571        if info["namespace"] is not None and info["namespace"] != "Interactive":
572            displayfields.append(("Namespace", info["namespace"].rstrip()))
573
574        add_fields(self.pinfo_fields3)
575        if info["isclass"] and info["init_definition"]:
576            displayfields.append(("Init definition", info["init_definition"].rstrip()))
577
578        # Source or docstring, depending on detail level and whether
579        # source found.
580        if detail_level > 0 and info["source"] is not None:
581            displayfields.append(("Source", cast_unicode(info["source"])))
582        elif info["docstring"] is not None:
583            displayfields.append(("Docstring", info["docstring"]))
584
585        # Constructor info for classes
586        if info["isclass"]:
587            if info["init_docstring"] is not None:
588                displayfields.append(("Init docstring", info["init_docstring"]))
589
590        # Info for objects:
591        else:
592            add_fields(self.pinfo_fields_obj)
593
594        # Finally send to printer/pager:
595        if displayfields:
596            print_color(self._format_fields(displayfields))
597
598    def info(self, obj, oname="", info=None, detail_level=0):
599        """Compute a dict with detailed information about an object.
600
601        Optional arguments:
602
603        - oname: name of the variable pointing to the object.
604
605        - info: a structure with some information fields which may have been
606          precomputed already.
607
608        - detail_level: if set to 1, more information is given.
609        """
610        obj_type = type(obj)
611        if info is None:
612            ismagic = 0
613            isalias = 0
614            ospace = ""
615        else:
616            ismagic = info.ismagic
617            isalias = info.isalias
618            ospace = info.namespace
619        # Get docstring, special-casing aliases:
620        if isalias:
621            if not callable(obj):
622                if len(obj) >= 2 and isinstance(obj[1], str):
623                    ds = "Alias to the system command:\n  {0}".format(obj[1])
624                else:  # pylint:disable=bare-except
625                    ds = "Alias: " + str(obj)
626            else:
627                ds = "Alias to " + str(obj)
628                if obj.__doc__:
629                    ds += "\nDocstring:\n" + obj.__doc__
630        else:
631            ds = getdoc(obj)
632            if ds is None:
633                ds = "<no docstring>"
634
635        # store output in a dict, we initialize it here and fill it as we go
636        out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic)
637
638        string_max = 200  # max size of strings to show (snipped if longer)
639        shalf = int((string_max - 5) / 2)
640
641        if ismagic:
642            obj_type_name = "Magic function"
643        elif isalias:
644            obj_type_name = "System alias"
645        else:
646            obj_type_name = obj_type.__name__
647        out["type_name"] = obj_type_name
648
649        try:
650            bclass = obj.__class__
651            out["base_class"] = str(bclass)
652        except:  # pylint:disable=bare-except
653            pass
654
655        # String form, but snip if too long in ? form (full in ??)
656        if detail_level >= self.str_detail_level:
657            try:
658                ostr = str(obj)
659                str_head = "string_form"
660                if not detail_level and len(ostr) > string_max:
661                    ostr = ostr[:shalf] + " <...> " + ostr[-shalf:]
662                    ostr = ("\n" + " " * len(str_head.expandtabs())).join(
663                        q.strip() for q in ostr.split("\n")
664                    )
665                out[str_head] = ostr
666            except:  # pylint:disable=bare-except
667                pass
668
669        if ospace:
670            out["namespace"] = ospace
671
672        # Length (for strings and lists)
673        try:
674            out["length"] = str(len(obj))
675        except:  # pylint:disable=bare-except
676            pass
677
678        # Filename where object was defined
679        binary_file = False
680        fname = find_file(obj)
681        if fname is None:
682            # if anything goes wrong, we don't want to show source, so it's as
683            # if the file was binary
684            binary_file = True
685        else:
686            if fname.endswith((".so", ".dll", ".pyd")):
687                binary_file = True
688            elif fname.endswith("<string>"):
689                fname = "Dynamically generated function. " "No source code available."
690            out["file"] = fname
691
692        # Docstrings only in detail 0 mode, since source contains them (we
693        # avoid repetitions).  If source fails, we add them back, see below.
694        if ds and detail_level == 0:
695            out["docstring"] = ds
696
697        # Original source code for any callable
698        if detail_level:
699            # Flush the source cache because inspect can return out-of-date
700            # source
701            linecache.checkcache()
702            source = None
703            try:
704                try:
705                    source = getsource(obj, binary_file)
706                except TypeError:
707                    if hasattr(obj, "__class__"):
708                        source = getsource(obj.__class__, binary_file)
709                if source is not None:
710                    source = source.rstrip()
711                    if HAS_PYGMENTS:
712                        lexer = pyghooks.XonshLexer()
713                        source = list(pygments.lex(source, lexer=lexer))
714                    out["source"] = source
715            except Exception:  # pylint:disable=broad-except
716                pass
717
718            if ds and source is None:
719                out["docstring"] = ds
720
721        # Constructor docstring for classes
722        if inspect.isclass(obj):
723            out["isclass"] = True
724            # reconstruct the function definition and print it:
725            try:
726                obj_init = obj.__init__
727            except AttributeError:
728                init_def = init_ds = None
729            else:
730                init_def = self._getdef(obj_init, oname)
731                init_ds = getdoc(obj_init)
732                # Skip Python's auto-generated docstrings
733                if init_ds == _object_init_docstring:
734                    init_ds = None
735
736            if init_def or init_ds:
737                if init_def:
738                    out["init_definition"] = init_def
739                if init_ds:
740                    out["init_docstring"] = init_ds
741
742        # and class docstring for instances:
743        else:
744            # reconstruct the function definition and print it:
745            defln = self._getdef(obj, oname)
746            if defln:
747                out["definition"] = defln
748
749            # First, check whether the instance docstring is identical to the
750            # class one, and print it separately if they don't coincide.  In
751            # most cases they will, but it's nice to print all the info for
752            # objects which use instance-customized docstrings.
753            if ds:
754                try:
755                    cls = getattr(obj, "__class__")
756                except:  # pylint:disable=bare-except
757                    class_ds = None
758                else:
759                    class_ds = getdoc(cls)
760                # Skip Python's auto-generated docstrings
761                if class_ds in _builtin_type_docstrings:
762                    class_ds = None
763                if class_ds and ds != class_ds:
764                    out["class_docstring"] = class_ds
765
766            # Next, try to show constructor docstrings
767            try:
768                init_ds = getdoc(obj.__init__)
769                # Skip Python's auto-generated docstrings
770                if init_ds == _object_init_docstring:
771                    init_ds = None
772            except AttributeError:
773                init_ds = None
774            if init_ds:
775                out["init_docstring"] = init_ds
776
777            # Call form docstring for callable instances
778            if safe_hasattr(obj, "__call__") and not is_simple_callable(obj):
779                call_def = self._getdef(obj.__call__, oname)
780                if call_def:
781                    call_def = call_def
782                    # it may never be the case that call def and definition
783                    # differ, but don't include the same signature twice
784                    if call_def != out.get("definition"):
785                        out["call_def"] = call_def
786                call_ds = getdoc(obj.__call__)
787                # Skip Python's auto-generated docstrings
788                if call_ds == _func_call_docstring:
789                    call_ds = None
790                if call_ds:
791                    out["call_docstring"] = call_ds
792
793        # Compute the object's argspec as a callable.  The key is to decide
794        # whether to pull it from the object itself, from its __init__ or
795        # from its __call__ method.
796
797        if inspect.isclass(obj):
798            # Old-style classes need not have an __init__
799            callable_obj = getattr(obj, "__init__", None)
800        elif callable(obj):
801            callable_obj = obj
802        else:
803            callable_obj = None
804
805        if callable_obj:
806            try:
807                argspec = getargspec(callable_obj)
808            except (TypeError, AttributeError):
809                # For extensions/builtins we can't retrieve the argspec
810                pass
811            else:
812                # named tuples' _asdict() method returns an OrderedDict, but we
813                # we want a normal
814                out["argspec"] = argspec_dict = dict(argspec._asdict())
815                # We called this varkw before argspec became a named tuple.
816                # With getfullargspec it's also called varkw.
817                if "varkw" not in argspec_dict:
818                    argspec_dict["varkw"] = argspec_dict.pop("keywords")
819
820        return object_info(**out)
821