1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
4
5"""
6Data object model, as per https://docs.python.org/3/reference/datamodel.html.
7
8This module describes, at least partially, a data object model for some
9of astroid's nodes. The model contains special attributes that nodes such
10as functions, classes, modules etc have, such as __doc__, __class__,
11__module__ etc, being used when doing attribute lookups over nodes.
12
13For instance, inferring `obj.__class__` will first trigger an inference
14of the `obj` variable. If it was successfully inferred, then an attribute
15`__class__ will be looked for in the inferred object. This is the part
16where the data model occurs. The model is attached to those nodes
17and the lookup mechanism will try to see if attributes such as
18`__class__` are defined by the model or not. If they are defined,
19the model will be requested to return the corresponding value of that
20attribute. Thus the model can be viewed as a special part of the lookup
21mechanism.
22"""
23
24from __future__ import annotations
25
26import itertools
27import os
28import pprint
29import types
30from collections.abc import Iterator
31from functools import lru_cache
32from typing import TYPE_CHECKING, Any, Literal
33
34import astroid
35from astroid import bases, nodes, util
36from astroid.context import InferenceContext, copy_context
37from astroid.exceptions import AttributeInferenceError, InferenceError, NoDefault
38from astroid.manager import AstroidManager
39from astroid.nodes import node_classes
40from astroid.typing import InferenceResult, SuccessfulInferenceResult
41
42if TYPE_CHECKING:
43 from astroid.objects import Property
44
45IMPL_PREFIX = "attr_"
46LEN_OF_IMPL_PREFIX = len(IMPL_PREFIX)
47
48
49def _dunder_dict(instance, attributes):
50 obj = node_classes.Dict(
51 parent=instance,
52 lineno=instance.lineno,
53 col_offset=instance.col_offset,
54 end_lineno=instance.end_lineno,
55 end_col_offset=instance.end_col_offset,
56 )
57
58 # Convert the keys to node strings
59 keys = [
60 node_classes.Const(value=value, parent=obj) for value in list(attributes.keys())
61 ]
62
63 # The original attribute has a list of elements for each key,
64 # but that is not useful for retrieving the special attribute's value.
65 # In this case, we're picking the last value from each list.
66 values = [elem[-1] for elem in attributes.values()]
67
68 obj.postinit(list(zip(keys, values)))
69 return obj
70
71
72def _get_bound_node(model: ObjectModel) -> Any:
73 # TODO: Use isinstance instead of try ... except after _instance has typing
74 try:
75 return model._instance._proxied
76 except AttributeError:
77 return model._instance
78
79
80class ObjectModel:
81 def __init__(self):
82 self._instance = None
83
84 def __repr__(self):
85 result = []
86 cname = type(self).__name__
87 string = "%(cname)s(%(fields)s)"
88 alignment = len(cname) + 1
89 for field in sorted(self.attributes()):
90 width = 80 - len(field) - alignment
91 lines = pprint.pformat(field, indent=2, width=width).splitlines(True)
92
93 inner = [lines[0]]
94 for line in lines[1:]:
95 inner.append(" " * alignment + line)
96 result.append(field)
97
98 return string % {
99 "cname": cname,
100 "fields": (",\n" + " " * alignment).join(result),
101 }
102
103 def __call__(self, instance):
104 self._instance = instance
105 return self
106
107 def __get__(self, instance, cls=None):
108 # ObjectModel needs to be a descriptor so that just doing
109 # `special_attributes = SomeObjectModel` should be enough in the body of a node.
110 # But at the same time, node.special_attributes should return an object
111 # which can be used for manipulating the special attributes. That's the reason
112 # we pass the instance through which it got accessed to ObjectModel.__call__,
113 # returning itself afterwards, so we can still have access to the
114 # underlying data model and to the instance for which it got accessed.
115 return self(instance)
116
117 def __contains__(self, name) -> bool:
118 return name in self.attributes()
119
120 @lru_cache # noqa
121 def attributes(self) -> list[str]:
122 """Get the attributes which are exported by this object model."""
123 return [o[LEN_OF_IMPL_PREFIX:] for o in dir(self) if o.startswith(IMPL_PREFIX)]
124
125 def lookup(self, name):
126 """Look up the given *name* in the current model.
127
128 It should return an AST or an interpreter object,
129 but if the name is not found, then an AttributeInferenceError will be raised.
130 """
131 if name in self.attributes():
132 return getattr(self, IMPL_PREFIX + name)
133 raise AttributeInferenceError(target=self._instance, attribute=name)
134
135 @property
136 def attr___new__(self) -> bases.BoundMethod:
137 """Calling cls.__new__(type) on an object returns an instance of 'type'."""
138 from astroid import builder # pylint: disable=import-outside-toplevel
139
140 node: nodes.FunctionDef = builder.extract_node(
141 """def __new__(self, cls): return cls()"""
142 )
143 # We set the parent as being the ClassDef of 'object' as that
144 # triggers correct inference as a call to __new__ in bases.py
145 node.parent = AstroidManager().builtins_module["object"]
146
147 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
148
149 @property
150 def attr___init__(self) -> bases.BoundMethod:
151 """Calling cls.__init__() normally returns None."""
152 from astroid import builder # pylint: disable=import-outside-toplevel
153
154 # The *args and **kwargs are necessary not to trigger warnings about missing
155 # or extra parameters for '__init__' methods we don't infer correctly.
156 # This BoundMethod is the fallback value for those.
157 node: nodes.FunctionDef = builder.extract_node(
158 """def __init__(self, *args, **kwargs): return None"""
159 )
160 # We set the parent as being the ClassDef of 'object' as that
161 # is where this method originally comes from
162 node.parent = AstroidManager().builtins_module["object"]
163
164 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
165
166
167class ModuleModel(ObjectModel):
168 def _builtins(self):
169 builtins_ast_module = AstroidManager().builtins_module
170 return builtins_ast_module.special_attributes.lookup("__dict__")
171
172 @property
173 def attr_builtins(self):
174 return self._builtins()
175
176 @property
177 def attr___path__(self):
178 if not self._instance.package:
179 raise AttributeInferenceError(target=self._instance, attribute="__path__")
180
181 path_objs = [
182 node_classes.Const(
183 value=(
184 path if not path.endswith("__init__.py") else os.path.dirname(path)
185 ),
186 parent=self._instance,
187 )
188 for path in self._instance.path
189 ]
190
191 container = node_classes.List(parent=self._instance)
192 container.postinit(path_objs)
193
194 return container
195
196 @property
197 def attr___name__(self):
198 return node_classes.Const(value=self._instance.name, parent=self._instance)
199
200 @property
201 def attr___doc__(self):
202 return node_classes.Const(
203 value=getattr(self._instance.doc_node, "value", None),
204 parent=self._instance,
205 )
206
207 @property
208 def attr___file__(self):
209 return node_classes.Const(value=self._instance.file, parent=self._instance)
210
211 @property
212 def attr___dict__(self):
213 return _dunder_dict(self._instance, self._instance.globals)
214
215 @property
216 def attr___package__(self):
217 if not self._instance.package:
218 value = ""
219 else:
220 value = self._instance.name
221
222 return node_classes.Const(value=value, parent=self._instance)
223
224 # These are related to the Python 3 implementation of the
225 # import system,
226 # https://docs.python.org/3/reference/import.html#import-related-module-attributes
227
228 @property
229 def attr___spec__(self):
230 # No handling for now.
231 return node_classes.Unknown()
232
233 @property
234 def attr___loader__(self):
235 # No handling for now.
236 return node_classes.Unknown()
237
238 @property
239 def attr___cached__(self):
240 # No handling for now.
241 return node_classes.Unknown()
242
243
244class FunctionModel(ObjectModel):
245 @property
246 def attr___name__(self):
247 return node_classes.Const(value=self._instance.name, parent=self._instance)
248
249 @property
250 def attr___doc__(self):
251 return node_classes.Const(
252 value=getattr(self._instance.doc_node, "value", None),
253 parent=self._instance,
254 )
255
256 @property
257 def attr___qualname__(self):
258 return node_classes.Const(value=self._instance.qname(), parent=self._instance)
259
260 @property
261 def attr___defaults__(self):
262 func = self._instance
263 if not func.args.defaults:
264 return node_classes.Const(value=None, parent=func)
265
266 defaults_obj = node_classes.Tuple(parent=func)
267 defaults_obj.postinit(func.args.defaults)
268 return defaults_obj
269
270 @property
271 def attr___annotations__(self):
272 obj = node_classes.Dict(
273 parent=self._instance,
274 lineno=self._instance.lineno,
275 col_offset=self._instance.col_offset,
276 end_lineno=self._instance.end_lineno,
277 end_col_offset=self._instance.end_col_offset,
278 )
279
280 if not self._instance.returns:
281 returns = None
282 else:
283 returns = self._instance.returns
284
285 args = self._instance.args
286 pair_annotations = itertools.chain(
287 zip(args.args or [], args.annotations),
288 zip(args.kwonlyargs, args.kwonlyargs_annotations),
289 zip(args.posonlyargs or [], args.posonlyargs_annotations),
290 )
291
292 annotations = {
293 arg.name: annotation for (arg, annotation) in pair_annotations if annotation
294 }
295 if args.varargannotation:
296 annotations[args.vararg] = args.varargannotation
297 if args.kwargannotation:
298 annotations[args.kwarg] = args.kwargannotation
299 if returns:
300 annotations["return"] = returns
301
302 items = [
303 (node_classes.Const(key, parent=obj), value)
304 for (key, value) in annotations.items()
305 ]
306
307 obj.postinit(items)
308 return obj
309
310 @property
311 def attr___dict__(self):
312 return node_classes.Dict(
313 parent=self._instance,
314 lineno=self._instance.lineno,
315 col_offset=self._instance.col_offset,
316 end_lineno=self._instance.end_lineno,
317 end_col_offset=self._instance.end_col_offset,
318 )
319
320 attr___globals__ = attr___dict__
321
322 @property
323 def attr___kwdefaults__(self):
324 def _default_args(args, parent):
325 for arg in args.kwonlyargs:
326 try:
327 default = args.default_value(arg.name)
328 except NoDefault:
329 continue
330
331 name = node_classes.Const(arg.name, parent=parent)
332 yield name, default
333
334 args = self._instance.args
335 obj = node_classes.Dict(
336 parent=self._instance,
337 lineno=self._instance.lineno,
338 col_offset=self._instance.col_offset,
339 end_lineno=self._instance.end_lineno,
340 end_col_offset=self._instance.end_col_offset,
341 )
342 defaults = dict(_default_args(args, obj))
343
344 obj.postinit(list(defaults.items()))
345 return obj
346
347 @property
348 def attr___module__(self):
349 return node_classes.Const(self._instance.root().qname())
350
351 @property
352 def attr___get__(self):
353 func = self._instance
354
355 class DescriptorBoundMethod(bases.BoundMethod):
356 """Bound method which knows how to understand calling descriptor
357 binding.
358 """
359
360 def implicit_parameters(self) -> Literal[0]:
361 # Different than BoundMethod since the signature
362 # is different.
363 return 0
364
365 def infer_call_result(
366 self,
367 caller: SuccessfulInferenceResult | None,
368 context: InferenceContext | None = None,
369 ) -> Iterator[bases.BoundMethod]:
370 if len(caller.args) > 2 or len(caller.args) < 1:
371 raise InferenceError(
372 "Invalid arguments for descriptor binding",
373 target=self,
374 context=context,
375 )
376
377 context = copy_context(context)
378 try:
379 cls = next(caller.args[0].infer(context=context))
380 except StopIteration as e:
381 raise InferenceError(context=context, node=caller.args[0]) from e
382
383 if isinstance(cls, util.UninferableBase):
384 raise InferenceError(
385 "Invalid class inferred", target=self, context=context
386 )
387
388 # For some reason func is a Node that the below
389 # code is not expecting
390 if isinstance(func, bases.BoundMethod):
391 yield func
392 return
393
394 # Rebuild the original value, but with the parent set as the
395 # class where it will be bound.
396 new_func = func.__class__(
397 name=func.name,
398 lineno=func.lineno,
399 col_offset=func.col_offset,
400 parent=func.parent,
401 end_lineno=func.end_lineno,
402 end_col_offset=func.end_col_offset,
403 )
404 # pylint: disable=no-member
405 new_func.postinit(
406 func.args,
407 func.body,
408 func.decorators,
409 func.returns,
410 doc_node=func.doc_node,
411 )
412
413 # Build a proper bound method that points to our newly built function.
414 proxy = bases.UnboundMethod(new_func)
415 yield bases.BoundMethod(proxy=proxy, bound=cls)
416
417 @property
418 def args(self):
419 """Overwrite the underlying args to match those of the underlying func.
420
421 Usually the underlying *func* is a function/method, as in:
422
423 def test(self):
424 pass
425
426 This has only the *self* parameter but when we access test.__get__
427 we get a new object which has two parameters, *self* and *type*.
428 """
429 nonlocal func
430 arguments = astroid.Arguments(
431 parent=func.args.parent, vararg=None, kwarg=None
432 )
433
434 positional_or_keyword_params = func.args.args.copy()
435 positional_or_keyword_params.append(
436 astroid.AssignName(
437 name="type",
438 lineno=0,
439 col_offset=0,
440 parent=arguments,
441 end_lineno=None,
442 end_col_offset=None,
443 )
444 )
445
446 positional_only_params = func.args.posonlyargs.copy()
447
448 arguments.postinit(
449 args=positional_or_keyword_params,
450 posonlyargs=positional_only_params,
451 defaults=[],
452 kwonlyargs=[],
453 kw_defaults=[],
454 annotations=[],
455 kwonlyargs_annotations=[],
456 posonlyargs_annotations=[],
457 )
458 return arguments
459
460 return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
461
462 # These are here just for completion.
463 @property
464 def attr___ne__(self):
465 return node_classes.Unknown()
466
467 attr___subclasshook__ = attr___ne__
468 attr___str__ = attr___ne__
469 attr___sizeof__ = attr___ne__
470 attr___setattr___ = attr___ne__
471 attr___repr__ = attr___ne__
472 attr___reduce__ = attr___ne__
473 attr___reduce_ex__ = attr___ne__
474 attr___lt__ = attr___ne__
475 attr___eq__ = attr___ne__
476 attr___gt__ = attr___ne__
477 attr___format__ = attr___ne__
478 attr___delattr___ = attr___ne__
479 attr___getattribute__ = attr___ne__
480 attr___hash__ = attr___ne__
481 attr___dir__ = attr___ne__
482 attr___call__ = attr___ne__
483 attr___class__ = attr___ne__
484 attr___closure__ = attr___ne__
485 attr___code__ = attr___ne__
486
487
488class ClassModel(ObjectModel):
489 def __init__(self):
490 # Add a context so that inferences called from an instance don't recurse endlessly
491 self.context = InferenceContext()
492
493 super().__init__()
494
495 @property
496 def attr___module__(self):
497 return node_classes.Const(self._instance.root().qname())
498
499 @property
500 def attr___name__(self):
501 return node_classes.Const(self._instance.name)
502
503 @property
504 def attr___qualname__(self):
505 return node_classes.Const(self._instance.qname())
506
507 @property
508 def attr___doc__(self):
509 return node_classes.Const(getattr(self._instance.doc_node, "value", None))
510
511 @property
512 def attr___mro__(self):
513 if not self._instance.newstyle:
514 raise AttributeInferenceError(target=self._instance, attribute="__mro__")
515
516 mro = self._instance.mro()
517 obj = node_classes.Tuple(parent=self._instance)
518 obj.postinit(mro)
519 return obj
520
521 @property
522 def attr_mro(self):
523 if not self._instance.newstyle:
524 raise AttributeInferenceError(target=self._instance, attribute="mro")
525
526 other_self = self
527
528 # Cls.mro is a method and we need to return one in order to have a proper inference.
529 # The method we're returning is capable of inferring the underlying MRO though.
530 class MroBoundMethod(bases.BoundMethod):
531 def infer_call_result(
532 self,
533 caller: SuccessfulInferenceResult | None,
534 context: InferenceContext | None = None,
535 ) -> Iterator[node_classes.Tuple]:
536 yield other_self.attr___mro__
537
538 implicit_metaclass = self._instance.implicit_metaclass()
539 mro_method = implicit_metaclass.locals["mro"][0]
540 return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
541
542 @property
543 def attr___bases__(self):
544 obj = node_classes.Tuple()
545 context = InferenceContext()
546 elts = list(self._instance._inferred_bases(context))
547 obj.postinit(elts=elts)
548 return obj
549
550 @property
551 def attr___class__(self):
552 # pylint: disable=import-outside-toplevel; circular import
553 from astroid import helpers
554
555 return helpers.object_type(self._instance)
556
557 @property
558 def attr___subclasses__(self):
559 """Get the subclasses of the underlying class.
560
561 This looks only in the current module for retrieving the subclasses,
562 thus it might miss a couple of them.
563 """
564 if not self._instance.newstyle:
565 raise AttributeInferenceError(
566 target=self._instance, attribute="__subclasses__"
567 )
568
569 qname = self._instance.qname()
570 root = self._instance.root()
571 classes = [
572 cls
573 for cls in root.nodes_of_class(nodes.ClassDef)
574 if cls != self._instance and cls.is_subtype_of(qname, context=self.context)
575 ]
576
577 obj = node_classes.List(parent=self._instance)
578 obj.postinit(classes)
579
580 class SubclassesBoundMethod(bases.BoundMethod):
581 def infer_call_result(
582 self,
583 caller: SuccessfulInferenceResult | None,
584 context: InferenceContext | None = None,
585 ) -> Iterator[node_classes.List]:
586 yield obj
587
588 implicit_metaclass = self._instance.implicit_metaclass()
589 subclasses_method = implicit_metaclass.locals["__subclasses__"][0]
590 return SubclassesBoundMethod(proxy=subclasses_method, bound=implicit_metaclass)
591
592 @property
593 def attr___dict__(self):
594 return node_classes.Dict(
595 parent=self._instance,
596 lineno=self._instance.lineno,
597 col_offset=self._instance.col_offset,
598 end_lineno=self._instance.end_lineno,
599 end_col_offset=self._instance.end_col_offset,
600 )
601
602 @property
603 def attr___call__(self):
604 """Calling a class A() returns an instance of A."""
605 return self._instance.instantiate_class()
606
607
608class SuperModel(ObjectModel):
609 @property
610 def attr___thisclass__(self):
611 return self._instance.mro_pointer
612
613 @property
614 def attr___self_class__(self):
615 return self._instance._self_class
616
617 @property
618 def attr___self__(self):
619 return self._instance.type
620
621 @property
622 def attr___class__(self):
623 return self._instance._proxied
624
625
626class UnboundMethodModel(ObjectModel):
627 @property
628 def attr___class__(self):
629 # pylint: disable=import-outside-toplevel; circular import
630 from astroid import helpers
631
632 return helpers.object_type(self._instance)
633
634 @property
635 def attr___func__(self):
636 return self._instance._proxied
637
638 @property
639 def attr___self__(self):
640 return node_classes.Const(value=None, parent=self._instance)
641
642 attr_im_func = attr___func__
643 attr_im_class = attr___class__
644 attr_im_self = attr___self__
645
646
647class ContextManagerModel(ObjectModel):
648 """Model for context managers.
649
650 Based on 3.3.9 of the Data Model documentation:
651 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
652 """
653
654 @property
655 def attr___enter__(self) -> bases.BoundMethod:
656 """Representation of the base implementation of __enter__.
657
658 As per Python documentation:
659 Enter the runtime context related to this object. The with statement
660 will bind this method's return value to the target(s) specified in the
661 as clause of the statement, if any.
662 """
663 from astroid import builder # pylint: disable=import-outside-toplevel
664
665 node: nodes.FunctionDef = builder.extract_node("""def __enter__(self): ...""")
666 # We set the parent as being the ClassDef of 'object' as that
667 # is where this method originally comes from
668 node.parent = AstroidManager().builtins_module["object"]
669
670 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
671
672 @property
673 def attr___exit__(self) -> bases.BoundMethod:
674 """Representation of the base implementation of __exit__.
675
676 As per Python documentation:
677 Exit the runtime context related to this object. The parameters describe the
678 exception that caused the context to be exited. If the context was exited
679 without an exception, all three arguments will be None.
680 """
681 from astroid import builder # pylint: disable=import-outside-toplevel
682
683 node: nodes.FunctionDef = builder.extract_node(
684 """def __exit__(self, exc_type, exc_value, traceback): ..."""
685 )
686 # We set the parent as being the ClassDef of 'object' as that
687 # is where this method originally comes from
688 node.parent = AstroidManager().builtins_module["object"]
689
690 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
691
692
693class BoundMethodModel(FunctionModel):
694 @property
695 def attr___func__(self):
696 return self._instance._proxied._proxied
697
698 @property
699 def attr___self__(self):
700 return self._instance.bound
701
702
703class GeneratorModel(FunctionModel, ContextManagerModel):
704 def __new__(cls, *args, **kwargs):
705 # Append the values from the GeneratorType unto this object.
706 ret = super().__new__(cls, *args, **kwargs)
707 generator = AstroidManager().builtins_module["generator"]
708 for name, values in generator.locals.items():
709 method = values[0]
710
711 def patched(cls, meth=method):
712 return meth
713
714 setattr(type(ret), IMPL_PREFIX + name, property(patched))
715
716 return ret
717
718 @property
719 def attr___name__(self):
720 return node_classes.Const(
721 value=self._instance.parent.name, parent=self._instance
722 )
723
724 @property
725 def attr___doc__(self):
726 return node_classes.Const(
727 value=getattr(self._instance.parent.doc_node, "value", None),
728 parent=self._instance,
729 )
730
731
732class AsyncGeneratorModel(GeneratorModel):
733 def __new__(cls, *args, **kwargs):
734 # Append the values from the AGeneratorType unto this object.
735 ret = super().__new__(cls, *args, **kwargs)
736 astroid_builtins = AstroidManager().builtins_module
737 generator = astroid_builtins.get("async_generator")
738 if generator is None:
739 # Make it backward compatible.
740 generator = astroid_builtins.get("generator")
741
742 for name, values in generator.locals.items():
743 method = values[0]
744
745 def patched(cls, meth=method):
746 return meth
747
748 setattr(type(ret), IMPL_PREFIX + name, property(patched))
749
750 return ret
751
752
753class InstanceModel(ObjectModel):
754 @property
755 def attr___class__(self):
756 return self._instance._proxied
757
758 @property
759 def attr___module__(self):
760 return node_classes.Const(self._instance.root().qname())
761
762 @property
763 def attr___doc__(self):
764 return node_classes.Const(getattr(self._instance.doc_node, "value", None))
765
766 @property
767 def attr___dict__(self):
768 return _dunder_dict(self._instance, self._instance.instance_attrs)
769
770
771# Exception instances
772
773
774class ExceptionInstanceModel(InstanceModel):
775 @property
776 def attr_args(self) -> nodes.Tuple:
777 return nodes.Tuple(parent=self._instance)
778
779 @property
780 def attr___traceback__(self):
781 builtins_ast_module = AstroidManager().builtins_module
782 traceback_type = builtins_ast_module[types.TracebackType.__name__]
783 return traceback_type.instantiate_class()
784
785
786class SyntaxErrorInstanceModel(ExceptionInstanceModel):
787 @property
788 def attr_text(self):
789 return node_classes.Const("")
790
791
792class OSErrorInstanceModel(ExceptionInstanceModel):
793 @property
794 def attr_filename(self):
795 return node_classes.Const("")
796
797 @property
798 def attr_errno(self):
799 return node_classes.Const(0)
800
801 @property
802 def attr_strerror(self):
803 return node_classes.Const("")
804
805 attr_filename2 = attr_filename
806
807
808class ImportErrorInstanceModel(ExceptionInstanceModel):
809 @property
810 def attr_name(self):
811 return node_classes.Const("")
812
813 @property
814 def attr_path(self):
815 return node_classes.Const("")
816
817
818class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel):
819 @property
820 def attr_object(self):
821 return node_classes.Const(b"")
822
823
824BUILTIN_EXCEPTIONS = {
825 "builtins.SyntaxError": SyntaxErrorInstanceModel,
826 "builtins.ImportError": ImportErrorInstanceModel,
827 "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
828 # These are all similar to OSError in terms of attributes
829 "builtins.OSError": OSErrorInstanceModel,
830 "builtins.BlockingIOError": OSErrorInstanceModel,
831 "builtins.BrokenPipeError": OSErrorInstanceModel,
832 "builtins.ChildProcessError": OSErrorInstanceModel,
833 "builtins.ConnectionAbortedError": OSErrorInstanceModel,
834 "builtins.ConnectionError": OSErrorInstanceModel,
835 "builtins.ConnectionRefusedError": OSErrorInstanceModel,
836 "builtins.ConnectionResetError": OSErrorInstanceModel,
837 "builtins.FileExistsError": OSErrorInstanceModel,
838 "builtins.FileNotFoundError": OSErrorInstanceModel,
839 "builtins.InterruptedError": OSErrorInstanceModel,
840 "builtins.IsADirectoryError": OSErrorInstanceModel,
841 "builtins.NotADirectoryError": OSErrorInstanceModel,
842 "builtins.PermissionError": OSErrorInstanceModel,
843 "builtins.ProcessLookupError": OSErrorInstanceModel,
844 "builtins.TimeoutError": OSErrorInstanceModel,
845}
846
847
848class DictModel(ObjectModel):
849 @property
850 def attr___class__(self):
851 return self._instance._proxied
852
853 def _generic_dict_attribute(self, obj, name):
854 """Generate a bound method that can infer the given *obj*."""
855
856 class DictMethodBoundMethod(astroid.BoundMethod):
857 def infer_call_result(
858 self,
859 caller: SuccessfulInferenceResult | None,
860 context: InferenceContext | None = None,
861 ) -> Iterator[InferenceResult]:
862 yield obj
863
864 meth = next(self._instance._proxied.igetattr(name), None)
865 return DictMethodBoundMethod(proxy=meth, bound=self._instance)
866
867 @property
868 def attr_items(self):
869 from astroid import objects # pylint: disable=import-outside-toplevel
870
871 elems = []
872 obj = node_classes.List(parent=self._instance)
873 for key, value in self._instance.items:
874 elem = node_classes.Tuple(parent=obj)
875 elem.postinit((key, value))
876 elems.append(elem)
877 obj.postinit(elts=elems)
878
879 items_obj = objects.DictItems(obj)
880 return self._generic_dict_attribute(items_obj, "items")
881
882 @property
883 def attr_keys(self):
884 from astroid import objects # pylint: disable=import-outside-toplevel
885
886 keys = [key for (key, _) in self._instance.items]
887 obj = node_classes.List(parent=self._instance)
888 obj.postinit(elts=keys)
889
890 keys_obj = objects.DictKeys(obj)
891 return self._generic_dict_attribute(keys_obj, "keys")
892
893 @property
894 def attr_values(self):
895 from astroid import objects # pylint: disable=import-outside-toplevel
896
897 values = [value for (_, value) in self._instance.items]
898 obj = node_classes.List(parent=self._instance)
899 obj.postinit(values)
900
901 values_obj = objects.DictValues(obj)
902 return self._generic_dict_attribute(values_obj, "values")
903
904
905class PropertyModel(ObjectModel):
906 """Model for a builtin property."""
907
908 def _init_function(self, name):
909 function = nodes.FunctionDef(
910 name=name,
911 parent=self._instance,
912 lineno=self._instance.lineno,
913 col_offset=self._instance.col_offset,
914 end_lineno=self._instance.end_lineno,
915 end_col_offset=self._instance.end_col_offset,
916 )
917
918 args = nodes.Arguments(parent=function, vararg=None, kwarg=None)
919 args.postinit(
920 args=[],
921 defaults=[],
922 kwonlyargs=[],
923 kw_defaults=[],
924 annotations=[],
925 posonlyargs=[],
926 posonlyargs_annotations=[],
927 kwonlyargs_annotations=[],
928 )
929
930 function.postinit(args=args, body=[])
931 return function
932
933 @property
934 def attr_fget(self):
935 func = self._instance
936
937 class PropertyFuncAccessor(nodes.FunctionDef):
938 def infer_call_result(
939 self,
940 caller: SuccessfulInferenceResult | None,
941 context: InferenceContext | None = None,
942 ) -> Iterator[InferenceResult]:
943 nonlocal func
944 if caller and len(caller.args) != 1:
945 raise InferenceError(
946 "fget() needs a single argument", target=self, context=context
947 )
948
949 yield from func.function.infer_call_result(
950 caller=caller, context=context
951 )
952
953 property_accessor = PropertyFuncAccessor(
954 name="fget",
955 parent=self._instance,
956 lineno=self._instance.lineno,
957 col_offset=self._instance.col_offset,
958 end_lineno=self._instance.end_lineno,
959 end_col_offset=self._instance.end_col_offset,
960 )
961 property_accessor.postinit(args=func.args, body=func.body)
962 return property_accessor
963
964 @property
965 def attr_fset(self):
966 func = self._instance
967
968 def find_setter(func: Property) -> astroid.FunctionDef | None:
969 """
970 Given a property, find the corresponding setter function and returns it.
971
972 :param func: property for which the setter has to be found
973 :return: the setter function or None
974 """
975 for target in [
976 t for t in func.parent.get_children() if t.name == func.function.name
977 ]:
978 for dec_name in target.decoratornames():
979 if dec_name.endswith(func.function.name + ".setter"):
980 return target
981 return None
982
983 func_setter = find_setter(func)
984 if not func_setter:
985 raise InferenceError(
986 f"Unable to find the setter of property {func.function.name}"
987 )
988
989 class PropertyFuncAccessor(nodes.FunctionDef):
990 def infer_call_result(
991 self,
992 caller: SuccessfulInferenceResult | None,
993 context: InferenceContext | None = None,
994 ) -> Iterator[InferenceResult]:
995 nonlocal func_setter
996 if caller and len(caller.args) != 2:
997 raise InferenceError(
998 "fset() needs two arguments", target=self, context=context
999 )
1000 yield from func_setter.infer_call_result(caller=caller, context=context)
1001
1002 property_accessor = PropertyFuncAccessor(
1003 name="fset",
1004 parent=self._instance,
1005 lineno=self._instance.lineno,
1006 col_offset=self._instance.col_offset,
1007 end_lineno=self._instance.end_lineno,
1008 end_col_offset=self._instance.end_col_offset,
1009 )
1010 property_accessor.postinit(args=func_setter.args, body=func_setter.body)
1011 return property_accessor
1012
1013 @property
1014 def attr_setter(self):
1015 return self._init_function("setter")
1016
1017 @property
1018 def attr_deleter(self):
1019 return self._init_function("deleter")
1020
1021 @property
1022 def attr_getter(self):
1023 return self._init_function("getter")
1024
1025 # pylint: enable=import-outside-toplevel