""" Docstrings are another source of information for functions and classes. :mod:`dynamic` tries to find all executions of functions, while the docstring parsing is much easier. There are two different types of docstrings that |jedi| understands: - `Sphinx `_ - `Epydoc `_ For example, the sphinx annotation ``:type foo: str`` clearly states that the type of ``foo`` is ``str``. As an addition to parameter searching, this module also provides return annotations. """ import re from jedi import cache from jedi.parser import Parser from jedi import evaluate from jedi.evaluate import representation as er DOCSTRING_PARAM_PATTERNS = [ r'\s*:type\s+%s:\s*([^\n]+)', # Sphinx r'\s*@type\s+%s:\s*([^\n]+)', # Epydoc ] DOCSTRING_RETURN_PATTERNS = [ re.compile(r'\s*:rtype:\s*([^\n]+)', re.M), # Sphinx re.compile(r'\s*@rtype:\s*([^\n]+)', re.M), # Epydoc ] REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`') @cache.memoize_default() def follow_param(param): func = param.parent_function # print func, param, param.parent_function param_str = _search_param_in_docstr(func.docstr, str(param.get_name())) user_position = (1, 0) if param_str is not None: # Try to import module part in dotted name. # (e.g., 'threading' in 'threading.Thread'). if '.' in param_str: param_str = 'import %s\n%s' % ( param_str.rsplit('.', 1)[0], param_str) user_position = (2, 0) p = Parser(param_str, None, user_position, no_docstr=True) if p.user_stmt is None: return [] return evaluate.follow_statement(p.user_stmt) return [] def _search_param_in_docstr(docstr, param_str): """ Search `docstr` for a type of `param_str`. >>> _search_param_in_docstr(':type param: int', 'param') 'int' >>> _search_param_in_docstr('@type param: int', 'param') 'int' >>> _search_param_in_docstr( ... ':type param: :class:`threading.Thread`', 'param') 'threading.Thread' >>> _search_param_in_docstr('no document', 'param') is None True """ # look at #40 to see definitions of those params patterns = [re.compile(p % re.escape(param_str)) for p in DOCSTRING_PARAM_PATTERNS] for pattern in patterns: match = pattern.search(docstr) if match: return _strip_rest_role(match.group(1)) return None def _strip_rest_role(type_str): """ Strip off the part looks like a ReST role in `type_str`. >>> _strip_rest_role(':class:`ClassName`') # strip off :class: 'ClassName' >>> _strip_rest_role(':py:obj:`module.Object`') # works with domain 'module.Object' >>> _strip_rest_role('ClassName') # do nothing when not ReST role 'ClassName' See also: http://sphinx-doc.org/domains.html#cross-referencing-python-objects """ match = REST_ROLE_PATTERN.match(type_str) if match: return match.group(1) else: return type_str def find_return_types(func): def search_return_in_docstr(code): for p in DOCSTRING_RETURN_PATTERNS: match = p.search(code) if match: return match.group(1) if isinstance(func, er.InstanceElement): func = func.var if isinstance(func, er.Function): func = func.base_func type_str = search_return_in_docstr(func.docstr) if not type_str: return [] p = Parser(type_str, None, (1, 0), no_docstr=True) if p.user_stmt is None: return [] p.user_stmt.parent = func return list(evaluate.follow_statement(p.user_stmt))