Source code for xdoctest.dynamic_analysis

"""
Utilities for dynamically inspecting code
"""
import inspect
import os
import types


[docs] def parse_dynamic_calldefs(modpath_or_module): """ Dynamic parsing of module doctestable items. Unlike static parsing this forces execution of the module code before test-time, however the former is limited to plain-text python files whereas this can discover doctests in binary extension libraries. Args: modpath_or_module (str | PathLike | ModuleType): path to module or the module itself Returns: Dict[str, xdoctest.static_analysis.CallDefNode]: mapping from callnames to CallDefNodes, which contain info about the item with the doctest. CommandLine: python -m xdoctest.dynamic_analysis parse_dynamic_calldefs Example: >>> from xdoctest import dynamic_analysis >>> module = dynamic_analysis >>> calldefs = parse_dynamic_calldefs(module.__file__) >>> for key, calldef in sorted(calldefs.items()): ... print('key = {!r}'.format(key)) ... print(' * calldef.callname = {}'.format(calldef.callname)) ... if calldef.docstr is None: ... print(' * len(calldef.docstr) = {}'.format(calldef.docstr)) ... else: ... print(' * len(calldef.docstr) = {}'.format(len(calldef.docstr))) """ from xdoctest import static_analysis as static import types if isinstance(modpath_or_module, types.ModuleType): module = modpath_or_module else: modpath = modpath_or_module if modpath.endswith('.ipynb'): """ # Devnote: modpath = ub.expandpath("~/code/xdoctest/tests/notebook_with_doctests.ipynb") xdoctest ~/code/xdoctest/tests/notebook_with_doctests.ipynb """ from xdoctest.utils import util_notebook module = util_notebook.import_notebook_from_path(modpath) else: # Possible option for dynamic parsing from xdoctest.utils import util_import module = util_import.import_module_from_path(modpath) calldefs = {} if getattr(module, '__doc__'): calldefs['__doc__'] = static.CallDefNode( callname='__doc__', docstr=module.__doc__, lineno=0, doclineno=1, doclineno_end=1, args=None ) for key, val in iter_module_doctestables(module): # if hasattr(val, '__doc__'): if hasattr(val, '__doc__') and hasattr(val, '__name__'): calldefs[key] = static.CallDefNode( callname=val.__name__, docstr=val.__doc__, lineno=0, doclineno=1, doclineno_end=1, args=None ) return calldefs
[docs] def get_stack_frame(n=0, strict=True): """ Gets the current stack frame or any of its ancestors dynamically Args: n (int): n=0 means the frame you called this function in. n=1 is the parent frame. strict (bool): (default = True) Returns: FrameType: frame_cur Example: >>> frame_cur = get_stack_frame(n=0) >>> print('frame_cur = %r' % (frame_cur,)) >>> assert frame_cur.f_globals['frame_cur'] is frame_cur """ frame_cur = inspect.currentframe() # Use n+1 to always skip the frame of this function for ix in range(n + 1): frame_next = frame_cur.f_back if frame_next is None: # nocover if strict: raise AssertionError('Frame level %r is root' % ix) else: break frame_cur = frame_next return frame_cur
[docs] def get_parent_frame(n=0): """ Returns the frame of that called you. This is equivalent to `get_stack_frame(n=1)` Args: n (int): n=0 means the frame you called this function in. n=1 is the parent frame. Returns: FrameType: parent_frame Example: >>> root0 = get_stack_frame(n=0) >>> def foo(): >>> child = get_stack_frame(n=0) >>> root1 = get_parent_frame(n=0) >>> root2 = get_stack_frame(n=1) >>> return child, root1, root2 >>> # Note this wont work in IPython because several >>> # frames will be inserted between here and foo >>> child, root1, root2 = foo() >>> print('root0 = %r' % (root0,)) >>> print('root1 = %r' % (root1,)) >>> print('root2 = %r' % (root2,)) >>> print('child = %r' % (child,)) >>> assert root0 == root1 >>> assert root1 == root2 >>> assert child != root1 """ parent_frame = get_stack_frame(n=n + 2) return parent_frame
[docs] def iter_module_doctestables(module): r""" Yields doctestable objects that belong to a live python module Args: module (ModuleType): live python module Yields: Tuple[str, callable]: (funcname, func) doctestable CommandLine: python -m xdoctest.dynamic_analysis iter_module_doctestables Example: >>> from xdoctest import dynamic_analysis >>> module = dynamic_analysis >>> doctestable_list = list(iter_module_doctestables(module)) >>> items = sorted([str(item) for item in doctestable_list]) >>> print('[' + '\n'.join(items) + ']') """ valid_func_types = ( types.FunctionType, types.BuiltinFunctionType, types.MethodType, classmethod, staticmethod, property, ) def _recurse(item, module): return is_defined_by_module(item, module) for key, val in module.__dict__.items(): if isinstance(val, valid_func_types): if not _recurse(val, module): continue yield key, val elif isinstance(val, type): if not _recurse(val, module): continue # Yield the class itself yield key, val # Yield methods of the class for subkey, subval in val.__dict__.items(): # Unbound methods are still typed as functions if isinstance(subval, valid_func_types): if not _recurse(subval, module): continue # unpack underlying function if isinstance(subval, property): item = subval.fget elif isinstance(subval, staticmethod): item = subval.__func__ elif isinstance(subval, classmethod): item = subval.__func__ else: item = subval yield key + '.' + subkey, item
[docs] def is_defined_by_module(item, module): """ Check if item is directly defined by a module. This check may not always work, especially for decorated functions. Args: item (object): item to check module (ModuleType): module to check against CommandLine: xdoctest -m xdoctest.dynamic_analysis is_defined_by_module Example: >>> from xdoctest import dynamic_analysis >>> item = dynamic_analysis.is_defined_by_module >>> module = dynamic_analysis >>> assert is_defined_by_module(item, module) >>> item = dynamic_analysis.inspect >>> assert not is_defined_by_module(item, module) >>> item = dynamic_analysis.inspect.ismodule >>> assert not is_defined_by_module(item, module) >>> assert not is_defined_by_module(print, module) >>> # xdoctest: +REQUIRES(CPython) >>> import _ctypes >>> item = _ctypes.Array >>> module = _ctypes >>> assert is_defined_by_module(item, module) >>> item = _ctypes.CFuncPtr.restype >>> module = _ctypes >>> assert is_defined_by_module(item, module) """ from xdoctest import static_analysis as static target_modname = module.__name__ # invalid_types = (int, float, list, tuple, set) # if isinstance(item, invalid_types) or isinstance(item, str): # raise TypeError('can only test definitions for classes and functions') flag = False if isinstance(item, types.ModuleType): if not hasattr(item, '__file__'): try: # hack for cv2 and xfeatures2d name = static.modpath_to_modname(module.__file__) flag = name in str(item) except Exception: flag = False else: item_modpath = os.path.realpath(os.path.dirname(item.__file__)) mod_fpath = module.__file__.replace('.pyc', '.py') if not mod_fpath.endswith('__init__.py'): flag = False else: modpath = os.path.realpath(os.path.dirname(mod_fpath)) modpath = modpath.replace('.pyc', '.py') flag = item_modpath.startswith(modpath) else: # unwrap static/class/property methods if isinstance(item, property): item = item.fget if isinstance(item, staticmethod): item = item.__func__ if isinstance(item, classmethod): item = item.__func__ if getattr(item, '__module__', None) == target_modname: flag = True elif hasattr(item, '__objclass__'): # should we just unwrap objclass? parent = item.__objclass__ if getattr(parent, '__module__', None) == target_modname: flag = True if not flag: try: item_modname = item.__globals__['__name__'] if item_modname == target_modname: flag = True except AttributeError: pass return flag
if __name__ == '__main__': import xdoctest as xdoc xdoc.doctest_module()