#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Copyright (c) Merchise Autrement [~º/~] and Contributors
# All rights reserved.
#
# This is free software; you can do what the LICENCE file allows you to.
#
'''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 `inspect's <inspect>`:mod: documentation.
'''
from __future__ import (division as _py3_division,
print_function as _py3_print,
absolute_import as _absolute_import)
from inspect import * # noqa
from xoutil.deprecation import deprecate_linked
deprecate_linked()
del deprecate_linked
try:
getfullargspec # noqa
except NameError:
from xoutil.future.collections import namedtuple
FullArgSpec = namedtuple(
'FullArgSpec',
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults,'
'annotations')
[docs] def getfullargspec(func):
import inspect
spec = inspect.getargspec(func)
return FullArgSpec(
spec.args, spec.varargs, spec.keywords, spec.defaults,
None, None, None)
# Some private imports migrated from 'xoutil.eight.inspect' -->
try:
from inspect import _sentinel
except ImportError:
_sentinel = object()
try:
from inspect import _static_getmro
except ImportError:
def _safe_search_bases(cls, accum=None):
# Simulate the "classic class" search order.
accum = [] if accum is None else accum
if cls not in accum:
accum.append(cls)
for base in cls.__bases__:
_safe_search_bases(base, accum)
return accum
def _static_getmro(klass):
'''Get a reasonable method resolution order of a class.
Works well for both old-style and new-style classes.
'''
if isinstance(klass, type):
return type.__dict__['__mro__'].__get__(klass)
else:
try:
from types import ClassType # old class type
if isinstance(klass, ClassType):
return _safe_search_bases(klass)
except ImportError:
# Python 3.1 lacks both _static_getmro and ClassType
pass
from xoutil.eight import type_name
msg = "doesn't apply to '{}' object"
raise TypeError(msg.format(type_name(klass)))
try:
from inspect import _check_instance
except ImportError:
def _check_instance(obj, attr):
try:
instance_dict = object.__getattribute__(obj, "__dict__")
except AttributeError:
instance_dict = {}
return dict.get(instance_dict, attr, _sentinel)
try:
from inspect import _check_class
except ImportError:
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
try:
from inspect import _is_type
except ImportError:
def _is_type(obj):
try:
_static_getmro(obj)
return True
except TypeError:
return False
try:
from inspect import _shadowed_dict
except ImportError:
def _objclass(class_dict, entry):
try:
# TODO: Look implementation of `inspect.classify_class_attrs`.
# Extend this concept to understand the inner type of any class or
# instance attribute.
return class_dict.__objclass__ is entry
except AttributeError:
# FIX: to avoid error in pypy
return True
def _shadowed_dict(klass):
def dict_get(item):
if isinstance(item, type):
return type.__dict__["__dict__"].__get__(item)
else:
return {'__dict__': item.__dict__}
for entry in _static_getmro(klass):
try:
class_dict = dict_get(entry)["__dict__"]
except KeyError:
pass
else:
from xoutil.future.types import GetSetDescriptorType
if not (type(class_dict) is GetSetDescriptorType and
class_dict.__name__ == "__dict__" and
_objclass(class_dict, entry)):
return class_dict
return _sentinel
try:
getattr_static # noqa
except NameError:
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.
'''
from xoutil.eight import typeof
instance_result = _sentinel
if not _is_type(obj):
from xoutil.future.types import MemberDescriptorType as mdt
klass = typeof(obj)
dict_attr = _shadowed_dict(klass)
if dict_attr is _sentinel or type(dict_attr) is mdt:
instance_result = _check_instance(obj, attr)
else:
klass = obj
klass_result = _check_class(klass, attr)
ires = instance_result is not _sentinel
kres = klass_result is not _sentinel
if ires and kres and _is_descriptor(klass_result):
return klass_result
elif ires:
return instance_result
elif kres:
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:
raise AttributeError(attr)
# <-- end of section migrated from 'xoutil.eight.inspect'
[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.params import check_default, Undefined
default = check_default()(*default)
is_type = isinstance(obj, type)
res = getattr_static(obj, name, Undefined)
if isdatadescriptor(res): # noqa
try:
owner = type if is_type else type(obj)
res = res.__get__(obj, owner)
except Exception: # TODO: @med Which expections.
res = Undefined
if res is Undefined and not is_type:
cls = type(obj)
res = getattr_static(cls, name, Undefined)
if isdatadescriptor(res): # noqa
try:
res = res.__get__(obj, cls)
except Exception: # TODO: @med Which?
try:
res = res.__get__(cls, type)
except Exception: # TODO: @med Which?
res = Undefined
if res is not Undefined:
return res
elif default is not Undefined:
return default
else:
from xoutil.eight import type_name
msg = "'%s' object has no attribute '%s'"
raise AttributeError(msg % (type_name(obj), name))
def safe_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::
>>> safe_name(int)
'int'
>>> safe_name(0) is None
True
>>> safe_name(0, affirm=True)
'int'
>>> safe_name((0, 1.1)) is None
True
>>> safe_name((0, 1.1), affirm=True)
'(int, float)'
'''
from xoutil.eight import class_types, string_types
from types import FunctionType, MethodType
from types import BuiltinFunctionType, BuiltinMethodType
named_types = class_types + (FunctionType, MethodType,
BuiltinFunctionType, BuiltinMethodType)
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): # noqa
res = res.__get__(obj, type)
except Exception:
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): # noqa
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(safe_name(t, affirm) for t in obj)
return str('%s%s%s' % (head, items, tail))
else:
return safe_name(type(obj))
else:
return None
from xoutil.deprecation import deprecated # noqa
type_name = deprecated(safe_name, removed_in_version='1.8.1')(safe_name)
del deprecated
def _static_issubclass(C, B):
'''like ``issubclass(C, B) -> bool`` but without using ABCs.
Return whether class C is a strict subclass (i.e., a derived class) of
class B.
When using a tuple as the second argument it's a shortcut for::
any(_static_issubclass(C, b) for b in B)
This function returns False instead raising "TypeError: issubclass() arg 2
must be a class or tuple of classes" if `B` any tuple member) is not
instance of `type`.
'''
mro = _static_getmro(C)
if isinstance(B, tuple):
return any(b in mro for b in B)
else:
return B in mro