749ae85347
The keyword arguments fully_qualified and truncate_builtins are useful to allow to be passed, and are meaningful to pass along from this function to the `get_class_name` function so let them be passed. Change-Id: I0787bbaf209f3c223e72214d63e006cfc1d40866
222 lines
7.5 KiB
Python
222 lines
7.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 types
|
|
|
|
import six
|
|
|
|
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')
|
|
|
|
if six.PY3:
|
|
Parameter = inspect.Parameter
|
|
Signature = inspect.Signature
|
|
get_signature = inspect.signature
|
|
else:
|
|
# Provide an equivalent but use funcsigs instead...
|
|
import funcsigs
|
|
Parameter = funcsigs.Parameter
|
|
Signature = funcsigs.Signature
|
|
get_signature = funcsigs.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, six.class_types):
|
|
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, six.class_types):
|
|
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, six.class_types):
|
|
# 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 six.get_method_self(method)
|
|
except AttributeError:
|
|
return None
|
|
|
|
|
|
def is_same_callback(callback1, callback2, strict=True):
|
|
"""Returns if the two callbacks are the same."""
|
|
if callback1 is callback2:
|
|
# This happens when plain methods are given (or static/non-bound
|
|
# methods).
|
|
return True
|
|
if callback1 == callback2:
|
|
if not strict:
|
|
return True
|
|
# 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 = six.get_method_self(callback1)
|
|
self2 = six.get_method_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(six.iterkeys(sig.parameters))
|
|
for param_name, p in six.iteritems(sig.parameters):
|
|
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 six.itervalues(sig.parameters))
|