# -*- coding: utf-8 -*-
# --------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2014 Jonathan Labéjof <jonathan.labejof@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# --------------------------------------------------------------------
"""Python reflection tools."""
from __future__ import unicode_literals, absolute_import
from .iterable import ensureiterable
from inspect import isclass, isroutine, ismethod, getmodule, getmro
from six import PY2, get_method_self, get_method_function
from six.moves import range
__all__ = ['isoldstyle', 'base_elts', 'find_embedding', 'is_inherited']
[docs]def isoldstyle(cls):
"""Return True if cls is an old style class (does not inherits from object
in python2."""
return getmro(cls)[-1] is not object
[docs]def base_elts(elt, cls=None, depth=None):
"""Get bases elements of the input elt.
- If elt is an instance, get class and all base classes.
- If elt is a method, get all base methods.
- If elt is a class, get all base classes.
- In other case, get an empty list.
:param elt: supposed inherited elt.
:param cls: cls from where find attributes equal to elt. If None,
it is found as much as possible. Required in python3 for function
classes.
:type cls: type or list
:param int depth: search depth. If None (default), depth is maximal.
:return: elt bases elements. if elt has not base elements, result is empty.
:rtype: list
"""
result = []
elt_name = getattr(elt, '__name__', None)
if elt_name is not None:
cls = [] if cls is None else ensureiterable(cls)
elt_is_class = False
# if cls is None and elt is routine, it is possible to find the cls
if not cls and isroutine(elt):
if hasattr(elt, '__self__'): # from the instance
instance = get_method_self(elt) # get instance
if instance is None and PY2: # get base im_class if PY2
cls = list(elt.im_class.__bases__)
else: # use instance class
cls = [instance.__class__]
# cls is elt if elt is a class
elif isclass(elt):
elt_is_class = True
cls = list(elt.__bases__)
if cls: # if cls is not empty, find all base classes
index_of_found_classes = 0 # get last visited class index
visited_classes = set(cls) # cache for visited classes
len_classes = len(cls)
if depth is None: # if depth is None, get maximal value
depth = -1 # set negative value
while depth != 0 and index_of_found_classes != len_classes:
len_classes = len(cls)
for index in range(index_of_found_classes, len_classes):
_cls = cls[index]
for base_cls in _cls.__bases__:
if base_cls in visited_classes:
continue
else:
visited_classes.add(base_cls)
cls.append(base_cls)
index_of_found_classes = len_classes
depth -= 1
if elt_is_class:
# if cls is elt, result is classes minus first class
result = cls
elif isroutine(elt):
# get an elt to compare with found element
if ismethod(elt):
elt_to_compare = get_method_function(elt)
else:
elt_to_compare = elt
for _cls in cls: # for all classes
# get possible base elt
b_elt = getattr(_cls, elt_name, None)
if b_elt is not None:
# compare funcs
if ismethod(b_elt):
bec = get_method_function(b_elt)
else:
bec = b_elt
# if matching, add to result
if bec is elt_to_compare:
result.append(b_elt)
return result
[docs]def is_inherited(elt, cls=None):
"""True iif elt is inherited in a base class.
:param elt: elt to check such as an inherited element.
:param type cls: base cls where find the base elt.
:return: true if elt is an inherited element.
:rtype: bool
"""
return base_elts(elt, cls=cls, depth=1)
[docs]def find_embedding(elt, embedding=None):
"""Try to get elt embedding elements.
:param embedding: embedding element. Must have a module.
:return: a list of [module [,class]*] embedding elements which define elt.
:rtype: list
"""
result = [] # result is empty in the worst case
# start to get module
module = getmodule(elt)
if module is not None: # if module exists
visited = set() # cache to avoid to visit twice same element
if embedding is None:
embedding = module
# list of compounds elements which construct the path to elt
compounds = [embedding]
while compounds: # while compounds elements exist
# get last compound
last_embedding = compounds[-1]
# stop to iterate on compounds when last embedding is elt
if last_embedding == elt:
result = compounds # result is compounds
break
else:
# search among embedded elements
for name in dir(last_embedding):
# get embedded element
embedded = getattr(last_embedding, name)
try: # check if embedded has already been visited
if embedded not in visited:
visited.add(embedded) # set it as visited
else:
continue
except TypeError:
pass
else:
# get embedded module
embedded_module = getmodule(embedded)
# and compare it with elt module
if embedded_module is module:
# add embedded to compounds
compounds.append(embedded)
# end the second loop
break
else:
# remove last element if no coumpound element is found
compounds.pop(-1)
return result