import sys
import types
import logging
import pkgutil
import importlib
import traceback
logger = logging.getLogger(__name__)
def _package(module):
"""Hack for python2 since it does not have __package__ always set up.
Args:
module: Module for which we want the package name
Returns:
str: Model's package name
"""
return (
module.__name__ if module.__package__ is None else module.__package__
)
[docs]def get_object(path="", obj=None):
"""Return an object from a dot path.
Path can either be a full path, in which case the `get_object` function
will try to import the modules in the path and follow it to the final
object. Or it can be a path relative to the object passed in as the second
argument.
Args:
path (str): Full or relative dot path to the desired object
obj (object): Starting object. Dot path is calculated relatively to
this object.
Returns:
Object at the end of the path, or list of non hidden objects if we use
the star query.
Example for full paths::
>>> get_object('os.path.join')
<function join at 0x1002d9ed8>
>>> get_object('tea.process')
<module 'tea.process' from 'tea/process/__init__.pyc'>
Example for relative paths when an object is passed in::
>>> import os
>>> get_object('path.join', os)
<function join at 0x1002d9ed8>
Example for a star query. (Star query can be used only as the last element
of the path::
>>> get_object('tea.dsa.*')
[]
>>> get_object('tea.dsa.singleton.*')
[<class 'tea.dsa.singleton.Singleton'>,
<class 'tea.dsa.singleton.SingletonMetaclass'>
<module 'six' from '...'>]
>>> get_object('tea.dsa.*')
[<module 'tea.dsa.singleton' from '...'>] # Since we imported it
"""
if not path:
return obj
path = path.split(".")
if obj is None:
obj = importlib.import_module(path[0])
path = path[1:]
for item in path:
if item == "*":
# This is the star query, returns non hidden objects
return [
getattr(obj, name)
for name in dir(obj)
if not name.startswith("__")
]
if isinstance(obj, types.ModuleType):
submodule = "{}.{}".format(_package(obj), item)
try:
obj = importlib.import_module(submodule)
except Exception as import_error:
try:
obj = getattr(obj, item)
except Exception:
# FIXME: I know I should probably merge the errors, but
# it's easier just to throw the import error since
# it's most probably the one user wants to see.
# Create a new LoadingError and throw a combination
# of the import error and attribute error.
raise import_error
else:
obj = getattr(obj, item)
return obj
[docs]class Loader(object):
"""Module loader class loads recursively a module and all it's submodules.
Loaded modules will be stored in the ``modules`` attribute of the loader as
a dictionary of {module_path: module} key, value pairs.
Errors accounted during the loading process will not stop the loading
process. They will be stored in the ``errors`` attribute of the loader as a
dictionary of {module_path: exception} key, value pairs.
Usage::
loader = Loader()
loader.load('foo')
loader.load('baz.bar', 'boo')
import baz
loader.load(baz)
"""
def __init__(self):
self.modules = {}
self.errors = {}
[docs] def load(self, *modules):
"""Load one or more modules.
Args:
modules: Either a string full path to a module or an actual module
object.
"""
for module in modules:
if isinstance(module, str):
try:
module = get_object(module)
except Exception as e:
self.errors[module] = e
continue
self.modules[module.__package__] = module
for (loader, module_name, is_pkg) in pkgutil.walk_packages(
module.__path__
):
full_name = "{}.{}".format(_package(module), module_name)
try:
self.modules[full_name] = get_object(full_name)
if is_pkg:
self.load(self.modules[full_name])
except Exception as e:
self.errors[full_name] = e
[docs]def load_subclasses(klass, modules=None):
"""Load recursively all all subclasses from a module.
Args:
klass (str or list of str): Class whose subclasses we want to load.
modules: List of additional modules or module names that should be
recursively imported in order to find all the subclasses of the
desired class. Default: None
FIXME: This function is kept only for backward compatibility reasons, it
should not be used. Deprecation warning should be raised and it should
be replaces by the ``Loader`` class.
"""
if modules:
if isinstance(modules, str):
modules = [modules]
loader = Loader()
loader.load(*modules)
return klass.__subclasses__()
[docs]def get_exception():
"""Return full formatted traceback as a string."""
trace = ""
exception = ""
exc_list = traceback.format_exception_only(
sys.exc_info()[0], sys.exc_info()[1]
)
for entry in exc_list:
exception += entry
tb_list = traceback.format_tb(sys.exc_info()[2])
for entry in tb_list:
trace += entry
return "%s\n%s" % (exception, trace)
[docs]def cmp(x, y):
"""Compare function from python2."""
return (x > y) - (x < y)