Source code for pykern.pkinspect
# -*- coding: utf-8 -*-
u"""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
"""
from __future__ import absolute_import, division, print_function
# Avoid pykern imports so avoid dependency issues for pkconfig
from pykern import pkcollections
import inspect
import os
import os.path
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 Call(pkcollections.Dict):
"""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[frame_or_log.f_globals['__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
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 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:
pkcollections.Dict: 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_module():
"""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.
Note: may return __main__ module.
Will raise exception if calling from __main__
Returns:
module: module which is calling module
"""
return caller()._module
[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
"""
n = inspect.getmodule(obj).__name__
return n.split('.');
[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