objectmodel.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. # Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
  2. # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
  3. # Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
  4. # Copyright (c) 2017 Ceridwen <ceridwenv@gmail.com>
  5. # Copyright (c) 2017 Calen Pennington <cale@edx.org>
  6. # Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
  7. # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
  8. # Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
  9. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  10. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  11. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  12. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  13. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  14. """
  15. Data object model, as per https://docs.python.org/3/reference/datamodel.html.
  16. This module describes, at least partially, a data object model for some
  17. of astroid's nodes. The model contains special attributes that nodes such
  18. as functions, classes, modules etc have, such as __doc__, __class__,
  19. __module__ etc, being used when doing attribute lookups over nodes.
  20. For instance, inferring `obj.__class__` will first trigger an inference
  21. of the `obj` variable. If it was successfully inferred, then an attribute
  22. `__class__ will be looked for in the inferred object. This is the part
  23. where the data model occurs. The model is attached to those nodes
  24. and the lookup mechanism will try to see if attributes such as
  25. `__class__` are defined by the model or not. If they are defined,
  26. the model will be requested to return the corresponding value of that
  27. attribute. Thus the model can be viewed as a special part of the lookup
  28. mechanism.
  29. """
  30. import itertools
  31. import os
  32. import pprint
  33. import types
  34. from functools import lru_cache
  35. from typing import TYPE_CHECKING, Optional
  36. import astroid
  37. from astroid import util
  38. from astroid.context import InferenceContext, copy_context
  39. from astroid.exceptions import AttributeInferenceError, InferenceError, NoDefault
  40. from astroid.manager import AstroidManager
  41. from astroid.nodes import node_classes
  42. objects = util.lazy_import("objects")
  43. if TYPE_CHECKING:
  44. from astroid.objects import Property
  45. IMPL_PREFIX = "attr_"
  46. def _dunder_dict(instance, attributes):
  47. obj = node_classes.Dict(parent=instance)
  48. # Convert the keys to node strings
  49. keys = [
  50. node_classes.Const(value=value, parent=obj) for value in list(attributes.keys())
  51. ]
  52. # The original attribute has a list of elements for each key,
  53. # but that is not useful for retrieving the special attribute's value.
  54. # In this case, we're picking the last value from each list.
  55. values = [elem[-1] for elem in attributes.values()]
  56. obj.postinit(list(zip(keys, values)))
  57. return obj
  58. class ObjectModel:
  59. def __init__(self):
  60. self._instance = None
  61. def __repr__(self):
  62. result = []
  63. cname = type(self).__name__
  64. string = "%(cname)s(%(fields)s)"
  65. alignment = len(cname) + 1
  66. for field in sorted(self.attributes()):
  67. width = 80 - len(field) - alignment
  68. lines = pprint.pformat(field, indent=2, width=width).splitlines(True)
  69. inner = [lines[0]]
  70. for line in lines[1:]:
  71. inner.append(" " * alignment + line)
  72. result.append(field)
  73. return string % {
  74. "cname": cname,
  75. "fields": (",\n" + " " * alignment).join(result),
  76. }
  77. def __call__(self, instance):
  78. self._instance = instance
  79. return self
  80. def __get__(self, instance, cls=None):
  81. # ObjectModel needs to be a descriptor so that just doing
  82. # `special_attributes = SomeObjectModel` should be enough in the body of a node.
  83. # But at the same time, node.special_attributes should return an object
  84. # which can be used for manipulating the special attributes. That's the reason
  85. # we pass the instance through which it got accessed to ObjectModel.__call__,
  86. # returning itself afterwards, so we can still have access to the
  87. # underlying data model and to the instance for which it got accessed.
  88. return self(instance)
  89. def __contains__(self, name):
  90. return name in self.attributes()
  91. @lru_cache(maxsize=None)
  92. def attributes(self):
  93. """Get the attributes which are exported by this object model."""
  94. return [
  95. obj[len(IMPL_PREFIX) :] for obj in dir(self) if obj.startswith(IMPL_PREFIX)
  96. ]
  97. def lookup(self, name):
  98. """Look up the given *name* in the current model
  99. It should return an AST or an interpreter object,
  100. but if the name is not found, then an AttributeInferenceError will be raised.
  101. """
  102. if name in self.attributes():
  103. return getattr(self, IMPL_PREFIX + name)
  104. raise AttributeInferenceError(target=self._instance, attribute=name)
  105. class ModuleModel(ObjectModel):
  106. def _builtins(self):
  107. builtins_ast_module = AstroidManager().builtins_module
  108. return builtins_ast_module.special_attributes.lookup("__dict__")
  109. @property
  110. def attr_builtins(self):
  111. return self._builtins()
  112. @property
  113. def attr___path__(self):
  114. if not self._instance.package:
  115. raise AttributeInferenceError(target=self._instance, attribute="__path__")
  116. path_objs = [
  117. node_classes.Const(
  118. value=path
  119. if not path.endswith("__init__.py")
  120. else os.path.dirname(path),
  121. parent=self._instance,
  122. )
  123. for path in self._instance.path
  124. ]
  125. container = node_classes.List(parent=self._instance)
  126. container.postinit(path_objs)
  127. return container
  128. @property
  129. def attr___name__(self):
  130. return node_classes.Const(value=self._instance.name, parent=self._instance)
  131. @property
  132. def attr___doc__(self):
  133. return node_classes.Const(value=self._instance.doc, parent=self._instance)
  134. @property
  135. def attr___file__(self):
  136. return node_classes.Const(value=self._instance.file, parent=self._instance)
  137. @property
  138. def attr___dict__(self):
  139. return _dunder_dict(self._instance, self._instance.globals)
  140. @property
  141. def attr___package__(self):
  142. if not self._instance.package:
  143. value = ""
  144. else:
  145. value = self._instance.name
  146. return node_classes.Const(value=value, parent=self._instance)
  147. # These are related to the Python 3 implementation of the
  148. # import system,
  149. # https://docs.python.org/3/reference/import.html#import-related-module-attributes
  150. @property
  151. def attr___spec__(self):
  152. # No handling for now.
  153. return node_classes.Unknown()
  154. @property
  155. def attr___loader__(self):
  156. # No handling for now.
  157. return node_classes.Unknown()
  158. @property
  159. def attr___cached__(self):
  160. # No handling for now.
  161. return node_classes.Unknown()
  162. class FunctionModel(ObjectModel):
  163. @property
  164. def attr___name__(self):
  165. return node_classes.Const(value=self._instance.name, parent=self._instance)
  166. @property
  167. def attr___doc__(self):
  168. return node_classes.Const(value=self._instance.doc, parent=self._instance)
  169. @property
  170. def attr___qualname__(self):
  171. return node_classes.Const(value=self._instance.qname(), parent=self._instance)
  172. @property
  173. def attr___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 attr___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. zip(args.args or [], args.annotations),
  190. zip(args.kwonlyargs, args.kwonlyargs_annotations),
  191. zip(args.posonlyargs or [], args.posonlyargs_annotations),
  192. )
  193. annotations = {
  194. arg.name: annotation for (arg, annotation) in pair_annotations if annotation
  195. }
  196. if args.varargannotation:
  197. annotations[args.vararg] = args.varargannotation
  198. if args.kwargannotation:
  199. annotations[args.kwarg] = args.kwargannotation
  200. if returns:
  201. annotations["return"] = returns
  202. items = [
  203. (node_classes.Const(key, parent=obj), value)
  204. for (key, value) in annotations.items()
  205. ]
  206. obj.postinit(items)
  207. return obj
  208. @property
  209. def attr___dict__(self):
  210. return node_classes.Dict(parent=self._instance)
  211. attr___globals__ = attr___dict__
  212. @property
  213. def attr___kwdefaults__(self):
  214. def _default_args(args, parent):
  215. for arg in args.kwonlyargs:
  216. try:
  217. default = args.default_value(arg.name)
  218. except NoDefault:
  219. continue
  220. name = node_classes.Const(arg.name, parent=parent)
  221. yield name, default
  222. args = self._instance.args
  223. obj = node_classes.Dict(parent=self._instance)
  224. defaults = dict(_default_args(args, obj))
  225. obj.postinit(list(defaults.items()))
  226. return obj
  227. @property
  228. def attr___module__(self):
  229. return node_classes.Const(self._instance.root().qname())
  230. @property
  231. def attr___get__(self):
  232. # pylint: disable=import-outside-toplevel; circular import
  233. from astroid import bases
  234. func = self._instance
  235. class DescriptorBoundMethod(bases.BoundMethod):
  236. """Bound method which knows how to understand calling descriptor binding."""
  237. def implicit_parameters(self):
  238. # Different than BoundMethod since the signature
  239. # is different.
  240. return 0
  241. def infer_call_result(self, caller, context=None):
  242. if len(caller.args) > 2 or len(caller.args) < 1:
  243. raise InferenceError(
  244. "Invalid arguments for descriptor binding",
  245. target=self,
  246. context=context,
  247. )
  248. context = copy_context(context)
  249. try:
  250. cls = next(caller.args[0].infer(context=context))
  251. except StopIteration as e:
  252. raise InferenceError(context=context, node=caller.args[0]) from e
  253. if cls is astroid.Uninferable:
  254. raise InferenceError(
  255. "Invalid class inferred", target=self, context=context
  256. )
  257. # For some reason func is a Node that the below
  258. # code is not expecting
  259. if isinstance(func, bases.BoundMethod):
  260. yield func
  261. return
  262. # Rebuild the original value, but with the parent set as the
  263. # class where it will be bound.
  264. new_func = func.__class__(
  265. name=func.name,
  266. doc=func.doc,
  267. lineno=func.lineno,
  268. col_offset=func.col_offset,
  269. parent=func.parent,
  270. )
  271. # pylint: disable=no-member
  272. new_func.postinit(func.args, func.body, func.decorators, func.returns)
  273. # Build a proper bound method that points to our newly built function.
  274. proxy = bases.UnboundMethod(new_func)
  275. yield bases.BoundMethod(proxy=proxy, bound=cls)
  276. @property
  277. def args(self):
  278. """Overwrite the underlying args to match those of the underlying func
  279. Usually the underlying *func* is a function/method, as in:
  280. def test(self):
  281. pass
  282. This has only the *self* parameter but when we access test.__get__
  283. we get a new object which has two parameters, *self* and *type*.
  284. """
  285. nonlocal func
  286. positional_or_keyword_params = func.args.args.copy()
  287. positional_or_keyword_params.append(astroid.AssignName(name="type"))
  288. positional_only_params = func.args.posonlyargs.copy()
  289. arguments = astroid.Arguments(parent=func.args.parent)
  290. arguments.postinit(
  291. args=positional_or_keyword_params,
  292. posonlyargs=positional_only_params,
  293. defaults=[],
  294. kwonlyargs=[],
  295. kw_defaults=[],
  296. annotations=[],
  297. )
  298. return arguments
  299. return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
  300. # These are here just for completion.
  301. @property
  302. def attr___ne__(self):
  303. return node_classes.Unknown()
  304. attr___subclasshook__ = attr___ne__
  305. attr___str__ = attr___ne__
  306. attr___sizeof__ = attr___ne__
  307. attr___setattr___ = attr___ne__
  308. attr___repr__ = attr___ne__
  309. attr___reduce__ = attr___ne__
  310. attr___reduce_ex__ = attr___ne__
  311. attr___new__ = attr___ne__
  312. attr___lt__ = attr___ne__
  313. attr___eq__ = attr___ne__
  314. attr___gt__ = attr___ne__
  315. attr___format__ = attr___ne__
  316. attr___delattr___ = attr___ne__
  317. attr___getattribute__ = attr___ne__
  318. attr___hash__ = attr___ne__
  319. attr___init__ = attr___ne__
  320. attr___dir__ = attr___ne__
  321. attr___call__ = attr___ne__
  322. attr___class__ = attr___ne__
  323. attr___closure__ = attr___ne__
  324. attr___code__ = attr___ne__
  325. class ClassModel(ObjectModel):
  326. def __init__(self):
  327. # Add a context so that inferences called from an instance don't recurse endlessly
  328. self.context = InferenceContext()
  329. super().__init__()
  330. @property
  331. def attr___module__(self):
  332. return node_classes.Const(self._instance.root().qname())
  333. @property
  334. def attr___name__(self):
  335. return node_classes.Const(self._instance.name)
  336. @property
  337. def attr___qualname__(self):
  338. return node_classes.Const(self._instance.qname())
  339. @property
  340. def attr___doc__(self):
  341. return node_classes.Const(self._instance.doc)
  342. @property
  343. def attr___mro__(self):
  344. if not self._instance.newstyle:
  345. raise AttributeInferenceError(target=self._instance, attribute="__mro__")
  346. mro = self._instance.mro()
  347. obj = node_classes.Tuple(parent=self._instance)
  348. obj.postinit(mro)
  349. return obj
  350. @property
  351. def attr_mro(self):
  352. if not self._instance.newstyle:
  353. raise AttributeInferenceError(target=self._instance, attribute="mro")
  354. # pylint: disable=import-outside-toplevel; circular import
  355. from astroid import bases
  356. other_self = self
  357. # Cls.mro is a method and we need to return one in order to have a proper inference.
  358. # The method we're returning is capable of inferring the underlying MRO though.
  359. class MroBoundMethod(bases.BoundMethod):
  360. def infer_call_result(self, caller, context=None):
  361. yield other_self.attr___mro__
  362. implicit_metaclass = self._instance.implicit_metaclass()
  363. mro_method = implicit_metaclass.locals["mro"][0]
  364. return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
  365. @property
  366. def attr___bases__(self):
  367. obj = node_classes.Tuple()
  368. context = InferenceContext()
  369. elts = list(self._instance._inferred_bases(context))
  370. obj.postinit(elts=elts)
  371. return obj
  372. @property
  373. def attr___class__(self):
  374. # pylint: disable=import-outside-toplevel; circular import
  375. from astroid import helpers
  376. return helpers.object_type(self._instance)
  377. @property
  378. def attr___subclasses__(self):
  379. """Get the subclasses of the underlying class
  380. This looks only in the current module for retrieving the subclasses,
  381. thus it might miss a couple of them.
  382. """
  383. # pylint: disable=import-outside-toplevel; circular import
  384. from astroid import bases
  385. from astroid.nodes import scoped_nodes
  386. if not self._instance.newstyle:
  387. raise AttributeInferenceError(
  388. target=self._instance, attribute="__subclasses__"
  389. )
  390. qname = self._instance.qname()
  391. root = self._instance.root()
  392. classes = [
  393. cls
  394. for cls in root.nodes_of_class(scoped_nodes.ClassDef)
  395. if cls != self._instance and cls.is_subtype_of(qname, context=self.context)
  396. ]
  397. obj = node_classes.List(parent=self._instance)
  398. obj.postinit(classes)
  399. class SubclassesBoundMethod(bases.BoundMethod):
  400. def infer_call_result(self, caller, context=None):
  401. yield obj
  402. implicit_metaclass = self._instance.implicit_metaclass()
  403. subclasses_method = implicit_metaclass.locals["__subclasses__"][0]
  404. return SubclassesBoundMethod(proxy=subclasses_method, bound=implicit_metaclass)
  405. @property
  406. def attr___dict__(self):
  407. return node_classes.Dict(parent=self._instance)
  408. class SuperModel(ObjectModel):
  409. @property
  410. def attr___thisclass__(self):
  411. return self._instance.mro_pointer
  412. @property
  413. def attr___self_class__(self):
  414. return self._instance._self_class
  415. @property
  416. def attr___self__(self):
  417. return self._instance.type
  418. @property
  419. def attr___class__(self):
  420. return self._instance._proxied
  421. class UnboundMethodModel(ObjectModel):
  422. @property
  423. def attr___class__(self):
  424. # pylint: disable=import-outside-toplevel; circular import
  425. from astroid import helpers
  426. return helpers.object_type(self._instance)
  427. @property
  428. def attr___func__(self):
  429. return self._instance._proxied
  430. @property
  431. def attr___self__(self):
  432. return node_classes.Const(value=None, parent=self._instance)
  433. attr_im_func = attr___func__
  434. attr_im_class = attr___class__
  435. attr_im_self = attr___self__
  436. class BoundMethodModel(FunctionModel):
  437. @property
  438. def attr___func__(self):
  439. return self._instance._proxied._proxied
  440. @property
  441. def attr___self__(self):
  442. return self._instance.bound
  443. class GeneratorModel(FunctionModel):
  444. def __new__(cls, *args, **kwargs):
  445. # Append the values from the GeneratorType unto this object.
  446. ret = super().__new__(cls, *args, **kwargs)
  447. generator = AstroidManager().builtins_module["generator"]
  448. for name, values in generator.locals.items():
  449. method = values[0]
  450. def patched(cls, meth=method):
  451. return meth
  452. setattr(type(ret), IMPL_PREFIX + name, property(patched))
  453. return ret
  454. @property
  455. def attr___name__(self):
  456. return node_classes.Const(
  457. value=self._instance.parent.name, parent=self._instance
  458. )
  459. @property
  460. def attr___doc__(self):
  461. return node_classes.Const(
  462. value=self._instance.parent.doc, parent=self._instance
  463. )
  464. class AsyncGeneratorModel(GeneratorModel):
  465. def __new__(cls, *args, **kwargs):
  466. # Append the values from the AGeneratorType unto this object.
  467. ret = super().__new__(cls, *args, **kwargs)
  468. astroid_builtins = AstroidManager().builtins_module
  469. generator = astroid_builtins.get("async_generator")
  470. if generator is None:
  471. # Make it backward compatible.
  472. generator = astroid_builtins.get("generator")
  473. for name, values in generator.locals.items():
  474. method = values[0]
  475. def patched(cls, meth=method):
  476. return meth
  477. setattr(type(ret), IMPL_PREFIX + name, property(patched))
  478. return ret
  479. class InstanceModel(ObjectModel):
  480. @property
  481. def attr___class__(self):
  482. return self._instance._proxied
  483. @property
  484. def attr___module__(self):
  485. return node_classes.Const(self._instance.root().qname())
  486. @property
  487. def attr___doc__(self):
  488. return node_classes.Const(self._instance.doc)
  489. @property
  490. def attr___dict__(self):
  491. return _dunder_dict(self._instance, self._instance.instance_attrs)
  492. # Exception instances
  493. class ExceptionInstanceModel(InstanceModel):
  494. @property
  495. def attr_args(self):
  496. message = node_classes.Const("")
  497. args = node_classes.Tuple(parent=self._instance)
  498. args.postinit((message,))
  499. return args
  500. @property
  501. def attr___traceback__(self):
  502. builtins_ast_module = AstroidManager().builtins_module
  503. traceback_type = builtins_ast_module[types.TracebackType.__name__]
  504. return traceback_type.instantiate_class()
  505. class SyntaxErrorInstanceModel(ExceptionInstanceModel):
  506. @property
  507. def attr_text(self):
  508. return node_classes.Const("")
  509. class OSErrorInstanceModel(ExceptionInstanceModel):
  510. @property
  511. def attr_filename(self):
  512. return node_classes.Const("")
  513. @property
  514. def attr_errno(self):
  515. return node_classes.Const(0)
  516. @property
  517. def attr_strerror(self):
  518. return node_classes.Const("")
  519. attr_filename2 = attr_filename
  520. class ImportErrorInstanceModel(ExceptionInstanceModel):
  521. @property
  522. def attr_name(self):
  523. return node_classes.Const("")
  524. @property
  525. def attr_path(self):
  526. return node_classes.Const("")
  527. class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel):
  528. @property
  529. def attr_object(self):
  530. return node_classes.Const("")
  531. BUILTIN_EXCEPTIONS = {
  532. "builtins.SyntaxError": SyntaxErrorInstanceModel,
  533. "builtins.ImportError": ImportErrorInstanceModel,
  534. "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
  535. # These are all similar to OSError in terms of attributes
  536. "builtins.OSError": OSErrorInstanceModel,
  537. "builtins.BlockingIOError": OSErrorInstanceModel,
  538. "builtins.BrokenPipeError": OSErrorInstanceModel,
  539. "builtins.ChildProcessError": OSErrorInstanceModel,
  540. "builtins.ConnectionAbortedError": OSErrorInstanceModel,
  541. "builtins.ConnectionError": OSErrorInstanceModel,
  542. "builtins.ConnectionRefusedError": OSErrorInstanceModel,
  543. "builtins.ConnectionResetError": OSErrorInstanceModel,
  544. "builtins.FileExistsError": OSErrorInstanceModel,
  545. "builtins.FileNotFoundError": OSErrorInstanceModel,
  546. "builtins.InterruptedError": OSErrorInstanceModel,
  547. "builtins.IsADirectoryError": OSErrorInstanceModel,
  548. "builtins.NotADirectoryError": OSErrorInstanceModel,
  549. "builtins.PermissionError": OSErrorInstanceModel,
  550. "builtins.ProcessLookupError": OSErrorInstanceModel,
  551. "builtins.TimeoutError": OSErrorInstanceModel,
  552. }
  553. class DictModel(ObjectModel):
  554. @property
  555. def attr___class__(self):
  556. return self._instance._proxied
  557. def _generic_dict_attribute(self, obj, name):
  558. """Generate a bound method that can infer the given *obj*."""
  559. class DictMethodBoundMethod(astroid.BoundMethod):
  560. def infer_call_result(self, caller, context=None):
  561. yield obj
  562. meth = next(self._instance._proxied.igetattr(name), None)
  563. return DictMethodBoundMethod(proxy=meth, bound=self._instance)
  564. @property
  565. def attr_items(self):
  566. elems = []
  567. obj = node_classes.List(parent=self._instance)
  568. for key, value in self._instance.items:
  569. elem = node_classes.Tuple(parent=obj)
  570. elem.postinit((key, value))
  571. elems.append(elem)
  572. obj.postinit(elts=elems)
  573. obj = objects.DictItems(obj)
  574. return self._generic_dict_attribute(obj, "items")
  575. @property
  576. def attr_keys(self):
  577. keys = [key for (key, _) in self._instance.items]
  578. obj = node_classes.List(parent=self._instance)
  579. obj.postinit(elts=keys)
  580. obj = objects.DictKeys(obj)
  581. return self._generic_dict_attribute(obj, "keys")
  582. @property
  583. def attr_values(self):
  584. values = [value for (_, value) in self._instance.items]
  585. obj = node_classes.List(parent=self._instance)
  586. obj.postinit(values)
  587. obj = objects.DictValues(obj)
  588. return self._generic_dict_attribute(obj, "values")
  589. class PropertyModel(ObjectModel):
  590. """Model for a builtin property"""
  591. # pylint: disable=import-outside-toplevel
  592. def _init_function(self, name):
  593. from astroid.nodes.node_classes import Arguments
  594. from astroid.nodes.scoped_nodes import FunctionDef
  595. args = Arguments()
  596. args.postinit(
  597. args=[],
  598. defaults=[],
  599. kwonlyargs=[],
  600. kw_defaults=[],
  601. annotations=[],
  602. posonlyargs=[],
  603. posonlyargs_annotations=[],
  604. kwonlyargs_annotations=[],
  605. )
  606. function = FunctionDef(name=name, parent=self._instance)
  607. function.postinit(args=args, body=[])
  608. return function
  609. @property
  610. def attr_fget(self):
  611. from astroid.nodes.scoped_nodes import FunctionDef
  612. func = self._instance
  613. class PropertyFuncAccessor(FunctionDef):
  614. def infer_call_result(self, caller=None, context=None):
  615. nonlocal func
  616. if caller and len(caller.args) != 1:
  617. raise InferenceError(
  618. "fget() needs a single argument", target=self, context=context
  619. )
  620. yield from func.function.infer_call_result(
  621. caller=caller, context=context
  622. )
  623. property_accessor = PropertyFuncAccessor(name="fget", parent=self._instance)
  624. property_accessor.postinit(args=func.args, body=func.body)
  625. return property_accessor
  626. @property
  627. def attr_fset(self):
  628. from astroid.nodes.scoped_nodes import FunctionDef
  629. func = self._instance
  630. def find_setter(func: "Property") -> Optional[astroid.FunctionDef]:
  631. """
  632. Given a property, find the corresponding setter function and returns it.
  633. :param func: property for which the setter has to be found
  634. :return: the setter function or None
  635. """
  636. for target in [
  637. t for t in func.parent.get_children() if t.name == func.function.name
  638. ]:
  639. for dec_name in target.decoratornames():
  640. if dec_name.endswith(func.function.name + ".setter"):
  641. return target
  642. return None
  643. func_setter = find_setter(func)
  644. if not func_setter:
  645. raise InferenceError(
  646. f"Unable to find the setter of property {func.function.name}"
  647. )
  648. class PropertyFuncAccessor(FunctionDef):
  649. def infer_call_result(self, caller=None, context=None):
  650. nonlocal func_setter
  651. if caller and len(caller.args) != 2:
  652. raise InferenceError(
  653. "fset() needs two arguments", target=self, context=context
  654. )
  655. yield from func_setter.infer_call_result(caller=caller, context=context)
  656. property_accessor = PropertyFuncAccessor(name="fset", parent=self._instance)
  657. property_accessor.postinit(args=func_setter.args, body=func_setter.body)
  658. return property_accessor
  659. @property
  660. def attr_setter(self):
  661. return self._init_function("setter")
  662. @property
  663. def attr_deleter(self):
  664. return self._init_function("deleter")
  665. @property
  666. def attr_getter(self):
  667. return self._init_function("getter")
  668. # pylint: enable=import-outside-toplevel