155 lines
4.9 KiB
Python
155 lines
4.9 KiB
Python
# -*- test-case-name: twisted.test.test_reflect -*-
|
|
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Standardized versions of various cool and/or strange things that you can do
|
|
with Python's reflection capabilities.
|
|
"""
|
|
|
|
import sys
|
|
|
|
from jsonschema.compat import PY3
|
|
|
|
|
|
class _NoModuleFound(Exception):
|
|
"""
|
|
No module was found because none exists.
|
|
"""
|
|
|
|
|
|
|
|
class InvalidName(ValueError):
|
|
"""
|
|
The given name is not a dot-separated list of Python objects.
|
|
"""
|
|
|
|
|
|
|
|
class ModuleNotFound(InvalidName):
|
|
"""
|
|
The module associated with the given name doesn't exist and it can't be
|
|
imported.
|
|
"""
|
|
|
|
|
|
|
|
class ObjectNotFound(InvalidName):
|
|
"""
|
|
The object associated with the given name doesn't exist and it can't be
|
|
imported.
|
|
"""
|
|
|
|
|
|
|
|
if PY3:
|
|
def reraise(exception, traceback):
|
|
raise exception.with_traceback(traceback)
|
|
else:
|
|
exec("""def reraise(exception, traceback):
|
|
raise exception.__class__, exception, traceback""")
|
|
|
|
reraise.__doc__ = """
|
|
Re-raise an exception, with an optional traceback, in a way that is compatible
|
|
with both Python 2 and Python 3.
|
|
|
|
Note that on Python 3, re-raised exceptions will be mutated, with their
|
|
C{__traceback__} attribute being set.
|
|
|
|
@param exception: The exception instance.
|
|
@param traceback: The traceback to use, or C{None} indicating a new traceback.
|
|
"""
|
|
|
|
|
|
def _importAndCheckStack(importName):
|
|
"""
|
|
Import the given name as a module, then walk the stack to determine whether
|
|
the failure was the module not existing, or some code in the module (for
|
|
example a dependent import) failing. This can be helpful to determine
|
|
whether any actual application code was run. For example, to distiguish
|
|
administrative error (entering the wrong module name), from programmer
|
|
error (writing buggy code in a module that fails to import).
|
|
|
|
@param importName: The name of the module to import.
|
|
@type importName: C{str}
|
|
@raise Exception: if something bad happens. This can be any type of
|
|
exception, since nobody knows what loading some arbitrary code might
|
|
do.
|
|
@raise _NoModuleFound: if no module was found.
|
|
"""
|
|
try:
|
|
return __import__(importName)
|
|
except ImportError:
|
|
excType, excValue, excTraceback = sys.exc_info()
|
|
while excTraceback:
|
|
execName = excTraceback.tb_frame.f_globals["__name__"]
|
|
# in Python 2 execName is None when an ImportError is encountered,
|
|
# where in Python 3 execName is equal to the importName.
|
|
if execName is None or execName == importName:
|
|
reraise(excValue, excTraceback)
|
|
excTraceback = excTraceback.tb_next
|
|
raise _NoModuleFound()
|
|
|
|
|
|
|
|
def namedAny(name):
|
|
"""
|
|
Retrieve a Python object by its fully qualified name from the global Python
|
|
module namespace. The first part of the name, that describes a module,
|
|
will be discovered and imported. Each subsequent part of the name is
|
|
treated as the name of an attribute of the object specified by all of the
|
|
name which came before it. For example, the fully-qualified name of this
|
|
object is 'twisted.python.reflect.namedAny'.
|
|
|
|
@type name: L{str}
|
|
@param name: The name of the object to return.
|
|
|
|
@raise InvalidName: If the name is an empty string, starts or ends with
|
|
a '.', or is otherwise syntactically incorrect.
|
|
|
|
@raise ModuleNotFound: If the name is syntactically correct but the
|
|
module it specifies cannot be imported because it does not appear to
|
|
exist.
|
|
|
|
@raise ObjectNotFound: If the name is syntactically correct, includes at
|
|
least one '.', but the module it specifies cannot be imported because
|
|
it does not appear to exist.
|
|
|
|
@raise AttributeError: If an attribute of an object along the way cannot be
|
|
accessed, or a module along the way is not found.
|
|
|
|
@return: the Python object identified by 'name'.
|
|
"""
|
|
if not name:
|
|
raise InvalidName('Empty module name')
|
|
|
|
names = name.split('.')
|
|
|
|
# if the name starts or ends with a '.' or contains '..', the __import__
|
|
# will raise an 'Empty module name' error. This will provide a better error
|
|
# message.
|
|
if '' in names:
|
|
raise InvalidName(
|
|
"name must be a string giving a '.'-separated list of Python "
|
|
"identifiers, not %r" % (name,))
|
|
|
|
topLevelPackage = None
|
|
moduleNames = names[:]
|
|
while not topLevelPackage:
|
|
if moduleNames:
|
|
trialname = '.'.join(moduleNames)
|
|
try:
|
|
topLevelPackage = _importAndCheckStack(trialname)
|
|
except _NoModuleFound:
|
|
moduleNames.pop()
|
|
else:
|
|
if len(names) == 1:
|
|
raise ModuleNotFound("No module named %r" % (name,))
|
|
else:
|
|
raise ObjectNotFound('%r does not name an object' % (name,))
|
|
|
|
obj = topLevelPackage
|
|
for n in names[1:]:
|
|
obj = getattr(obj, n)
|
|
|
|
return obj
|