Source code for pykern.pkinspect

"""Helper functions for to :mod:`inspect`.

:copyright: Copyright (c) 2015 RadiaSoft, Inc.  All Rights Reserved.
:license: http://www.apache.org/licenses/LICENSE-2.0.html
"""

# Avoid pykern imports so avoid dependency issues for pkconfig
from pykern.pkcollections import PKDict
import importlib
import inspect
import os
import os.path
import pkgutil
import re
import sys

#: Used to simplify paths output
_start_dir = ""
try:
    _start_dir = os.getcwd()
except Exception:
    pass


_VALID_IDENTIFIER_RE = re.compile(r"^[a-z_]\w*$", re.IGNORECASE)


[docs] class SubmoduleNotFound(ModuleNotFoundError): """Raised by import_submodule""" pass
[docs] class Call(PKDict): """Saves file:line:name of stack frame and renders as string. Args: frame_or_log (frame or LogRecord): values to extract Attributes: filename (str): full path (co_filename) lineno (int): line number (f_lineno) name (str): function name (co_name) """ def __init__(self, frame_or_log): try: if hasattr(frame_or_log, "f_code"): super(Call, self).__init__( filename=frame_or_log.f_code.co_filename, lineno=frame_or_log.f_lineno, name=frame_or_log.f_code.co_name, # Only used by caller_module() _module=sys.modules.get(frame_or_log.f_globals.get("__name__")), ) else: super(Call, self).__init__( filename=frame_or_log.pathname, lineno=frame_or_log.lineno, name=frame_or_log.funcName, _module=None, ) finally: if frame_or_log: del frame_or_log
[docs] def pkdebug_str(self): return self.__str__()
def __str__(self): try: filename = os.path.relpath(self.filename, _start_dir) if len(filename) > len(self.filename): # "relpath" always makes relative even when no common components. # Take the absolute (shorter) path filename = self.filename return "{}:{}:{}".format(filename, self.lineno, self.name) except Exception: return "<no file>:0:<no func>"
[docs] def append_exception_reason(exc, reason): """Augment `exc` with `reason` Modifies `exc` in place by adding to `exc.args` or `exc.reason`. Does it's best to not cause another exception during this process. Args: exc (BaseException): what was raised reason (str): our related reason """ def _prefix_reason(string): return ("; " if len(string) > 0 else "") + reason if hasattr(exc, "reason") and isinstance(exc.reason, str): exc.reason += _prefix_reason(exc.reason) if hasattr(exc, "args"): if exc.args is None: exc.args = tuple() if isinstance(exc.args, (tuple, list)): if len(exc.args) == 0: exc.args = (reason,) elif isinstance(exc.args[0], str): x = list(exc.args) x[0] += _prefix_reason(x[0]) exc.args = tuple(x)
# Add other cases as they arise # Otherwise, leave exception unmodified
[docs] def caller(ignore_modules=None, exclude_first=True): """Which file:line:func is calling the caller of this function. Will not return the same module as the calling module, that is, will iterate until a new module is found. If `ignore_modules` is defined, will ignore those modules as well. Note: may return __main__ module. Will raise exception if calling from __main__ Args: ignore_modules (list): other modules (objects) to exclude [None] exclude_first (bool): skip first module found [True] Returns: PKDict: keys: filename, lineno, name, module """ frame = None try: exclude = [inspect.getmodule(caller)] if ignore_modules: exclude.extend(ignore_modules) exclude_orig_len = len(exclude) # Ugly code, because don't want to bind "frame" # in a call. frame = inspect.currentframe().f_back while True: m = inspect.getmodule(frame) # getmodule doesn't always work for some reason if not m: m = sys.modules[frame.f_globals["__name__"]] if m not in exclude: if len(exclude) > exclude_orig_len or not exclude_first: return Call(frame) # Have to go back two exclusions (this module and our caller) exclude.append(m) frame = frame.f_back # Will raise exception if calling from __main__ finally: # If an exception is thrown, the stack # hangs around forever. That's what the del frame # is for. if frame: del frame
[docs] def caller_func_name(): """Name of function one frame back Useful for inter-module dispatch and errors. Returns: str: function name """ return inspect.currentframe().f_back.f_back.f_code.co_name
[docs] def caller_module(exclude_first=True): """Which module is calling the caller of this function. Will not return the same module as the calling module, that is, will iterate until a new module is found. If exclude_first == True it will also exclude the first module found that is not the calling module. Note: may return __main__ module. Will raise exception if calling from __main__ Args: exclude_first (bool): skip first module found [True] Returns: module: module which is calling module """ return caller(exclude_first=exclude_first)._module
[docs] def import_submodule(submodule, subpackage=None, root_packages=None): """Import a module of the form ``root.subpackage.submodule`` Search ``root_packages``, e.g. ``(sirepo, pykern)``, for modules within the a subpackage of the roots. Args: submodule (str): last component of the full module name subpackage (str): name of "middle" component in full module name [submodule_name(caller_module())] packages (tuple or list): tuple/list of packages to search [root_package(caller_module())] Returns: module: imported module object Raises: SubmoduleNotFound: for submodule not being found vs `ModuleNotFoundError` when any module imported by submodule is in error. """ if root_packages is None: root_packages = (root_package(caller_module(exclude_first=False)),) if subpackage is None: subpackage = submodule_name(caller_module(exclude_first=False)) for p in root_packages: s = f"{p}.{subpackage}" n = f"{s}.{submodule}" try: return importlib.import_module(n) except ModuleNotFoundError as e: if e.name not in (p, s, n): # import is failing due to ModuleNotFoundError in a sub-import # not the module we are looking for. raise raise SubmoduleNotFound( # this becomes args[0] of BaseException f"cannot find module={subpackage}.{submodule} in root_packages={root_packages}", # Give a complete name to make sense in ModuleNotFound contexts name=f"{root_packages[-1]}.{subpackage}.{submodule}", )
[docs] def is_caller_main(): """Is the caller's calling module __main__? Returns: bool: True if calling module was called by __main__. """ return caller_module().__name__ == "__main__"
[docs] def is_valid_identifier(string): """Is this a valid Python identifier? Args: string (str): what to validate Returns: bool: True if is valid python ident. """ return bool(_VALID_IDENTIFIER_RE.search(string))
[docs] def module_basename(obj): """Parse the last part of a module name For example, module_basename(pkinspect) is 'pkinspect'. Args: obj (object): any python object Returns: str: base part of the module name """ return module_name_split(obj).pop()
[docs] def module_name_join(names): """Joins names with '.' Args: names (iterable): list of strings to join Returns: str: module name """ return ".".join(names)
[docs] def module_name_split(obj): """Splits obj's module name on '.' Args: obj (object): any python object Returns: str: base part of the module name """ return inspect.getmodule(obj).__name__.split(".")
[docs] def module_functions(func_prefix, module=None): """Get all module level functions starting with func_prefix Args: func_prefix (str): the prefix of function names to get module (object): a module to get functions from (calling module if None) Returns: PKDict: dict of function name mapped to the function object """ r = PKDict() for n, o in inspect.getmembers(module or caller_module(exclude_first=False)): if n.startswith(func_prefix) and inspect.isfunction(o): r[n] = o return r
[docs] def package_module_names(name_or_module): """List of modules of package `name_or_module` Args: name_or_module (object): absolute name of package, e.g. pykern.pkcli, or module object Returns: list: sorted, relative module names, e.g. [ci, fmt, github, ...] """ p = ( name_or_module if inspect.ismodule(name_or_module) else importlib.import_module(name_or_module) ) return sorted(m.name for m in pkgutil.iter_modules([os.path.dirname(p.__file__)]))
[docs] def root_package(obj): """Parse the root package in which `obj` is defined. For example, root_package(module_basename) is 'pykern'. Args: obj (object): any python object Returns: str: root package for the object """ return module_name_split(obj).pop(0)
[docs] def submodule_name(obj): """Remove the root package in which `obj` is defined. For example, root_package(module_basename) is 'pkinspect'. Args: obj (object): any python object Returns: str: submodule for the object """ x = module_name_split(obj) x.pop(0) return module_name_join(x)
[docs] def this_module(): """Module object for caller Returns: module: module object """ return caller(exclude_first=False)._module