# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------
# xoutil.inspect
# ----------------------------------------------------------------------
# Copyright (c) 2015 Merchise and Contributors
# Copyright (c) 2013, 2014 Merchise Autrement and Contributors
# All rights reserved
#
# This file is distributed under the terms of the LICENCE distributed
# with this package.
#
# Created 2014-05-02
'''Extensions to Python's ``inspect`` module.
You may use it as drop-in replacement of ``inspect``. Although we don't
document all items here. Refer to :mod:`inspect's <inspect>` documentation.
'''
from __future__ import (division as _py3_division,
print_function as _py3_print,
unicode_literals as _py3_unicode,
absolute_import as _absolute_import)
from xoutil.modules import copy_members as _copy_python_module_members
_pm = _copy_python_module_members()
types = _pm.types
isdatadescriptor = _pm.isdatadescriptor
# TODO: Rename all use of `StandardException`
from xoutil.eight.exceptions import StandardError as StandardException
try:
getattr_static = _pm.getattr_static
except AttributeError:
# Copied from `/usr/lib/python3.3/inspect.py`
# TODO: Maybe all these function must be moved to eight
from xoutil.eight import typeof as _typeof
_sentinel = object()
def _safe_search_bases(cls, accum):
# Simulate the "classic class" search order.
if cls in accum:
return
else:
accum.append(cls)
for base in cls.__bases__:
_safe_search_bases(base, accum)
def _static_getmro(klass):
'''Get a reasonable method resolution order of a class.
Works well for both old-style and new-style classes.
'''
try:
old_class_type = types.ClassType
except AttributeError:
# Python 3.1 lacks both getattr_static and ClassType
class ClassType(type):
'''Impossible class type.'''
old_class_type = ClassType
if isinstance(klass, type):
return type.__dict__['__mro__'].__get__(klass)
elif isinstance(klass, old_class_type):
res = []
_safe_search_bases(klass, res)
return res
else:
msg = "doesn't apply to '%s' object"
raise TypeError(msg % _typeof(klass).__name__)
def _check_instance(obj, attr):
try:
instance_dict = object.__getattribute__(obj, "__dict__")
except AttributeError:
instance_dict = {}
return dict.get(instance_dict, attr, _sentinel)
def _check_class(klass, attr):
for entry in _static_getmro(klass):
if _shadowed_dict(type(entry)) is _sentinel:
try:
return entry.__dict__[attr]
except KeyError:
pass
return _sentinel
def _is_type(obj):
try:
_static_getmro(obj)
return True
except StandardException:
return False
def _shadowed_dict(klass):
if isinstance(klass, type):
dict_get = type.__dict__["__dict__"].__get__
else:
dict_get = lambda item: {'__dict__': item.__dict__}
for entry in _static_getmro(klass):
try:
class_dict = dict_get(entry)["__dict__"]
except KeyError:
pass
else:
_desc = isinstance(class_dict, types.GetSetDescriptorType)
if (not _desc and class_dict.__name__ == "__dict__" and
class_dict.__objclass__ is entry):
return class_dict
return _sentinel
def _is_descriptor(klass_result):
_ktype = type(klass_result)
return (_check_class(_ktype, '__get__') is not _sentinel and
_check_class(_ktype, '__set__') is not _sentinel)
[docs] def getattr_static(obj, attr, default=_sentinel):
"""Retrieve attributes without triggering dynamic lookup via the
descriptor protocol, __getattr__ or __getattribute__.
Note: this function may not be able to retrieve all attributes
that getattr can fetch (like dynamically created attributes)
and may find attributes that getattr can't (like descriptors
that raise AttributeError). It can also return descriptor objects
instead of instance members in some cases. See the
documentation for details.
"""
instance_result = _sentinel
if not _is_type(obj):
_member = types.MemberDescriptorType
klass = _typeof(obj)
dict_attr = _shadowed_dict(klass)
if dict_attr is _sentinel or isinstance(dict_attr, _member):
instance_result = _check_instance(obj, attr)
else:
klass = obj
klass_result = _check_class(klass, attr)
if (instance_result is not _sentinel and
klass_result is not _sentinel and
_is_descriptor(klass_result)):
return klass_result
elif instance_result is not _sentinel:
return instance_result
elif klass_result is not _sentinel:
return klass_result
if obj is klass:
if isinstance(obj, type):
# for types we check the metaclass too
meta_result = _check_class(type(klass), attr)
if meta_result is not _sentinel:
return meta_result
elif attr == '__name__':
try:
return klass.__name__
except AttributeError:
pass
if default is not _sentinel:
return default
else:
print('>>>', default)
raise AttributeError(attr)
[docs]def get_attr_value(obj, name, *default):
'''Get a named attribute from an object in a safe way.
Similar to `getattr` but without triggering dynamic look-up via the
descriptor protocol, `__getattr__` or `__getattribute__` by using
`getattr_static`:func:.
'''
from xoutil import Undefined as _undef
from xoutil.tools import get_default
default = get_default(default, _undef)
is_type = isinstance(obj, type)
res = getattr_static(obj, name, _undef)
if isdatadescriptor(res):
try:
owner = type if is_type else type(obj)
res = res.__get__(obj, owner)
except BaseException:
res = _undef
if res is _undef and not is_type:
cls = type(obj)
res = getattr_static(cls, name, _undef)
if isdatadescriptor(res):
try:
res = res.__get__(obj, cls)
except StandardException:
try:
res = res.__get__(cls, type)
except StandardException:
res = _undef
if res is not _undef:
return res
elif default is not _undef:
return default
else:
from xoutil.eight import typeof
msg = "'%s' object has no attribute '%s'"
raise AttributeError(msg % (typeof(obj).__name__, name))
[docs]def type_name(obj, affirm=False):
'''Return the internal name for a type or a callable.
This function is safe. If :param obj: is not an instance of a proper type
then returns the following depending on :param affirm:
- If False returns None.
- If True convert a single object to its type before returns the name, but
if is a tuple, list or set; returns a string with a representation of
contained types.
Examples::
>>> type_name(int)
'int'
>>> type_name(0) is None
True
>>> type_name(0, affirm=True)
'int'
>>> type_name((0, 1.1)) is None
True
>>> type_name((0, 1.1), affirm=True)
'(int, float)'
'''
from xoutil.eight import class_types, string_types
named_types = class_types + (types.FunctionType, types.MethodType)
name = '__name__'
if isinstance(obj, (staticmethod, classmethod)):
fn = get_attr_value(obj, '__func__', None)
if fn:
obj = fn
if isinstance(obj, named_types):
# TODO: Why not use directly `get_attr_value``
try:
res = getattr_static(obj, name, None)
if res:
if isdatadescriptor(res):
res = res.__get__(obj, type)
except BaseException:
res = None
if res is None:
try:
res = obj.__name__
except AttributeError:
res = None
else:
res = None
if res is None:
# TODO: Why not use directly `get_attr_value``
# FIX: Improve and standardize the combination of next code
res = getattr_static(obj, name, None)
if res and isdatadescriptor(res):
res = res.__get__(obj, type(obj))
if isinstance(res, string_types):
return res
elif affirm:
if isinstance(obj, (tuple, list, set)):
if isinstance(obj, tuple):
head, tail = '()'
elif isinstance(obj, list):
head, tail = '[]'
else:
head, tail = '{}'
items = ', '.join(type_name(t, affirm) for t in obj)
return str('%s%s%s' % (head, items, tail))
else:
return type_name(type(obj))
else:
return None
# TODO: Implement a safe version for `attrgetter`
if not getattr(_pm, 'getfullargspec', None):
from xoutil.collections import namedtuple
FullArgSpec = namedtuple(
'FullArgSpec',
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults,'
'annotations'
)
def getfullargspec(func):
import inspect
spec = inspect.getargspec(func)
return FullArgSpec(
spec.args, spec.varargs, spec.keywords, spec.defaults,
None, None, None
)
# get rid of unused global variables
del _pm, _copy_python_module_members