Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

objectmodel.py 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. # Copyright (c) 2016 Claudiu Popa <pcmanticore@gmail.com>
  2. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  3. # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER
  4. """
  5. Data object model, as per https://docs.python.org/3/reference/datamodel.html.
  6. This module describes, at least partially, a data object model for some
  7. of astroid's nodes. The model contains special attributes that nodes such
  8. as functions, classes, modules etc have, such as __doc__, __class__,
  9. __module__ etc, being used when doing attribute lookups over nodes.
  10. For instance, inferring `obj.__class__` will first trigger an inference
  11. of the `obj` variable. If it was succesfully inferred, then an attribute
  12. `__class__ will be looked for in the inferred object. This is the part
  13. where the data model occurs. The model is attached to those nodes
  14. and the lookup mechanism will try to see if attributes such as
  15. `__class__` are defined by the model or not. If they are defined,
  16. the model will be requested to return the corresponding value of that
  17. attribute. Thus the model can be viewed as a special part of the lookup
  18. mechanism.
  19. """
  20. try:
  21. from functools import lru_cache
  22. except ImportError:
  23. from backports.functools_lru_cache import lru_cache
  24. import itertools
  25. import pprint
  26. import os
  27. import types
  28. import six
  29. import astroid
  30. from astroid import context as contextmod
  31. from astroid import exceptions
  32. from astroid import node_classes
  33. def _dunder_dict(instance, attributes):
  34. obj = node_classes.Dict(parent=instance)
  35. # Convert the keys to node strings
  36. keys = [node_classes.Const(value=value, parent=obj)
  37. for value in list(attributes.keys())]
  38. # The original attribute has a list of elements for each key,
  39. # but that is not useful for retrieving the special attribute's value.
  40. # In this case, we're picking the last value from each list.
  41. values = [elem[-1] for elem in attributes.values()]
  42. obj.postinit(list(zip(keys, values)))
  43. return obj
  44. class ObjectModel(object):
  45. def __init__(self):
  46. self._instance = None
  47. def __repr__(self):
  48. result = []
  49. cname = type(self).__name__
  50. string = '%(cname)s(%(fields)s)'
  51. alignment = len(cname) + 1
  52. for field in sorted(self.attributes()):
  53. width = 80 - len(field) - alignment
  54. lines = pprint.pformat(field, indent=2,
  55. width=width).splitlines(True)
  56. inner = [lines[0]]
  57. for line in lines[1:]:
  58. inner.append(' ' * alignment + line)
  59. result.append(field)
  60. return string % {'cname': cname,
  61. 'fields': (',\n' + ' ' * alignment).join(result)}
  62. def __call__(self, instance):
  63. self._instance = instance
  64. return self
  65. def __get__(self, instance, cls=None):
  66. # ObjectModel needs to be a descriptor so that just doing
  67. # `special_attributes = SomeObjectModel` should be enough in the body of a node.
  68. # But at the same time, node.special_attributes should return an object
  69. # which can be used for manipulating the special attributes. That's the reason
  70. # we pass the instance through which it got accessed to ObjectModel.__call__,
  71. # returning itself afterwards, so we can still have access to the
  72. # underlying data model and to the instance for which it got accessed.
  73. return self(instance)
  74. def __contains__(self, name):
  75. return name in self.attributes()
  76. @lru_cache(maxsize=None)
  77. def attributes(self):
  78. """Get the attributes which are exported by this object model."""
  79. return [obj[2:] for obj in dir(self) if obj.startswith('py')]
  80. def lookup(self, name):
  81. """Look up the given *name* in the current model
  82. It should return an AST or an interpreter object,
  83. but if the name is not found, then an AttributeInferenceError will be raised.
  84. """
  85. if name in self.attributes():
  86. return getattr(self, "py" + name)
  87. raise exceptions.AttributeInferenceError(target=self._instance, attribute=name)
  88. class ModuleModel(ObjectModel):
  89. def _builtins(self):
  90. builtins = astroid.MANAGER.astroid_cache[six.moves.builtins.__name__]
  91. return builtins.special_attributes.lookup('__dict__')
  92. if six.PY3:
  93. @property
  94. def pybuiltins(self):
  95. return self._builtins()
  96. else:
  97. @property
  98. def py__builtin__(self):
  99. return self._builtins()
  100. # __path__ is a standard attribute on *packages* not
  101. # non-package modules. The only mention of it in the
  102. # official 2.7 documentation I can find is in the
  103. # tutorial.
  104. @property
  105. def py__path__(self):
  106. if not self._instance.package:
  107. raise exceptions.AttributeInferenceError(target=self._instance,
  108. attribute='__path__')
  109. if isinstance(self._instance.path, list):
  110. path_objs = [
  111. node_classes.Const(value=path, parent=self._instance)
  112. for path in self._instance.path
  113. ]
  114. else:
  115. path = os.path.dirname(self._instance.path)
  116. path_objs = [node_classes.Const(value=path, parent=self._instance)]
  117. container = node_classes.List(parent=self._instance)
  118. container.postinit(path_objs)
  119. return container
  120. @property
  121. def py__name__(self):
  122. return node_classes.Const(value=self._instance.name,
  123. parent=self._instance)
  124. @property
  125. def py__doc__(self):
  126. return node_classes.Const(value=self._instance.doc,
  127. parent=self._instance)
  128. @property
  129. def py__file__(self):
  130. return node_classes.Const(value=self._instance.file,
  131. parent=self._instance)
  132. @property
  133. def py__dict__(self):
  134. return _dunder_dict(self._instance, self._instance.globals)
  135. # __package__ isn't mentioned anywhere outside a PEP:
  136. # https://www.python.org/dev/peps/pep-0366/
  137. @property
  138. def py__package__(self):
  139. if not self._instance.package:
  140. value = ''
  141. else:
  142. value = self._instance.name
  143. return node_classes.Const(value=value, parent=self._instance)
  144. # These are related to the Python 3 implementation of the
  145. # import system,
  146. # https://docs.python.org/3/reference/import.html#import-related-module-attributes
  147. @property
  148. def py__spec__(self):
  149. # No handling for now.
  150. return node_classes.Unknown()
  151. @property
  152. def py__loader__(self):
  153. # No handling for now.
  154. return node_classes.Unknown()
  155. @property
  156. def py__cached__(self):
  157. # No handling for now.
  158. return node_classes.Unknown()
  159. class FunctionModel(ObjectModel):
  160. @property
  161. def py__name__(self):
  162. return node_classes.Const(value=self._instance.name,
  163. parent=self._instance)
  164. @property
  165. def py__doc__(self):
  166. return node_classes.Const(value=self._instance.doc,
  167. parent=self._instance)
  168. @property
  169. def py__qualname__(self):
  170. return node_classes.Const(value=self._instance.qname(),
  171. parent=self._instance)
  172. @property
  173. def py__defaults__(self):
  174. func = self._instance
  175. if not func.args.defaults:
  176. return node_classes.Const(value=None, parent=func)
  177. defaults_obj = node_classes.Tuple(parent=func)
  178. defaults_obj.postinit(func.args.defaults)
  179. return defaults_obj
  180. @property
  181. def py__annotations__(self):
  182. obj = node_classes.Dict(parent=self._instance)
  183. if not self._instance.returns:
  184. returns = None
  185. else:
  186. returns = self._instance.returns
  187. args = self._instance.args
  188. pair_annotations = itertools.chain(
  189. six.moves.zip(args.args or [], args.annotations),
  190. six.moves.zip(args.kwonlyargs, args.kwonlyargs_annotations)
  191. )
  192. annotations = {
  193. arg.name: annotation
  194. for (arg, annotation) in pair_annotations
  195. if annotation
  196. }
  197. if args.varargannotation:
  198. annotations[args.vararg] = args.varargannotation
  199. if args.kwargannotation:
  200. annotations[args.kwarg] = args.kwargannotation
  201. if returns:
  202. annotations['return'] = returns
  203. items = [(node_classes.Const(key, parent=obj), value)
  204. for (key, value) in annotations.items()]
  205. obj.postinit(items)
  206. return obj
  207. @property
  208. def py__dict__(self):
  209. return node_classes.Dict(parent=self._instance)
  210. py__globals__ = py__dict__
  211. @property
  212. def py__kwdefaults__(self):
  213. def _default_args(args, parent):
  214. for arg in args.kwonlyargs:
  215. try:
  216. default = args.default_value(arg.name)
  217. except exceptions.NoDefault:
  218. continue
  219. name = node_classes.Const(arg.name, parent=parent)
  220. yield name, default
  221. args = self._instance.args
  222. obj = node_classes.Dict(parent=self._instance)
  223. defaults = dict(_default_args(args, obj))
  224. obj.postinit(list(defaults.items()))
  225. return obj
  226. @property
  227. def py__module__(self):
  228. return node_classes.Const(self._instance.root().qname())
  229. @property
  230. def py__get__(self):
  231. from astroid import bases
  232. func = self._instance
  233. class DescriptorBoundMethod(bases.BoundMethod):
  234. """Bound method which knows how to understand calling descriptor binding."""
  235. def infer_call_result(self, caller, context=None):
  236. if len(caller.args) != 2:
  237. raise exceptions.InferenceError(
  238. "Invalid arguments for descriptor binding",
  239. target=self, context=context)
  240. context = contextmod.copy_context(context)
  241. cls = next(caller.args[0].infer(context=context))
  242. # Rebuild the original value, but with the parent set as the
  243. # class where it will be bound.
  244. new_func = func.__class__(name=func.name, doc=func.doc,
  245. lineno=func.lineno, col_offset=func.col_offset,
  246. parent=cls)
  247. # pylint: disable=no-member
  248. new_func.postinit(func.args, func.body,
  249. func.decorators, func.returns)
  250. # Build a proper bound method that points to our newly built function.
  251. proxy = bases.UnboundMethod(new_func)
  252. yield bases.BoundMethod(proxy=proxy, bound=cls)
  253. return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
  254. # These are here just for completion.
  255. @property
  256. def py__ne__(self):
  257. return node_classes.Unknown()
  258. py__subclasshook__ = py__ne__
  259. py__str__ = py__ne__
  260. py__sizeof__ = py__ne__
  261. py__setattr__ = py__ne__
  262. py__repr__ = py__ne__
  263. py__reduce__ = py__ne__
  264. py__reduce_ex__ = py__ne__
  265. py__new__ = py__ne__
  266. py__lt__ = py__ne__
  267. py__eq__ = py__ne__
  268. py__gt__ = py__ne__
  269. py__format__ = py__ne__
  270. py__delattr__ = py__ne__
  271. py__getattribute__ = py__ne__
  272. py__hash__ = py__ne__
  273. py__init__ = py__ne__
  274. py__dir__ = py__ne__
  275. py__call__ = py__ne__
  276. py__class__ = py__ne__
  277. py__closure__ = py__ne__
  278. py__code__ = py__ne__
  279. if six.PY2:
  280. pyfunc_name = py__name__
  281. pyfunc_doc = py__doc__
  282. pyfunc_globals = py__globals__
  283. pyfunc_dict = py__dict__
  284. pyfunc_defaults = py__defaults__
  285. pyfunc_code = py__code__
  286. pyfunc_closure = py__closure__
  287. class ClassModel(ObjectModel):
  288. @property
  289. def py__module__(self):
  290. return node_classes.Const(self._instance.root().qname())
  291. @property
  292. def py__name__(self):
  293. return node_classes.Const(self._instance.name)
  294. @property
  295. def py__qualname__(self):
  296. return node_classes.Const(self._instance.qname())
  297. @property
  298. def py__doc__(self):
  299. return node_classes.Const(self._instance.doc)
  300. @property
  301. def py__mro__(self):
  302. if not self._instance.newstyle:
  303. raise exceptions.AttributeInferenceError(target=self._instance,
  304. attribute='__mro__')
  305. mro = self._instance.mro()
  306. obj = node_classes.Tuple(parent=self._instance)
  307. obj.postinit(mro)
  308. return obj
  309. @property
  310. def pymro(self):
  311. if not self._instance.newstyle:
  312. raise exceptions.AttributeInferenceError(target=self._instance,
  313. attribute='mro')
  314. from astroid import bases
  315. other_self = self
  316. # Cls.mro is a method and we need to return one in order to have a proper inference.
  317. # The method we're returning is capable of inferring the underlying MRO though.
  318. class MroBoundMethod(bases.BoundMethod):
  319. def infer_call_result(self, caller, context=None):
  320. yield other_self.py__mro__
  321. implicit_metaclass = self._instance.implicit_metaclass()
  322. mro_method = implicit_metaclass.locals['mro'][0]
  323. return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
  324. @property
  325. def py__bases__(self):
  326. obj = node_classes.Tuple()
  327. context = contextmod.InferenceContext()
  328. elts = list(self._instance._inferred_bases(context))
  329. obj.postinit(elts=elts)
  330. return obj
  331. @property
  332. def py__class__(self):
  333. from astroid import helpers
  334. return helpers.object_type(self._instance)
  335. @property
  336. def py__subclasses__(self):
  337. """Get the subclasses of the underlying class
  338. This looks only in the current module for retrieving the subclasses,
  339. thus it might miss a couple of them.
  340. """
  341. from astroid import bases
  342. from astroid import scoped_nodes
  343. if not self._instance.newstyle:
  344. raise exceptions.AttributeInferenceError(target=self._instance,
  345. attribute='__subclasses__')
  346. qname = self._instance.qname()
  347. root = self._instance.root()
  348. classes = [cls for cls in root.nodes_of_class(scoped_nodes.ClassDef)
  349. if cls != self._instance and cls.is_subtype_of(qname)]
  350. obj = node_classes.List(parent=self._instance)
  351. obj.postinit(classes)
  352. class SubclassesBoundMethod(bases.BoundMethod):
  353. def infer_call_result(self, caller, context=None):
  354. yield obj
  355. implicit_metaclass = self._instance.implicit_metaclass()
  356. subclasses_method = implicit_metaclass.locals['__subclasses__'][0]
  357. return SubclassesBoundMethod(proxy=subclasses_method,
  358. bound=implicit_metaclass)
  359. @property
  360. def py__dict__(self):
  361. return node_classes.Dict(parent=self._instance)
  362. class SuperModel(ObjectModel):
  363. @property
  364. def py__thisclass__(self):
  365. return self._instance.mro_pointer
  366. @property
  367. def py__self_class__(self):
  368. return self._instance._self_class
  369. @property
  370. def py__self__(self):
  371. return self._instance.type
  372. @property
  373. def py__class__(self):
  374. return self._instance._proxied
  375. class UnboundMethodModel(ObjectModel):
  376. @property
  377. def py__class__(self):
  378. from astroid import helpers
  379. return helpers.object_type(self._instance)
  380. @property
  381. def py__func__(self):
  382. return self._instance._proxied
  383. @property
  384. def py__self__(self):
  385. return node_classes.Const(value=None, parent=self._instance)
  386. pyim_func = py__func__
  387. pyim_class = py__class__
  388. pyim_self = py__self__
  389. class BoundMethodModel(FunctionModel):
  390. @property
  391. def py__func__(self):
  392. return self._instance._proxied._proxied
  393. @property
  394. def py__self__(self):
  395. return self._instance.bound
  396. class GeneratorModel(FunctionModel):
  397. def __new__(cls, *args, **kwargs):
  398. # Append the values from the GeneratorType unto this object.
  399. ret = super(GeneratorModel, cls).__new__(cls, *args, **kwargs)
  400. generator = astroid.MANAGER.astroid_cache[six.moves.builtins.__name__]['generator']
  401. for name, values in generator.locals.items():
  402. method = values[0]
  403. patched = lambda cls, meth=method: meth
  404. setattr(type(ret), 'py' + name, property(patched))
  405. return ret
  406. @property
  407. def py__name__(self):
  408. return node_classes.Const(value=self._instance.parent.name,
  409. parent=self._instance)
  410. @property
  411. def py__doc__(self):
  412. return node_classes.Const(value=self._instance.parent.doc,
  413. parent=self._instance)
  414. class InstanceModel(ObjectModel):
  415. @property
  416. def py__class__(self):
  417. return self._instance._proxied
  418. @property
  419. def py__module__(self):
  420. return node_classes.Const(self._instance.root().qname())
  421. @property
  422. def py__doc__(self):
  423. return node_classes.Const(self._instance.doc)
  424. @property
  425. def py__dict__(self):
  426. return _dunder_dict(self._instance, self._instance.instance_attrs)
  427. class ExceptionInstanceModel(InstanceModel):
  428. @property
  429. def pyargs(self):
  430. message = node_classes.Const('')
  431. args = node_classes.Tuple(parent=self._instance)
  432. args.postinit((message, ))
  433. return args
  434. if six.PY3:
  435. # It's available only on Python 3.
  436. @property
  437. def py__traceback__(self):
  438. builtins = astroid.MANAGER.astroid_cache[six.moves.builtins.__name__]
  439. traceback_type = builtins[types.TracebackType.__name__]
  440. return traceback_type.instantiate_class()
  441. if six.PY2:
  442. # It's available only on Python 2.
  443. @property
  444. def pymessage(self):
  445. return node_classes.Const('')
  446. class DictModel(ObjectModel):
  447. @property
  448. def py__class__(self):
  449. return self._instance._proxied
  450. def _generic_dict_attribute(self, obj, name):
  451. """Generate a bound method that can infer the given *obj*."""
  452. class DictMethodBoundMethod(astroid.BoundMethod):
  453. def infer_call_result(self, caller, context=None):
  454. yield obj
  455. meth = next(self._instance._proxied.igetattr(name))
  456. return DictMethodBoundMethod(proxy=meth, bound=self._instance)
  457. @property
  458. def pyitems(self):
  459. elems = []
  460. obj = node_classes.List(parent=self._instance)
  461. for key, value in self._instance.items:
  462. elem = node_classes.Tuple(parent=obj)
  463. elem.postinit((key, value))
  464. elems.append(elem)
  465. obj.postinit(elts=elems)
  466. if six.PY3:
  467. from astroid import objects
  468. obj = objects.DictItems(obj)
  469. return self._generic_dict_attribute(obj, 'items')
  470. @property
  471. def pykeys(self):
  472. keys = [key for (key, _) in self._instance.items]
  473. obj = node_classes.List(parent=self._instance)
  474. obj.postinit(elts=keys)
  475. if six.PY3:
  476. from astroid import objects
  477. obj = objects.DictKeys(obj)
  478. return self._generic_dict_attribute(obj, 'keys')
  479. @property
  480. def pyvalues(self):
  481. values = [value for (_, value) in self._instance.items]
  482. obj = node_classes.List(parent=self._instance)
  483. obj.postinit(values)
  484. if six.PY3:
  485. from astroid import objects
  486. obj = objects.DictValues(obj)
  487. return self._generic_dict_attribute(obj, 'values')