Use __qualname__ where appropriate
The __qualname__ attribute simplifies the determination of an objects class name and callable name and is useful in python 3.x to be able to use since it can correctly identify names better than the python 2.x __name__ attribute can. Adds a few tests in to ensure that the usage of __qualname__ where available functions as expected. See: http://legacy.python.org/dev/peps/pep-3155/ Fixes bug 1341441 Change-Id: Ic6942cbbc8e35d65fb3ac603ff1dfc8e20c194a3
This commit is contained in:

committed by
Joshua Harlow

parent
08b5505419
commit
5c84ddad84
@@ -19,6 +19,9 @@ import functools
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from taskflow import states
|
||||
from taskflow import test
|
||||
from taskflow.tests import utils as test_utils
|
||||
@@ -111,17 +114,22 @@ class GetCallableNameTest(test.TestCase):
|
||||
|
||||
def test_method(self):
|
||||
name = reflection.get_callable_name(Class.method)
|
||||
self.assertEqual(name, '.'.join((__name__, 'method')))
|
||||
self.assertEqual(name, '.'.join((__name__, 'Class', 'method')))
|
||||
|
||||
def test_instance_method(self):
|
||||
name = reflection.get_callable_name(Class().method)
|
||||
self.assertEqual(name, '.'.join((__name__, 'Class', 'method')))
|
||||
|
||||
def test_static_method(self):
|
||||
# NOTE(imelnikov): static method are just functions, class name
|
||||
# is not recorded anywhere in them.
|
||||
name = reflection.get_callable_name(Class.static_method)
|
||||
self.assertEqual(name, '.'.join((__name__, 'static_method')))
|
||||
if six.PY3:
|
||||
self.assertEqual(name,
|
||||
'.'.join((__name__, 'Class', 'static_method')))
|
||||
else:
|
||||
# NOTE(imelnikov): static method are just functions, class name
|
||||
# is not recorded anywhere in them.
|
||||
self.assertEqual(name,
|
||||
'.'.join((__name__, 'static_method')))
|
||||
|
||||
def test_class_method(self):
|
||||
name = reflection.get_callable_name(Class.class_method)
|
||||
@@ -141,6 +149,46 @@ class GetCallableNameTest(test.TestCase):
|
||||
'__call__')))
|
||||
|
||||
|
||||
# These extended/special case tests only work on python 3, due to python 2
|
||||
# being broken/incorrect with regard to these special cases...
|
||||
@testtools.skipIf(not six.PY3, 'python 3.x is not currently available')
|
||||
class GetCallableNameTestExtended(test.TestCase):
|
||||
# Tests items in http://legacy.python.org/dev/peps/pep-3155/
|
||||
|
||||
class InnerCallableClass(object):
|
||||
def __call__(self):
|
||||
pass
|
||||
|
||||
def test_inner_callable_class(self):
|
||||
obj = self.InnerCallableClass()
|
||||
name = reflection.get_callable_name(obj.__call__)
|
||||
expected_name = '.'.join((__name__, 'GetCallableNameTestExtended',
|
||||
'InnerCallableClass', '__call__'))
|
||||
self.assertEqual(expected_name, name)
|
||||
|
||||
def test_inner_callable_function(self):
|
||||
def a():
|
||||
|
||||
def b():
|
||||
pass
|
||||
|
||||
return b
|
||||
|
||||
name = reflection.get_callable_name(a())
|
||||
expected_name = '.'.join((__name__, 'GetCallableNameTestExtended',
|
||||
'test_inner_callable_function', '<locals>',
|
||||
'a', '<locals>', 'b'))
|
||||
self.assertEqual(expected_name, name)
|
||||
|
||||
def test_inner_class(self):
|
||||
obj = self.InnerCallableClass()
|
||||
name = reflection.get_callable_name(obj)
|
||||
expected_name = '.'.join((__name__,
|
||||
'GetCallableNameTestExtended',
|
||||
'InnerCallableClass'))
|
||||
self.assertEqual(expected_name, name)
|
||||
|
||||
|
||||
class NotifierTest(test.TestCase):
|
||||
|
||||
def test_notify_called(self):
|
||||
|
@@ -21,6 +21,16 @@ import six
|
||||
|
||||
from taskflow.openstack.common import importutils
|
||||
|
||||
try:
|
||||
_TYPE_TYPE = types.TypeType
|
||||
except AttributeError:
|
||||
_TYPE_TYPE = type
|
||||
|
||||
# See: https://docs.python.org/2/library/__builtin__.html#module-__builtin__
|
||||
# and see https://docs.python.org/2/reference/executionmodel.html (and likely
|
||||
# others)...
|
||||
_BUILTIN_MODULES = ('builtins', '__builtin__', 'exceptions')
|
||||
|
||||
|
||||
def _get_members(obj, exclude_hidden):
|
||||
"""Yields the members of an object, filtering by hidden/not hidden."""
|
||||
@@ -86,12 +96,27 @@ def get_class_name(obj, fully_qualified=True):
|
||||
"""
|
||||
if not isinstance(obj, six.class_types):
|
||||
obj = type(obj)
|
||||
if obj.__module__ in ('builtins', '__builtin__', 'exceptions'):
|
||||
return obj.__name__
|
||||
if fully_qualified:
|
||||
return '.'.join((obj.__module__, obj.__name__))
|
||||
try:
|
||||
built_in = obj.__module__ in _BUILTIN_MODULES
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
return obj.__name__
|
||||
if built_in:
|
||||
try:
|
||||
return obj.__qualname__
|
||||
except AttributeError:
|
||||
return obj.__name__
|
||||
pieces = []
|
||||
try:
|
||||
pieces.append(obj.__qualname__)
|
||||
except AttributeError:
|
||||
pieces.append(obj.__name__)
|
||||
if fully_qualified:
|
||||
try:
|
||||
pieces.insert(0, obj.__module__)
|
||||
except AttributeError:
|
||||
pass
|
||||
return '.'.join(pieces)
|
||||
|
||||
|
||||
def get_all_class_names(obj, up_to=object):
|
||||
@@ -115,21 +140,36 @@ def get_callable_name(function):
|
||||
"""
|
||||
method_self = get_method_self(function)
|
||||
if method_self is not None:
|
||||
# this is bound method
|
||||
# This is a bound method.
|
||||
if isinstance(method_self, six.class_types):
|
||||
# this is bound class method
|
||||
# This is a bound class method.
|
||||
im_class = method_self
|
||||
else:
|
||||
im_class = type(method_self)
|
||||
parts = (im_class.__module__, im_class.__name__,
|
||||
function.__name__)
|
||||
elif inspect.isfunction(function) or inspect.ismethod(function):
|
||||
parts = (function.__module__, function.__name__)
|
||||
try:
|
||||
parts = (im_class.__module__, function.__qualname__)
|
||||
except AttributeError:
|
||||
parts = (im_class.__module__, im_class.__name__, function.__name__)
|
||||
elif inspect.ismethod(function) or inspect.isfunction(function):
|
||||
# This could be a function, a static method, a unbound method...
|
||||
try:
|
||||
parts = (function.__module__, function.__qualname__)
|
||||
except AttributeError:
|
||||
if hasattr(function, 'im_class'):
|
||||
# This is a unbound method, which exists only in python 2.x
|
||||
im_class = function.im_class
|
||||
parts = (im_class.__module__,
|
||||
im_class.__name__, function.__name__)
|
||||
else:
|
||||
parts = (function.__module__, function.__name__)
|
||||
else:
|
||||
im_class = type(function)
|
||||
if im_class is type:
|
||||
if im_class is _TYPE_TYPE:
|
||||
im_class = function
|
||||
parts = (im_class.__module__, im_class.__name__)
|
||||
try:
|
||||
parts = (im_class.__module__, im_class.__qualname__)
|
||||
except AttributeError:
|
||||
parts = (im_class.__module__, im_class.__name__)
|
||||
return '.'.join(parts)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user