6ccdbd3731
Change-Id: Ia487c2961849a1d42eb72b2a69c360ea638f9d1b Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
240 lines
8.5 KiB
Python
240 lines
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (C) 2012-2013 Yahoo! Inc. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Reflection module.
|
|
|
|
.. versionadded:: 1.1
|
|
"""
|
|
|
|
import inspect
|
|
import logging
|
|
import operator
|
|
import types
|
|
|
|
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__', '__builtins__', 'exceptions')
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
Parameter = inspect.Parameter
|
|
Signature = inspect.Signature
|
|
get_signature = inspect.signature
|
|
|
|
|
|
def get_members(obj, exclude_hidden=True):
|
|
"""Yields the members of an object, filtering by hidden/not hidden.
|
|
|
|
.. versionadded:: 2.3
|
|
"""
|
|
for (name, value) in inspect.getmembers(obj):
|
|
if name.startswith("_") and exclude_hidden:
|
|
continue
|
|
yield (name, value)
|
|
|
|
|
|
def get_member_names(obj, exclude_hidden=True):
|
|
"""Get all the member names for a object."""
|
|
return [name for (name, _obj) in
|
|
get_members(obj, exclude_hidden=exclude_hidden)]
|
|
|
|
|
|
def get_class_name(obj, fully_qualified=True, truncate_builtins=True):
|
|
"""Get class name for object.
|
|
|
|
If object is a type, returns name of the type. If object is a bound
|
|
method or a class method, returns its ``self`` object's class name.
|
|
If object is an instance of class, returns instance's class name.
|
|
Else, name of the type of the object is returned. If fully_qualified
|
|
is True, returns fully qualified name of the type. For builtin types,
|
|
just name is returned. TypeError is raised if can't get class name from
|
|
object.
|
|
"""
|
|
if inspect.isfunction(obj):
|
|
raise TypeError("Can't get class name.")
|
|
|
|
if inspect.ismethod(obj):
|
|
obj = get_method_self(obj)
|
|
if not isinstance(obj, type):
|
|
obj = type(obj)
|
|
if truncate_builtins:
|
|
try:
|
|
built_in = obj.__module__ in _BUILTIN_MODULES
|
|
except AttributeError: # nosec
|
|
pass
|
|
else:
|
|
if built_in:
|
|
return obj.__name__
|
|
if fully_qualified and hasattr(obj, '__module__'):
|
|
return '%s.%s' % (obj.__module__, obj.__name__)
|
|
else:
|
|
return obj.__name__
|
|
|
|
|
|
def get_all_class_names(obj, up_to=object,
|
|
fully_qualified=True, truncate_builtins=True):
|
|
"""Get class names of object parent classes.
|
|
|
|
Iterate over all class names object is instance or subclass of,
|
|
in order of method resolution (mro). If up_to parameter is provided,
|
|
only name of classes that are sublcasses to that class are returned.
|
|
"""
|
|
if not isinstance(obj, type):
|
|
obj = type(obj)
|
|
for cls in obj.mro():
|
|
if issubclass(cls, up_to):
|
|
yield get_class_name(cls,
|
|
fully_qualified=fully_qualified,
|
|
truncate_builtins=truncate_builtins)
|
|
|
|
|
|
def get_callable_name(function):
|
|
"""Generate a name from callable.
|
|
|
|
Tries to do the best to guess fully qualified callable name.
|
|
"""
|
|
method_self = get_method_self(function)
|
|
if method_self is not None:
|
|
# This is a bound method.
|
|
if isinstance(method_self, type):
|
|
# This is a bound class method.
|
|
im_class = method_self
|
|
else:
|
|
im_class = type(method_self)
|
|
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_TYPE:
|
|
im_class = function
|
|
try:
|
|
parts = (im_class.__module__, im_class.__qualname__)
|
|
except AttributeError:
|
|
parts = (im_class.__module__, im_class.__name__)
|
|
return '.'.join(parts)
|
|
|
|
|
|
def get_method_self(method):
|
|
"""Gets the ``self`` object attached to this method (or none)."""
|
|
if not inspect.ismethod(method):
|
|
return None
|
|
try:
|
|
return operator.attrgetter("__self__")(method)
|
|
except AttributeError:
|
|
return None
|
|
|
|
|
|
def is_same_callback(callback1, callback2, strict=True):
|
|
"""Returns if the two callbacks are the same.
|
|
|
|
'strict' arg has no meaning for python 3.8 onwards and will
|
|
always return the equality of both callback based on 'self'
|
|
comparison only.
|
|
"""
|
|
if callback1 is callback2:
|
|
# This happens when plain methods are given (or static/non-bound
|
|
# methods).
|
|
return True
|
|
if callback1 == callback2:
|
|
# NOTE(gmann): python3.8 onward, comparison of bound methods is
|
|
# changed. It no longer decide the bound method's equality based
|
|
# on their bounded objects equality instead it checks the identity
|
|
# of their '__self__'. So python3.8 onward, two different bound
|
|
# methods are no longer equal even __eq__ method return True.
|
|
# Or in other term, 'strict' arg has no meaning from python 3.8
|
|
# onwards above if condition never satisfy if both callback are
|
|
# bounded to two different objects.
|
|
# For backward compatibility for python <3.8, we can keep the 'strict'
|
|
# arg and the below code of comparing 'self' and once minimum
|
|
# supported python version is 3.8 we can remove both because python
|
|
# 3.8 onward == operator itself checks identity of 'self'.
|
|
# Ref bug: https://bugs.launchpad.net/oslo.utils/+bug/1841072
|
|
if not strict:
|
|
LOG.warning('"strict" arg is deprecated because it no '
|
|
'longer work for python 3.8 onwards')
|
|
return True
|
|
# Until python 3.7, two bound methods are equal if functions
|
|
# themselves are equal and objects they are applied to are equal.
|
|
# This means that a bound method could be the same bound method on
|
|
# another object if the objects have __eq__ methods that return true
|
|
# (when in fact it is a different bound method). Python u so crazy!
|
|
try:
|
|
self1 = operator.attrgetter("__self__")(callback1)
|
|
self2 = operator.attrgetter("__self__")(callback2)
|
|
return self1 is self2
|
|
except AttributeError: # nosec
|
|
pass
|
|
return False
|
|
|
|
|
|
def is_bound_method(method):
|
|
"""Returns if the given method is bound to an object."""
|
|
return get_method_self(method) is not None
|
|
|
|
|
|
def is_subclass(obj, cls):
|
|
"""Returns if the object is class and it is subclass of a given class."""
|
|
return inspect.isclass(obj) and issubclass(obj, cls)
|
|
|
|
|
|
def get_callable_args(function, required_only=False):
|
|
"""Get names of callable arguments.
|
|
|
|
Special arguments (like ``*args`` and ``**kwargs``) are not included into
|
|
output.
|
|
|
|
If required_only is True, optional arguments (with default values)
|
|
are not included into output.
|
|
"""
|
|
sig = get_signature(function)
|
|
function_args = list(sig.parameters.keys())
|
|
for param_name, p in sig.parameters.items():
|
|
if (p.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD) or
|
|
(required_only and p.default is not Parameter.empty)):
|
|
function_args.remove(param_name)
|
|
return function_args
|
|
|
|
|
|
def accepts_kwargs(function):
|
|
"""Returns ``True`` if function accepts kwargs otherwise ``False``."""
|
|
sig = get_signature(function)
|
|
return any(
|
|
p.kind == Parameter.VAR_KEYWORD for p in sig.parameters.values()
|
|
)
|