123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- # Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
- # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
- # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
- """
- Data object model, as per https://docs.python.org/3/reference/datamodel.html.
-
- This module describes, at least partially, a data object model for some
- of astroid's nodes. The model contains special attributes that nodes such
- as functions, classes, modules etc have, such as __doc__, __class__,
- __module__ etc, being used when doing attribute lookups over nodes.
-
- For instance, inferring `obj.__class__` will first trigger an inference
- of the `obj` variable. If it was succesfully inferred, then an attribute
- `__class__ will be looked for in the inferred object. This is the part
- where the data model occurs. The model is attached to those nodes
- and the lookup mechanism will try to see if attributes such as
- `__class__` are defined by the model or not. If they are defined,
- the model will be requested to return the corresponding value of that
- attribute. Thus the model can be viewed as a special part of the lookup
- mechanism.
- """
-
- try:
- from functools import lru_cache
- except ImportError:
- from backports.functools_lru_cache import lru_cache
-
- import itertools
- import pprint
- import os
- import types
-
- import six
-
- import astroid
- from astroid import context as contextmod
- from astroid import exceptions
- from astroid import node_classes
-
-
- def _dunder_dict(instance, attributes):
- obj = node_classes.Dict(parent=instance)
-
- # Convert the keys to node strings
- keys = [node_classes.Const(value=value, parent=obj)
- for value in list(attributes.keys())]
-
- # The original attribute has a list of elements for each key,
- # but that is not useful for retrieving the special attribute's value.
- # In this case, we're picking the last value from each list.
- values = [elem[-1] for elem in attributes.values()]
-
- obj.postinit(list(zip(keys, values)))
- return obj
-
-
- class ObjectModel(object):
-
- def __init__(self):
- self._instance = None
-
- def __repr__(self):
- result = []
- cname = type(self).__name__
- string = '%(cname)s(%(fields)s)'
- alignment = len(cname) + 1
- for field in sorted(self.attributes()):
- width = 80 - len(field) - alignment
- lines = pprint.pformat(field, indent=2,
- width=width).splitlines(True)
-
- inner = [lines[0]]
- for line in lines[1:]:
- inner.append(' ' * alignment + line)
- result.append(field)
-
- return string % {'cname': cname,
- 'fields': (',\n' + ' ' * alignment).join(result)}
-
- def __call__(self, instance):
- self._instance = instance
- return self
-
- def __get__(self, instance, cls=None):
- # ObjectModel needs to be a descriptor so that just doing
- # `special_attributes = SomeObjectModel` should be enough in the body of a node.
- # But at the same time, node.special_attributes should return an object
- # which can be used for manipulating the special attributes. That's the reason
- # we pass the instance through which it got accessed to ObjectModel.__call__,
- # returning itself afterwards, so we can still have access to the
- # underlying data model and to the instance for which it got accessed.
- return self(instance)
-
- def __contains__(self, name):
- return name in self.attributes()
-
- @lru_cache(maxsize=None)
- def attributes(self):
- """Get the attributes which are exported by this object model."""
- return [obj[2:] for obj in dir(self) if obj.startswith('py')]
-
- def lookup(self, name):
- """Look up the given *name* in the current model
-
- It should return an AST or an interpreter object,
- but if the name is not found, then an AttributeInferenceError will be raised.
- """
-
- if name in self.attributes():
- return getattr(self, "py" + name)
- raise exceptions.AttributeInferenceError(target=self._instance, attribute=name)
-
-
- class ModuleModel(ObjectModel):
-
- def _builtins(self):
- builtins = astroid.MANAGER.astroid_cache[six.moves.builtins.__name__]
- return builtins.special_attributes.lookup('__dict__')
-
- if six.PY3:
- @property
- def pybuiltins(self):
- return self._builtins()
-
- else:
- @property
- def py__builtin__(self):
- return self._builtins()
-
- # __path__ is a standard attribute on *packages* not
- # non-package modules. The only mention of it in the
- # official 2.7 documentation I can find is in the
- # tutorial.
-
- @property
- def py__path__(self):
- if not self._instance.package:
- raise exceptions.AttributeInferenceError(target=self._instance,
- attribute='__path__')
-
- if isinstance(self._instance.path, list):
- path_objs = [
- node_classes.Const(value=path, parent=self._instance)
- for path in self._instance.path
- ]
- else:
- path = os.path.dirname(self._instance.path)
- path_objs = [node_classes.Const(value=path, parent=self._instance)]
-
- container = node_classes.List(parent=self._instance)
- container.postinit(path_objs)
-
- return container
-
- @property
- def py__name__(self):
- return node_classes.Const(value=self._instance.name,
- parent=self._instance)
-
- @property
- def py__doc__(self):
- return node_classes.Const(value=self._instance.doc,
- parent=self._instance)
-
- @property
- def py__file__(self):
- return node_classes.Const(value=self._instance.file,
- parent=self._instance)
-
- @property
- def py__dict__(self):
- return _dunder_dict(self._instance, self._instance.globals)
-
- # __package__ isn't mentioned anywhere outside a PEP:
- # https://www.python.org/dev/peps/pep-0366/
- @property
- def py__package__(self):
- if not self._instance.package:
- value = ''
- else:
- value = self._instance.name
-
- return node_classes.Const(value=value, parent=self._instance)
-
- # These are related to the Python 3 implementation of the
- # import system,
- # https://docs.python.org/3/reference/import.html#import-related-module-attributes
-
- @property
- def py__spec__(self):
- # No handling for now.
- return node_classes.Unknown()
-
- @property
- def py__loader__(self):
- # No handling for now.
- return node_classes.Unknown()
-
- @property
- def py__cached__(self):
- # No handling for now.
- return node_classes.Unknown()
-
-
- class FunctionModel(ObjectModel):
-
- @property
- def py__name__(self):
- return node_classes.Const(value=self._instance.name,
- parent=self._instance)
-
- @property
- def py__doc__(self):
- return node_classes.Const(value=self._instance.doc,
- parent=self._instance)
-
- @property
- def py__qualname__(self):
- return node_classes.Const(value=self._instance.qname(),
- parent=self._instance)
-
- @property
- def py__defaults__(self):
- func = self._instance
- if not func.args.defaults:
- return node_classes.Const(value=None, parent=func)
-
- defaults_obj = node_classes.Tuple(parent=func)
- defaults_obj.postinit(func.args.defaults)
- return defaults_obj
-
- @property
- def py__annotations__(self):
- obj = node_classes.Dict(parent=self._instance)
-
- if not self._instance.returns:
- returns = None
- else:
- returns = self._instance.returns
-
- args = self._instance.args
- pair_annotations = itertools.chain(
- six.moves.zip(args.args or [], args.annotations),
- six.moves.zip(args.kwonlyargs, args.kwonlyargs_annotations)
- )
-
- annotations = {
- arg.name: annotation
- for (arg, annotation) in pair_annotations
- if annotation
- }
- if args.varargannotation:
- annotations[args.vararg] = args.varargannotation
- if args.kwargannotation:
- annotations[args.kwarg] = args.kwargannotation
- if returns:
- annotations['return'] = returns
-
- items = [(node_classes.Const(key, parent=obj), value)
- for (key, value) in annotations.items()]
-
- obj.postinit(items)
- return obj
-
- @property
- def py__dict__(self):
- return node_classes.Dict(parent=self._instance)
-
- py__globals__ = py__dict__
-
- @property
- def py__kwdefaults__(self):
-
- def _default_args(args, parent):
- for arg in args.kwonlyargs:
- try:
- default = args.default_value(arg.name)
- except exceptions.NoDefault:
- continue
-
- name = node_classes.Const(arg.name, parent=parent)
- yield name, default
-
- args = self._instance.args
- obj = node_classes.Dict(parent=self._instance)
- defaults = dict(_default_args(args, obj))
-
- obj.postinit(list(defaults.items()))
- return obj
-
- @property
- def py__module__(self):
- return node_classes.Const(self._instance.root().qname())
-
- @property
- def py__get__(self):
- from astroid import bases
-
- func = self._instance
-
- class DescriptorBoundMethod(bases.BoundMethod):
- """Bound method which knows how to understand calling descriptor binding."""
- def infer_call_result(self, caller, context=None):
- if len(caller.args) != 2:
- raise exceptions.InferenceError(
- "Invalid arguments for descriptor binding",
- target=self, context=context)
-
- context = contextmod.copy_context(context)
- cls = next(caller.args[0].infer(context=context))
-
- # Rebuild the original value, but with the parent set as the
- # class where it will be bound.
- new_func = func.__class__(name=func.name, doc=func.doc,
- lineno=func.lineno, col_offset=func.col_offset,
- parent=cls)
- # pylint: disable=no-member
- new_func.postinit(func.args, func.body,
- func.decorators, func.returns)
-
- # Build a proper bound method that points to our newly built function.
- proxy = bases.UnboundMethod(new_func)
- yield bases.BoundMethod(proxy=proxy, bound=cls)
-
- return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
-
- # These are here just for completion.
- @property
- def py__ne__(self):
- return node_classes.Unknown()
-
- py__subclasshook__ = py__ne__
- py__str__ = py__ne__
- py__sizeof__ = py__ne__
- py__setattr__ = py__ne__
- py__repr__ = py__ne__
- py__reduce__ = py__ne__
- py__reduce_ex__ = py__ne__
- py__new__ = py__ne__
- py__lt__ = py__ne__
- py__eq__ = py__ne__
- py__gt__ = py__ne__
- py__format__ = py__ne__
- py__delattr__ = py__ne__
- py__getattribute__ = py__ne__
- py__hash__ = py__ne__
- py__init__ = py__ne__
- py__dir__ = py__ne__
- py__call__ = py__ne__
- py__class__ = py__ne__
- py__closure__ = py__ne__
- py__code__ = py__ne__
-
- if six.PY2:
- pyfunc_name = py__name__
- pyfunc_doc = py__doc__
- pyfunc_globals = py__globals__
- pyfunc_dict = py__dict__
- pyfunc_defaults = py__defaults__
- pyfunc_code = py__code__
- pyfunc_closure = py__closure__
-
-
- class ClassModel(ObjectModel):
-
- @property
- def py__module__(self):
- return node_classes.Const(self._instance.root().qname())
-
- @property
- def py__name__(self):
- return node_classes.Const(self._instance.name)
-
- @property
- def py__qualname__(self):
- return node_classes.Const(self._instance.qname())
-
- @property
- def py__doc__(self):
- return node_classes.Const(self._instance.doc)
-
- @property
- def py__mro__(self):
- if not self._instance.newstyle:
- raise exceptions.AttributeInferenceError(target=self._instance,
- attribute='__mro__')
-
- mro = self._instance.mro()
- obj = node_classes.Tuple(parent=self._instance)
- obj.postinit(mro)
- return obj
-
- @property
- def pymro(self):
- if not self._instance.newstyle:
- raise exceptions.AttributeInferenceError(target=self._instance,
- attribute='mro')
-
- from astroid import bases
-
- other_self = self
-
- # Cls.mro is a method and we need to return one in order to have a proper inference.
- # The method we're returning is capable of inferring the underlying MRO though.
- class MroBoundMethod(bases.BoundMethod):
- def infer_call_result(self, caller, context=None):
- yield other_self.py__mro__
-
- implicit_metaclass = self._instance.implicit_metaclass()
- mro_method = implicit_metaclass.locals['mro'][0]
- return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
-
- @property
- def py__bases__(self):
- obj = node_classes.Tuple()
- context = contextmod.InferenceContext()
- elts = list(self._instance._inferred_bases(context))
- obj.postinit(elts=elts)
- return obj
-
- @property
- def py__class__(self):
- from astroid import helpers
- return helpers.object_type(self._instance)
-
- @property
- def py__subclasses__(self):
- """Get the subclasses of the underlying class
-
- This looks only in the current module for retrieving the subclasses,
- thus it might miss a couple of them.
- """
- from astroid import bases
- from astroid import scoped_nodes
-
- if not self._instance.newstyle:
- raise exceptions.AttributeInferenceError(target=self._instance,
- attribute='__subclasses__')
-
- qname = self._instance.qname()
- root = self._instance.root()
- classes = [cls for cls in root.nodes_of_class(scoped_nodes.ClassDef)
- if cls != self._instance and cls.is_subtype_of(qname)]
-
- obj = node_classes.List(parent=self._instance)
- obj.postinit(classes)
-
- class SubclassesBoundMethod(bases.BoundMethod):
- def infer_call_result(self, caller, context=None):
- yield obj
-
- implicit_metaclass = self._instance.implicit_metaclass()
- subclasses_method = implicit_metaclass.locals['__subclasses__'][0]
- return SubclassesBoundMethod(proxy=subclasses_method,
- bound=implicit_metaclass)
-
- @property
- def py__dict__(self):
- return node_classes.Dict(parent=self._instance)
-
-
- class SuperModel(ObjectModel):
-
- @property
- def py__thisclass__(self):
- return self._instance.mro_pointer
-
- @property
- def py__self_class__(self):
- return self._instance._self_class
-
- @property
- def py__self__(self):
- return self._instance.type
-
- @property
- def py__class__(self):
- return self._instance._proxied
-
-
- class UnboundMethodModel(ObjectModel):
-
- @property
- def py__class__(self):
- from astroid import helpers
- return helpers.object_type(self._instance)
-
- @property
- def py__func__(self):
- return self._instance._proxied
-
- @property
- def py__self__(self):
- return node_classes.Const(value=None, parent=self._instance)
-
- pyim_func = py__func__
- pyim_class = py__class__
- pyim_self = py__self__
-
-
- class BoundMethodModel(FunctionModel):
-
- @property
- def py__func__(self):
- return self._instance._proxied._proxied
-
- @property
- def py__self__(self):
- return self._instance.bound
-
-
- class GeneratorModel(FunctionModel):
-
- def __new__(cls, *args, **kwargs):
- # Append the values from the GeneratorType unto this object.
- ret = super(GeneratorModel, cls).__new__(cls, *args, **kwargs)
- generator = astroid.MANAGER.astroid_cache[six.moves.builtins.__name__]['generator']
- for name, values in generator.locals.items():
- method = values[0]
- patched = lambda cls, meth=method: meth
-
- setattr(type(ret), 'py' + name, property(patched))
-
- return ret
-
- @property
- def py__name__(self):
- return node_classes.Const(value=self._instance.parent.name,
- parent=self._instance)
-
- @property
- def py__doc__(self):
- return node_classes.Const(value=self._instance.parent.doc,
- parent=self._instance)
-
-
- class InstanceModel(ObjectModel):
-
- @property
- def py__class__(self):
- return self._instance._proxied
-
- @property
- def py__module__(self):
- return node_classes.Const(self._instance.root().qname())
-
- @property
- def py__doc__(self):
- return node_classes.Const(self._instance.doc)
-
- @property
- def py__dict__(self):
- return _dunder_dict(self._instance, self._instance.instance_attrs)
-
-
- class ExceptionInstanceModel(InstanceModel):
-
- @property
- def pyargs(self):
- message = node_classes.Const('')
- args = node_classes.Tuple(parent=self._instance)
- args.postinit((message, ))
- return args
-
- if six.PY3:
- # It's available only on Python 3.
-
- @property
- def py__traceback__(self):
- builtins = astroid.MANAGER.astroid_cache[six.moves.builtins.__name__]
- traceback_type = builtins[types.TracebackType.__name__]
- return traceback_type.instantiate_class()
-
- if six.PY2:
- # It's available only on Python 2.
-
- @property
- def pymessage(self):
- return node_classes.Const('')
-
-
- class DictModel(ObjectModel):
-
- @property
- def py__class__(self):
- return self._instance._proxied
-
- def _generic_dict_attribute(self, obj, name):
- """Generate a bound method that can infer the given *obj*."""
-
- class DictMethodBoundMethod(astroid.BoundMethod):
- def infer_call_result(self, caller, context=None):
- yield obj
-
- meth = next(self._instance._proxied.igetattr(name))
- return DictMethodBoundMethod(proxy=meth, bound=self._instance)
-
- @property
- def pyitems(self):
- elems = []
- obj = node_classes.List(parent=self._instance)
- for key, value in self._instance.items:
- elem = node_classes.Tuple(parent=obj)
- elem.postinit((key, value))
- elems.append(elem)
- obj.postinit(elts=elems)
-
- if six.PY3:
- from astroid import objects
- obj = objects.DictItems(obj)
-
- return self._generic_dict_attribute(obj, 'items')
-
- @property
- def pykeys(self):
- keys = [key for (key, _) in self._instance.items]
- obj = node_classes.List(parent=self._instance)
- obj.postinit(elts=keys)
-
- if six.PY3:
- from astroid import objects
- obj = objects.DictKeys(obj)
-
- return self._generic_dict_attribute(obj, 'keys')
-
- @property
- def pyvalues(self):
-
- values = [value for (_, value) in self._instance.items]
- obj = node_classes.List(parent=self._instance)
- obj.postinit(values)
-
- if six.PY3:
- from astroid import objects
- obj = objects.DictValues(obj)
-
- return self._generic_dict_attribute(obj, 'values')
|