Source code for grub.pycode

"""Searching python code"""

import re
from grub.base import (
    SearchStore,
    CodeSearcher,
    get_py_files_store,
    camelcase_and_underscore_tokenizer,
)


# TODO: Not finished: Continue
[docs]class PyCodeSearcherBase(CodeSearcher): def __post_init__(self): self.search_store = pyobj_semantics_dict(self.search_store)
doctest_line_p = re.compile('\s*>>>') empty_line = re.compile('\s*$')
[docs]def non_doctest_lines(doc): r"""Generator of lines of the doc string that are not in a doctest scope. >>> def _test_func(): ... '''Line 1 ... Another ... >>> doctest_1 ... >>> doctest_2 ... line_after_a_doc_test ... another_line_that_is_in_the_doc_test scope ... ... But now we're out of a doctest's scope ... ... >>> Oh no, another doctest! ... ''' >>> from inspect import getdoc >>> >>> list(non_doctest_lines(getdoc(_test_func))) ['Line 1', 'Another', "But now we're out of a doctest's scope", ''] :param doc: :return: """ last_line_was_a_doc_test = False for line in doc.splitlines(): if not doctest_line_p.match(line): if not last_line_was_a_doc_test: yield line last_line_was_a_doc_test = False else: if empty_line.match(line): last_line_was_a_doc_test = False else: last_line_was_a_doc_test = True
def call_and_return_none_on_exception(func, *args, **kwargs): try: return func(*args, **kwargs) except Exception: return None import inspect from doctest import DocTestFinder from inspect import signature, getfile, getcomments, getsource, getsourcelines, getdoc doctest_finder = DocTestFinder(recurse=False) def argnames(func): return ' '.join(inspect.signature(func).parameters) def tokenize_for_code(string): camelcase_and_underscore_tokenizer(string) _func_info_funcs = { 'func_name': lambda f: f.__qualname__, 'arg_names': argnames, 'comments': getcomments, 'doc': lambda f: '\n'.join(non_doctest_lines(getdoc(f))), }
[docs]def func_key_info(func) -> dict: """Information that points to the function's source""" func_src, lineno = getsourcelines(func) return getfile(func), lineno
[docs]def func_semantic_info(func) -> dict: """A dict of semantically relevant infos about a function.""" def gen(): for name, info_func in _func_info_funcs.items(): info = call_and_return_none_on_exception(info_func, func) if info is not None: yield name, info return dict(gen())
def import_module_from_filepath(filepath): import importlib.util spec = importlib.util.spec_from_file_location('module.name', filepath) m = importlib.util.module_from_spec(spec) spec.loader.exec_module(m) return m def objs_from_store(store): for k in store: f = store._prefix + k # TODO: Revise. Fragile m = import_module_from_filepath(f) for a in dir(m): if not a.startswith('__'): yield getattr(m, a) def gather_objs(objs): cumul = set() for obj in objs: try: cumul.add(obj) except TypeError: pass return cumul def pyobj_semantics_dict(src): module_files_store = get_py_files_store(src) objs = gather_objs(objs_from_store(module_files_store)) def gen(): for obj in objs: try: yield func_key_info(obj), '\n'.join(func_semantic_info(obj).values()) except Exception: pass return dict(gen())
[docs]def ddir(obj): """ List of (dir(obj)) attributes of obj that don't start with an underscore :param obj: Any python object :return: A list of attribute names """ return [a for a in dir(obj) if not a.startswith('_')]
[docs]def search_documented_attributes(obj, obj_to_attrs=ddir, max_results=10): """ Search the documented attributes of a python object :param obj: Any python object :param obj_to_attrs: The function that gives you attribute names of an object. :return: A SearchStore instance to search attributes via their docs >>> from inspect import getmodule >>> containing_module = getmodule(search_documented_attributes) # the module of this function >>> search_module = search_documented_attributes(containing_module, max_results=3) >>> list(search_module('documented attributes')) # if you get an error here, it's probably just be that the docs changed ['search_documented_attributes', 'ddir', 'inspect'] >>> list(search_module('documented objects')) # if you get an error here, it's probably just be that the docs changed ['search_documented_attributes', 'DocTestFinder', 'doctest_finder'] """ def documented_attrs(): for attr_name in obj_to_attrs(obj): attr_val = getattr(obj, attr_name) doc = getattr(attr_val, '__doc__', None) if isinstance(doc, str): yield attr_name, doc attrs_store = dict(documented_attrs()) max_results = min(max_results, len(attrs_store)) return SearchStore(attrs_store, max_results)