def _get_source_definition(obj): """Get a source representation of the function definition.""" try: obj = _unwrap(obj) if dir2.safe_hasattr( obj, '__call__') and not oinspect.is_simple_callable(obj): obj = obj.__call__ lines, lnum = inspect.findsource(obj) block = inspect.getblock(lines[lnum:]) # Trim leading whitespace for all lines. prefix = re.match('^([ \t]*)', block[0]).group() trimmed = [] for line in block: if line.startswith(prefix): line = line[len(prefix):] trimmed.append(line) # Override the default join to avoid wrapping. def join_lines(source): return ''.join(source) module = ast.parse('\n'.join(trimmed), mode='exec') function = module.body[0] # Remove 'self' if it's the first arg. if function.args.args and function.args.args[0].arg == 'self': function.args.args.pop(0) function.body = [] decl = astor.to_source(function, indent_with='', pretty_source=join_lines).strip() # Strip the trailing `:` if decl.endswith(':'): decl = decl[:-1] return decl except Exception: # pylint: disable=broad-except return None
def info(self, obj, oname='', formatter=None, info=None, detail_level=0): """Compute a dict with detailed information about an object. This overrides the superclass method for two main purposes: * avoid calling str() or repr() on the object * use our custom repr Args: obj: object to inspect. oname: (optional) string reference to this object formatter: (optional) custom docstring formatter info: (optional) previously computed information about obj detail_level: (optional) 0 or 1; 1 means "include more detail" Returns: A dict with information about obj. """ # We want to include the following list of keys for all objects: # * name # * found # * isclass # * string_form # * type_name # # For callables, we want to add a subset of: # * argspec # * call_docstring # * definition # * docstring # * file # * init_definition # * init_docstring # * source_end_line # * source_start_line # * source_definition # # For detail_level 1, we include: # * file # This can be expensive, as the stdlib mechanisms for looking up the file # containing obj may call repr(obj). # # NOTE: These keys should stay in sync with the corresponding list in our # frontend code. # # We want non-None values for: # * isalias # * ismagic # * namespace # # TODO(b/138128444): Handle class_docstring and call_def, or determine that # we're safe ignoring them. obj_type = type(obj) out = { 'name': oname, 'found': True, 'is_class': inspect.isclass(obj), 'string_form': None, # Fill in empty values. 'docstring': None, 'file': None, 'isalias': False, 'ismagic': info.ismagic if info else False, 'namespace': info.namespace if info else '', } if detail_level >= self.str_detail_level: out['string_form'] = _safe_repr(obj) if getattr(info, 'ismagic', False): out['type_name'] = 'Magic function' else: out['type_name'] = obj_type.__name__ # If the object is callable, we want to compute a docstring and related # information. We could exit early, but all the code below is in conditional # blocks, so there's no need. # # We can't simply call into the superclass method, as we need to avoid # (transitively) calling inspect.getfile(): this function will end up # calling repr() on our object. # We want to include a docstring if we don't have source, which happens # when: # * detail_level == 0, or # * detail_level == 1 but we can't find source # So we first try dealing with detail_level == 1, and then set # the docstring if no source is set. if detail_level == 1: # This should only ever happen if the user has asked for source (eg via # `obj??`), so we're OK with potentially calling repr for now. # TODO(b/138128444): Ensure we don't call str() or repr(). source = _getsource(obj) if source is None and hasattr(obj, '__class__'): source = _getsource(obj.__class__) if source is not None: out['source'] = source if 'source' not in out: formatter = formatter or (lambda x: x) docstring = formatter(_getdoc(obj) or '<no docstring>') if docstring: out['docstring'] = docstring if _iscallable(obj): filename = oinspect.find_file(obj) if filename and (filename.endswith( ('.py', '.py3', '.pyc')) or '<ipython-input' in filename): out['file'] = filename line = oinspect.find_source_lines(obj) out['source_start_line'] = line # inspect.getsourcelines exposes the length of the source as well, which # can be used to highlight the entire code block, but find_source_lines # currently does not expose this. For now just highlight the first line. out['source_end_line'] = line # The remaining attributes only apply to classes or callables. if inspect.isclass(obj): # For classes with an __init__, we set init_definition and init_docstring. init = getattr(obj, '__init__', None) if init: init_docstring = _getdoc(init) if init_docstring and init_docstring != _BASE_INIT_DOC: out['init_docstring'] = init_docstring init_def = self._getdef(init, oname) if init_def: out['init_definition'] = init_def src_def = _get_source_definition(init) if src_def: out['source_definition'] = src_def # For classes, the __init__ method is the method invoked on call, but # old-style classes may not have an __init__ method. if init: argspec = _getargspec_dict(init) if argspec: out['argspec'] = argspec elif callable(obj): definition = self._getdef(obj, oname) if definition: out['definition'] = definition src_def = _get_source_definition(obj) if src_def: out['source_definition'] = src_def if not oinspect.is_simple_callable(obj): call_docstring = _getdoc(obj.__call__) if call_docstring and call_docstring != _BASE_CALL_DOC: out['call_docstring'] = call_docstring out['argspec'] = _getargspec_dict(obj) return oinspect.object_info(**out)