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___annotations__(self) -> node_classes.Unkown:
497 return node_classes.Unknown()
498
499 @property
500 def attr___module__(self):
501 return node_classes.Const(self._instance.root().qname())
502
503 @property
504 def attr___name__(self):
505 return node_classes.Const(self._instance.name)
506
507 @property
508 def attr___qualname__(self):
509 return node_classes.Const(self._instance.qname())
510
511 @property
512 def attr___doc__(self):
513 return node_classes.Const(getattr(self._instance.doc_node, "value", None))
514
515 @property
516 def attr___mro__(self):
517 mro = self._instance.mro()
518 obj = node_classes.Tuple(parent=self._instance)
519 obj.postinit(mro)
520 return obj
521
522 @property
523 def attr_mro(self):
524 other_self = self
525
526 # Cls.mro is a method and we need to return one in order to have a proper inference.
527 # The method we're returning is capable of inferring the underlying MRO though.
528 class MroBoundMethod(bases.BoundMethod):
529 def infer_call_result(
530 self,
531 caller: SuccessfulInferenceResult | None,
532 context: InferenceContext | None = None,
533 ) -> Iterator[node_classes.Tuple]:
534 yield other_self.attr___mro__
535
536 implicit_metaclass = self._instance.implicit_metaclass()
537 mro_method = implicit_metaclass.locals["mro"][0]
538 return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
539
540 @property
541 def attr___bases__(self):
542 obj = node_classes.Tuple()
543 context = InferenceContext()
544 elts = list(self._instance._inferred_bases(context))
545 obj.postinit(elts=elts)
546 return obj
547
548 @property
549 def attr___class__(self):
550 # pylint: disable=import-outside-toplevel; circular import
551 from astroid import helpers
552
553 return helpers.object_type(self._instance)
554
555 @property
556 def attr___subclasses__(self):
557 """Get the subclasses of the underlying class.
558
559 This looks only in the current module for retrieving the subclasses,
560 thus it might miss a couple of them.
561 """
562
563 qname = self._instance.qname()
564 root = self._instance.root()
565 classes = [
566 cls
567 for cls in root.nodes_of_class(nodes.ClassDef)
568 if cls != self._instance and cls.is_subtype_of(qname, context=self.context)
569 ]
570
571 obj = node_classes.List(parent=self._instance)
572 obj.postinit(classes)
573
574 class SubclassesBoundMethod(bases.BoundMethod):
575 def infer_call_result(
576 self,
577 caller: SuccessfulInferenceResult | None,
578 context: InferenceContext | None = None,
579 ) -> Iterator[node_classes.List]:
580 yield obj
581
582 implicit_metaclass = self._instance.implicit_metaclass()
583 subclasses_method = implicit_metaclass.locals["__subclasses__"][0]
584 return SubclassesBoundMethod(proxy=subclasses_method, bound=implicit_metaclass)
585
586 @property
587 def attr___dict__(self):
588 return node_classes.Dict(
589 parent=self._instance,
590 lineno=self._instance.lineno,
591 col_offset=self._instance.col_offset,
592 end_lineno=self._instance.end_lineno,
593 end_col_offset=self._instance.end_col_offset,
594 )
595
596 @property
597 def attr___call__(self):
598 """Calling a class A() returns an instance of A."""
599 return self._instance.instantiate_class()
600
601
602class SuperModel(ObjectModel):
603 @property
604 def attr___thisclass__(self):
605 return self._instance.mro_pointer
606
607 @property
608 def attr___self_class__(self):
609 return self._instance._self_class
610
611 @property
612 def attr___self__(self):
613 return self._instance.type
614
615 @property
616 def attr___class__(self):
617 return self._instance._proxied
618
619
620class UnboundMethodModel(ObjectModel):
621 @property
622 def attr___class__(self):
623 # pylint: disable=import-outside-toplevel; circular import
624 from astroid import helpers
625
626 return helpers.object_type(self._instance)
627
628 @property
629 def attr___func__(self):
630 return self._instance._proxied
631
632 @property
633 def attr___self__(self):
634 return node_classes.Const(value=None, parent=self._instance)
635
636 attr_im_func = attr___func__
637 attr_im_class = attr___class__
638 attr_im_self = attr___self__
639
640
641class ContextManagerModel(ObjectModel):
642 """Model for context managers.
643
644 Based on 3.3.9 of the Data Model documentation:
645 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
646 """
647
648 @property
649 def attr___enter__(self) -> bases.BoundMethod:
650 """Representation of the base implementation of __enter__.
651
652 As per Python documentation:
653 Enter the runtime context related to this object. The with statement
654 will bind this method's return value to the target(s) specified in the
655 as clause of the statement, if any.
656 """
657 from astroid import builder # pylint: disable=import-outside-toplevel
658
659 node: nodes.FunctionDef = builder.extract_node("""def __enter__(self): ...""")
660 # We set the parent as being the ClassDef of 'object' as that
661 # is where this method originally comes from
662 node.parent = AstroidManager().builtins_module["object"]
663
664 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
665
666 @property
667 def attr___exit__(self) -> bases.BoundMethod:
668 """Representation of the base implementation of __exit__.
669
670 As per Python documentation:
671 Exit the runtime context related to this object. The parameters describe the
672 exception that caused the context to be exited. If the context was exited
673 without an exception, all three arguments will be None.
674 """
675 from astroid import builder # pylint: disable=import-outside-toplevel
676
677 node: nodes.FunctionDef = builder.extract_node(
678 """def __exit__(self, exc_type, exc_value, traceback): ..."""
679 )
680 # We set the parent as being the ClassDef of 'object' as that
681 # is where this method originally comes from
682 node.parent = AstroidManager().builtins_module["object"]
683
684 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
685
686
687class BoundMethodModel(FunctionModel):
688 @property
689 def attr___func__(self):
690 return self._instance._proxied._proxied
691
692 @property
693 def attr___self__(self):
694 return self._instance.bound
695
696
697class GeneratorBaseModel(FunctionModel, ContextManagerModel):
698 def __init__(self, gen_module: nodes.Module):
699 super().__init__()
700 for name, values in gen_module.locals.items():
701 method = values[0]
702 if isinstance(method, nodes.FunctionDef):
703 method = bases.BoundMethod(method, _get_bound_node(self))
704
705 def patched(cls, meth=method):
706 return meth
707
708 setattr(type(self), IMPL_PREFIX + name, property(patched))
709
710 @property
711 def attr___name__(self):
712 return node_classes.Const(
713 value=self._instance.parent.name, parent=self._instance
714 )
715
716 @property
717 def attr___doc__(self):
718 return node_classes.Const(
719 value=getattr(self._instance.parent.doc_node, "value", None),
720 parent=self._instance,
721 )
722
723
724class GeneratorModel(GeneratorBaseModel):
725 def __init__(self):
726 super().__init__(AstroidManager().builtins_module["generator"])
727
728
729class AsyncGeneratorModel(GeneratorBaseModel):
730 def __init__(self):
731 super().__init__(AstroidManager().builtins_module["async_generator"])
732
733
734class InstanceModel(ObjectModel):
735 @property
736 def attr___class__(self):
737 return self._instance._proxied
738
739 @property
740 def attr___module__(self):
741 return node_classes.Const(self._instance.root().qname())
742
743 @property
744 def attr___doc__(self):
745 return node_classes.Const(getattr(self._instance.doc_node, "value", None))
746
747 @property
748 def attr___dict__(self):
749 return _dunder_dict(self._instance, self._instance.instance_attrs)
750
751
752# Exception instances
753
754
755class ExceptionInstanceModel(InstanceModel):
756 @property
757 def attr_args(self) -> nodes.Tuple:
758 return nodes.Tuple(parent=self._instance)
759
760 @property
761 def attr___traceback__(self):
762 builtins_ast_module = AstroidManager().builtins_module
763 traceback_type = builtins_ast_module[types.TracebackType.__name__]
764 return traceback_type.instantiate_class()
765
766
767class SyntaxErrorInstanceModel(ExceptionInstanceModel):
768 @property
769 def attr_text(self):
770 return node_classes.Const("")
771
772
773class OSErrorInstanceModel(ExceptionInstanceModel):
774 @property
775 def attr_filename(self):
776 return node_classes.Const("")
777
778 @property
779 def attr_errno(self):
780 return node_classes.Const(0)
781
782 @property
783 def attr_strerror(self):
784 return node_classes.Const("")
785
786 attr_filename2 = attr_filename
787
788
789class ImportErrorInstanceModel(ExceptionInstanceModel):
790 @property
791 def attr_name(self):
792 return node_classes.Const("")
793
794 @property
795 def attr_path(self):
796 return node_classes.Const("")
797
798
799class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel):
800 @property
801 def attr_object(self):
802 return node_classes.Const(b"")
803
804
805BUILTIN_EXCEPTIONS = {
806 "builtins.SyntaxError": SyntaxErrorInstanceModel,
807 "builtins.ImportError": ImportErrorInstanceModel,
808 "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
809 # These are all similar to OSError in terms of attributes
810 "builtins.OSError": OSErrorInstanceModel,
811 "builtins.BlockingIOError": OSErrorInstanceModel,
812 "builtins.BrokenPipeError": OSErrorInstanceModel,
813 "builtins.ChildProcessError": OSErrorInstanceModel,
814 "builtins.ConnectionAbortedError": OSErrorInstanceModel,
815 "builtins.ConnectionError": OSErrorInstanceModel,
816 "builtins.ConnectionRefusedError": OSErrorInstanceModel,
817 "builtins.ConnectionResetError": OSErrorInstanceModel,
818 "builtins.FileExistsError": OSErrorInstanceModel,
819 "builtins.FileNotFoundError": OSErrorInstanceModel,
820 "builtins.InterruptedError": OSErrorInstanceModel,
821 "builtins.IsADirectoryError": OSErrorInstanceModel,
822 "builtins.NotADirectoryError": OSErrorInstanceModel,
823 "builtins.PermissionError": OSErrorInstanceModel,
824 "builtins.ProcessLookupError": OSErrorInstanceModel,
825 "builtins.TimeoutError": OSErrorInstanceModel,
826}
827
828
829class DictModel(ObjectModel):
830 @property
831 def attr___class__(self):
832 return self._instance._proxied
833
834 def _generic_dict_attribute(self, obj, name):
835 """Generate a bound method that can infer the given *obj*."""
836
837 class DictMethodBoundMethod(astroid.BoundMethod):
838 def infer_call_result(
839 self,
840 caller: SuccessfulInferenceResult | None,
841 context: InferenceContext | None = None,
842 ) -> Iterator[InferenceResult]:
843 yield obj
844
845 meth = next(self._instance._proxied.igetattr(name), None)
846 return DictMethodBoundMethod(proxy=meth, bound=self._instance)
847
848 @property
849 def attr_items(self):
850 from astroid import objects # pylint: disable=import-outside-toplevel
851
852 elems = []
853 obj = node_classes.List(parent=self._instance)
854 for key, value in self._instance.items:
855 elem = node_classes.Tuple(parent=obj)
856 elem.postinit((key, value))
857 elems.append(elem)
858 obj.postinit(elts=elems)
859
860 items_obj = objects.DictItems(obj)
861 return self._generic_dict_attribute(items_obj, "items")
862
863 @property
864 def attr_keys(self):
865 from astroid import objects # pylint: disable=import-outside-toplevel
866
867 keys = [key for (key, _) in self._instance.items]
868 obj = node_classes.List(parent=self._instance)
869 obj.postinit(elts=keys)
870
871 keys_obj = objects.DictKeys(obj)
872 return self._generic_dict_attribute(keys_obj, "keys")
873
874 @property
875 def attr_values(self):
876 from astroid import objects # pylint: disable=import-outside-toplevel
877
878 values = [value for (_, value) in self._instance.items]
879 obj = node_classes.List(parent=self._instance)
880 obj.postinit(values)
881
882 values_obj = objects.DictValues(obj)
883 return self._generic_dict_attribute(values_obj, "values")
884
885
886class PropertyModel(ObjectModel):
887 """Model for a builtin property."""
888
889 def _init_function(self, name):
890 function = nodes.FunctionDef(
891 name=name,
892 parent=self._instance,
893 lineno=self._instance.lineno,
894 col_offset=self._instance.col_offset,
895 end_lineno=self._instance.end_lineno,
896 end_col_offset=self._instance.end_col_offset,
897 )
898
899 args = nodes.Arguments(parent=function, vararg=None, kwarg=None)
900 args.postinit(
901 args=[],
902 defaults=[],
903 kwonlyargs=[],
904 kw_defaults=[],
905 annotations=[],
906 posonlyargs=[],
907 posonlyargs_annotations=[],
908 kwonlyargs_annotations=[],
909 )
910
911 function.postinit(args=args, body=[])
912 return function
913
914 @property
915 def attr_fget(self):
916 func = self._instance
917
918 class PropertyFuncAccessor(nodes.FunctionDef):
919 def infer_call_result(
920 self,
921 caller: SuccessfulInferenceResult | None,
922 context: InferenceContext | None = None,
923 ) -> Iterator[InferenceResult]:
924 nonlocal func
925 if caller and len(caller.args) != 1:
926 raise InferenceError(
927 "fget() needs a single argument", target=self, context=context
928 )
929
930 yield from func.function.infer_call_result(
931 caller=caller, context=context
932 )
933
934 property_accessor = PropertyFuncAccessor(
935 name="fget",
936 parent=self._instance,
937 lineno=self._instance.lineno,
938 col_offset=self._instance.col_offset,
939 end_lineno=self._instance.end_lineno,
940 end_col_offset=self._instance.end_col_offset,
941 )
942 property_accessor.postinit(args=func.args, body=func.body)
943 return property_accessor
944
945 @property
946 def attr_fset(self):
947 func = self._instance
948
949 def find_setter(func: Property) -> astroid.FunctionDef | None:
950 """
951 Given a property, find the corresponding setter function and returns it.
952
953 :param func: property for which the setter has to be found
954 :return: the setter function or None
955 """
956 for target in [
957 t for t in func.parent.get_children() if t.name == func.function.name
958 ]:
959 for dec_name in target.decoratornames():
960 if dec_name.endswith(func.function.name + ".setter"):
961 return target
962 return None
963
964 func_setter = find_setter(func)
965 if not func_setter:
966 raise InferenceError(
967 f"Unable to find the setter of property {func.function.name}"
968 )
969
970 class PropertyFuncAccessor(nodes.FunctionDef):
971 def infer_call_result(
972 self,
973 caller: SuccessfulInferenceResult | None,
974 context: InferenceContext | None = None,
975 ) -> Iterator[InferenceResult]:
976 nonlocal func_setter
977 if caller and len(caller.args) != 2:
978 raise InferenceError(
979 "fset() needs two arguments", target=self, context=context
980 )
981 yield from func_setter.infer_call_result(caller=caller, context=context)
982
983 property_accessor = PropertyFuncAccessor(
984 name="fset",
985 parent=self._instance,
986 lineno=self._instance.lineno,
987 col_offset=self._instance.col_offset,
988 end_lineno=self._instance.end_lineno,
989 end_col_offset=self._instance.end_col_offset,
990 )
991 property_accessor.postinit(args=func_setter.args, body=func_setter.body)
992 return property_accessor
993
994 @property
995 def attr_setter(self):
996 return self._init_function("setter")
997
998 @property
999 def attr_deleter(self):
1000 return self._init_function("deleter")
1001
1002 @property
1003 def attr_getter(self):
1004 return self._init_function("getter")
1005
1006 # pylint: enable=import-outside-toplevel