Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/interpreter/objectmodel.py: 46%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

539 statements  

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 types 

29from collections.abc import Iterator 

30from functools import lru_cache 

31from typing import TYPE_CHECKING, Any, Literal 

32 

33import astroid 

34from astroid import bases, nodes, util 

35from astroid.context import InferenceContext, copy_context 

36from astroid.exceptions import AttributeInferenceError, InferenceError, NoDefault 

37from astroid.manager import AstroidManager 

38from astroid.nodes import node_classes 

39from astroid.typing import InferenceResult, SuccessfulInferenceResult 

40 

41if TYPE_CHECKING: 

42 from astroid.objects import Property 

43 

44IMPL_PREFIX = "attr_" 

45LEN_OF_IMPL_PREFIX = len(IMPL_PREFIX) 

46 

47 

48def _dunder_dict(instance, attributes): 

49 obj = node_classes.Dict( 

50 parent=instance, 

51 lineno=instance.lineno, 

52 col_offset=instance.col_offset, 

53 end_lineno=instance.end_lineno, 

54 end_col_offset=instance.end_col_offset, 

55 ) 

56 

57 # Convert the keys to node strings 

58 keys = [ 

59 node_classes.Const(value=value, parent=obj) for value in list(attributes.keys()) 

60 ] 

61 

62 # The original attribute has a list of elements for each key, 

63 # but that is not useful for retrieving the special attribute's value. 

64 # In this case, we're picking the last value from each list. 

65 values = [elem[-1] for elem in attributes.values()] 

66 

67 obj.postinit(list(zip(keys, values))) 

68 return obj 

69 

70 

71def _get_bound_node(model: ObjectModel) -> Any: 

72 # TODO: Use isinstance instead of try ... except after _instance has typing 

73 try: 

74 return model._instance._proxied 

75 except AttributeError: 

76 return model._instance 

77 

78 

79class ObjectModel: 

80 def __init__(self): 

81 self._instance = None 

82 

83 def __repr__(self): 

84 import pprint # pylint: disable=import-outside-toplevel 

85 

86 result = [] 

87 cname = type(self).__name__ 

88 string = "%(cname)s(%(fields)s)" 

89 alignment = len(cname) + 1 

90 for field in sorted(self.attributes()): 

91 width = max(80 - len(field) - alignment, 1) 

92 try: 

93 lines = pprint.pformat(field, indent=2, width=width).splitlines(True) 

94 except ValueError: 

95 lines = [f"<{type(field).__name__}>"] 

96 

97 inner = [lines[0]] 

98 for line in lines[1:]: 

99 inner.append(" " * alignment + line) 

100 result.append(field) 

101 

102 return string % { 

103 "cname": cname, 

104 "fields": (",\n" + " " * alignment).join(result), 

105 } 

106 

107 def __call__(self, instance): 

108 self._instance = instance 

109 return self 

110 

111 def __get__(self, instance, cls=None): 

112 # ObjectModel needs to be a descriptor so that just doing 

113 # `special_attributes = SomeObjectModel` should be enough in the body of a node. 

114 # But at the same time, node.special_attributes should return an object 

115 # which can be used for manipulating the special attributes. That's the reason 

116 # we pass the instance through which it got accessed to ObjectModel.__call__, 

117 # returning itself afterwards, so we can still have access to the 

118 # underlying data model and to the instance for which it got accessed. 

119 return self(instance) 

120 

121 def __contains__(self, name) -> bool: 

122 return name in self.attributes() 

123 

124 @lru_cache # noqa 

125 def attributes(self) -> list[str]: 

126 """Get the attributes which are exported by this object model.""" 

127 return [o[LEN_OF_IMPL_PREFIX:] for o in dir(self) if o.startswith(IMPL_PREFIX)] 

128 

129 def lookup(self, name): 

130 """Look up the given *name* in the current model. 

131 

132 It should return an AST or an interpreter object, 

133 but if the name is not found, then an AttributeInferenceError will be raised. 

134 """ 

135 if name in self.attributes(): 

136 return getattr(self, IMPL_PREFIX + name) 

137 raise AttributeInferenceError(target=self._instance, attribute=name) 

138 

139 @property 

140 def attr___new__(self) -> bases.BoundMethod: 

141 """Calling cls.__new__(type) on an object returns an instance of 'type'.""" 

142 from astroid import builder # pylint: disable=import-outside-toplevel 

143 

144 node: nodes.FunctionDef = builder.extract_node( 

145 """def __new__(self, cls): return cls()""" 

146 ) 

147 # We set the parent as being the ClassDef of 'object' as that 

148 # triggers correct inference as a call to __new__ in bases.py 

149 node.parent = AstroidManager().builtins_module["object"] 

150 

151 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self)) 

152 

153 @property 

154 def attr___init__(self) -> bases.BoundMethod: 

155 """Calling cls.__init__() normally returns None.""" 

156 from astroid import builder # pylint: disable=import-outside-toplevel 

157 

158 # The *args and **kwargs are necessary not to trigger warnings about missing 

159 # or extra parameters for '__init__' methods we don't infer correctly. 

160 # This BoundMethod is the fallback value for those. 

161 node: nodes.FunctionDef = builder.extract_node( 

162 """def __init__(self, *args, **kwargs): return None""" 

163 ) 

164 # We set the parent as being the ClassDef of 'object' as that 

165 # is where this method originally comes from 

166 node.parent = AstroidManager().builtins_module["object"] 

167 

168 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self)) 

169 

170 # Base object attributes that return Unknown as fallback placeholders. 

171 @property 

172 def attr___ne__(self): 

173 return node_classes.Unknown(parent=self._instance) 

174 

175 attr___class__ = attr___ne__ 

176 attr___delattr__ = attr___ne__ 

177 attr___dir__ = attr___ne__ 

178 attr___doc__ = attr___ne__ 

179 attr___eq__ = attr___ne__ 

180 attr___format__ = attr___ne__ 

181 attr___ge__ = attr___ne__ 

182 attr___getattribute__ = attr___ne__ 

183 attr___getstate__ = attr___ne__ 

184 attr___gt__ = attr___ne__ 

185 attr___hash__ = attr___ne__ 

186 attr___init_subclass__ = attr___ne__ 

187 attr___le__ = attr___ne__ 

188 attr___lt__ = attr___ne__ 

189 attr___reduce__ = attr___ne__ 

190 attr___reduce_ex__ = attr___ne__ 

191 attr___repr__ = attr___ne__ 

192 attr___setattr__ = attr___ne__ 

193 attr___sizeof__ = attr___ne__ 

194 attr___str__ = attr___ne__ 

195 attr___subclasshook__ = attr___ne__ 

196 

197 

198class ModuleModel(ObjectModel): 

199 def _builtins(self): 

200 builtins_ast_module = AstroidManager().builtins_module 

201 return builtins_ast_module.special_attributes.lookup("__dict__") 

202 

203 @property 

204 def attr_builtins(self): 

205 return self._builtins() 

206 

207 @property 

208 def attr___path__(self): 

209 if not self._instance.package: 

210 raise AttributeInferenceError(target=self._instance, attribute="__path__") 

211 

212 path_objs = [ 

213 node_classes.Const( 

214 value=( 

215 path if not path.endswith("__init__.py") else os.path.dirname(path) 

216 ), 

217 parent=self._instance, 

218 ) 

219 for path in self._instance.path 

220 ] 

221 

222 container = node_classes.List(parent=self._instance) 

223 container.postinit(path_objs) 

224 

225 return container 

226 

227 @property 

228 def attr___name__(self): 

229 return node_classes.Const(value=self._instance.name, parent=self._instance) 

230 

231 @property 

232 def attr___doc__(self): 

233 return node_classes.Const( 

234 value=getattr(self._instance.doc_node, "value", None), 

235 parent=self._instance, 

236 ) 

237 

238 @property 

239 def attr___file__(self): 

240 return node_classes.Const(value=self._instance.file, parent=self._instance) 

241 

242 @property 

243 def attr___dict__(self): 

244 return _dunder_dict(self._instance, self._instance.globals) 

245 

246 @property 

247 def attr___package__(self): 

248 if not self._instance.package: 

249 value = "" 

250 else: 

251 value = self._instance.name 

252 

253 return node_classes.Const(value=value, parent=self._instance) 

254 

255 # These are related to the Python 3 implementation of the 

256 # import system, 

257 # https://docs.python.org/3/reference/import.html#import-related-module-attributes 

258 

259 @property 

260 def attr___spec__(self): 

261 # No handling for now. 

262 return node_classes.Unknown(parent=self._instance) 

263 

264 @property 

265 def attr___loader__(self): 

266 # No handling for now. 

267 return node_classes.Unknown(parent=self._instance) 

268 

269 @property 

270 def attr___cached__(self): 

271 # No handling for now. 

272 return node_classes.Unknown(parent=self._instance) 

273 

274 

275class FunctionModel(ObjectModel): 

276 def _is_builtin_func(self) -> bool: 

277 func = self._instance 

278 return isinstance(func.parent, nodes.Module) and not func.root().pure_python 

279 

280 @property 

281 def attr___name__(self): 

282 return node_classes.Const(value=self._instance.name, parent=self._instance) 

283 

284 @property 

285 def attr___doc__(self): 

286 return node_classes.Const( 

287 value=getattr(self._instance.doc_node, "value", None), 

288 parent=self._instance, 

289 ) 

290 

291 @property 

292 def attr___qualname__(self): 

293 return node_classes.Const(value=self._instance.qname(), parent=self._instance) 

294 

295 @property 

296 def attr___defaults__(self): 

297 func = self._instance 

298 if self._is_builtin_func(): 

299 raise AttributeInferenceError(target=func, attribute="__defaults__") 

300 if not func.args.defaults: 

301 return node_classes.Const(value=None, parent=func) 

302 

303 defaults_obj = node_classes.Tuple(parent=func) 

304 defaults_obj.postinit(func.args.defaults) 

305 return defaults_obj 

306 

307 @property 

308 def attr___annotations__(self): 

309 if self._is_builtin_func(): 

310 raise AttributeInferenceError( 

311 target=self._instance, attribute="__annotations__" 

312 ) 

313 obj = node_classes.Dict( 

314 parent=self._instance, 

315 lineno=self._instance.lineno, 

316 col_offset=self._instance.col_offset, 

317 end_lineno=self._instance.end_lineno, 

318 end_col_offset=self._instance.end_col_offset, 

319 ) 

320 

321 if not self._instance.returns: 

322 returns = None 

323 else: 

324 returns = self._instance.returns 

325 

326 args = self._instance.args 

327 pair_annotations = itertools.chain( 

328 zip(args.args or [], args.annotations), 

329 zip(args.kwonlyargs, args.kwonlyargs_annotations), 

330 zip(args.posonlyargs or [], args.posonlyargs_annotations), 

331 ) 

332 

333 annotations = { 

334 arg.name: annotation for (arg, annotation) in pair_annotations if annotation 

335 } 

336 if args.varargannotation: 

337 annotations[args.vararg] = args.varargannotation 

338 if args.kwargannotation: 

339 annotations[args.kwarg] = args.kwargannotation 

340 if returns: 

341 annotations["return"] = returns 

342 

343 items = [ 

344 (node_classes.Const(key, parent=obj), value) 

345 for (key, value) in annotations.items() 

346 ] 

347 

348 obj.postinit(items) 

349 return obj 

350 

351 @property 

352 def attr___dict__(self): 

353 if self._is_builtin_func(): 

354 raise AttributeInferenceError(target=self._instance, attribute="__dict__") 

355 return node_classes.Dict( 

356 parent=self._instance, 

357 lineno=self._instance.lineno, 

358 col_offset=self._instance.col_offset, 

359 end_lineno=self._instance.end_lineno, 

360 end_col_offset=self._instance.end_col_offset, 

361 ) 

362 

363 @property 

364 def attr___globals__(self): 

365 if self._is_builtin_func(): 

366 raise AttributeInferenceError( 

367 target=self._instance, attribute="__globals__" 

368 ) 

369 return node_classes.Dict( 

370 parent=self._instance, 

371 lineno=self._instance.lineno, 

372 col_offset=self._instance.col_offset, 

373 end_lineno=self._instance.end_lineno, 

374 end_col_offset=self._instance.end_col_offset, 

375 ) 

376 

377 @property 

378 def attr___kwdefaults__(self): 

379 if self._is_builtin_func(): 

380 raise AttributeInferenceError( 

381 target=self._instance, attribute="__kwdefaults__" 

382 ) 

383 

384 def _default_args(args, parent): 

385 for arg in args.kwonlyargs: 

386 try: 

387 default = args.default_value(arg.name) 

388 except NoDefault: 

389 continue 

390 

391 name = node_classes.Const(arg.name, parent=parent) 

392 yield name, default 

393 

394 args = self._instance.args 

395 obj = node_classes.Dict( 

396 parent=self._instance, 

397 lineno=self._instance.lineno, 

398 col_offset=self._instance.col_offset, 

399 end_lineno=self._instance.end_lineno, 

400 end_col_offset=self._instance.end_col_offset, 

401 ) 

402 defaults = dict(_default_args(args, obj)) 

403 

404 obj.postinit(list(defaults.items())) 

405 return obj 

406 

407 @property 

408 def attr___module__(self): 

409 return node_classes.Const(self._instance.root().qname()) 

410 

411 @property 

412 def attr___get__(self): 

413 func = self._instance 

414 

415 if self._is_builtin_func(): 

416 raise AttributeInferenceError(target=func, attribute="__get__") 

417 

418 class DescriptorBoundMethod(bases.BoundMethod): 

419 """Bound method which knows how to understand calling descriptor 

420 binding. 

421 """ 

422 

423 def implicit_parameters(self) -> Literal[0]: 

424 # Different than BoundMethod since the signature 

425 # is different. 

426 return 0 

427 

428 def infer_call_result( 

429 self, 

430 caller: SuccessfulInferenceResult | None, 

431 context: InferenceContext | None = None, 

432 ) -> Iterator[bases.BoundMethod]: 

433 if len(caller.args) > 2 or len(caller.args) < 1: 

434 raise InferenceError( 

435 "Invalid arguments for descriptor binding", 

436 target=self, 

437 context=context, 

438 ) 

439 

440 context = copy_context(context) 

441 try: 

442 cls = next(caller.args[0].infer(context=context)) 

443 except StopIteration as e: 

444 raise InferenceError(context=context, node=caller.args[0]) from e 

445 

446 if isinstance(cls, util.UninferableBase): 

447 raise InferenceError( 

448 "Invalid class inferred", target=self, context=context 

449 ) 

450 

451 # The `func` can already be a Unbound or BoundMethod. If the former, make sure to 

452 # wrap as a BoundMethod like we do below when constructing the function from scratch. 

453 if isinstance(func, bases.UnboundMethod): 

454 yield bases.BoundMethod(proxy=func, bound=cls) 

455 return 

456 

457 if isinstance(func, bases.BoundMethod): 

458 yield func 

459 return 

460 

461 # Rebuild the original value, but with the parent set as the 

462 # class where it will be bound. 

463 new_func_kwargs = { 

464 "name": func.name, 

465 "lineno": func.lineno, 

466 "col_offset": func.col_offset, 

467 "parent": func.parent, 

468 "end_lineno": func.end_lineno, 

469 "end_col_offset": func.end_col_offset, 

470 } 

471 if isinstance(func, astroid.objects.PartialFunction): 

472 new_func_kwargs["filled_args"] = func.filled_args 

473 new_func_kwargs["filled_keywords"] = func.filled_keywords 

474 

475 new_func = func.__class__(**new_func_kwargs) 

476 # pylint: disable=no-member 

477 new_func.postinit( 

478 func.args, 

479 func.body, 

480 func.decorators, 

481 func.returns, 

482 doc_node=func.doc_node, 

483 ) 

484 

485 # Build a proper bound method that points to our newly built function. 

486 proxy = bases.UnboundMethod(new_func) 

487 yield bases.BoundMethod(proxy=proxy, bound=cls) 

488 

489 @property 

490 def args(self): 

491 """Overwrite the underlying args to match those of the underlying func. 

492 

493 Usually the underlying *func* is a function/method, as in: 

494 

495 def test(self): 

496 pass 

497 

498 This has only the *self* parameter but when we access test.__get__ 

499 we get a new object which has two parameters, *self* and *type*. 

500 """ 

501 nonlocal func 

502 arguments = nodes.Arguments( 

503 parent=func.args.parent, vararg=None, kwarg=None 

504 ) 

505 

506 positional_or_keyword_params = func.args.args.copy() 

507 positional_or_keyword_params.append( 

508 nodes.AssignName( 

509 name="type", 

510 lineno=0, 

511 col_offset=0, 

512 parent=arguments, 

513 end_lineno=None, 

514 end_col_offset=None, 

515 ) 

516 ) 

517 

518 positional_only_params = func.args.posonlyargs.copy() 

519 

520 arguments.postinit( 

521 args=positional_or_keyword_params, 

522 posonlyargs=positional_only_params, 

523 defaults=[], 

524 kwonlyargs=[], 

525 kw_defaults=[], 

526 annotations=[], 

527 kwonlyargs_annotations=[], 

528 posonlyargs_annotations=[], 

529 ) 

530 return arguments 

531 

532 return DescriptorBoundMethod(proxy=self._instance, bound=self._instance) 

533 

534 # Function-specific attributes. 

535 @property 

536 def attr___call__(self): 

537 return node_classes.Unknown(parent=self._instance) 

538 

539 attr___builtins__ = attr___call__ 

540 attr___closure__ = attr___call__ 

541 attr___code__ = attr___call__ 

542 attr___type_params__ = attr___call__ 

543 

544 

545class ClassModel(ObjectModel): 

546 def __init__(self): 

547 # Add a context so that inferences called from an instance don't recurse endlessly 

548 self.context = InferenceContext() 

549 

550 super().__init__() 

551 

552 @property 

553 def attr___annotations__(self) -> node_classes.Unknown: 

554 return node_classes.Unknown(parent=self._instance) 

555 

556 @property 

557 def attr___module__(self): 

558 return node_classes.Const(self._instance.root().qname()) 

559 

560 @property 

561 def attr___name__(self): 

562 return node_classes.Const(self._instance.name) 

563 

564 @property 

565 def attr___qualname__(self): 

566 return node_classes.Const(self._instance.qname()) 

567 

568 @property 

569 def attr___doc__(self): 

570 return node_classes.Const(getattr(self._instance.doc_node, "value", None)) 

571 

572 @property 

573 def attr___mro__(self): 

574 mro = self._instance.mro() 

575 obj = node_classes.Tuple(parent=self._instance) 

576 obj.postinit(mro) 

577 return obj 

578 

579 @property 

580 def attr_mro(self): 

581 other_self = self 

582 

583 # Cls.mro is a method and we need to return one in order to have a proper inference. 

584 # The method we're returning is capable of inferring the underlying MRO though. 

585 class MroBoundMethod(bases.BoundMethod): 

586 def infer_call_result( 

587 self, 

588 caller: SuccessfulInferenceResult | None, 

589 context: InferenceContext | None = None, 

590 ) -> Iterator[node_classes.Tuple]: 

591 yield other_self.attr___mro__ 

592 

593 implicit_metaclass = self._instance.implicit_metaclass() 

594 mro_method = implicit_metaclass.locals["mro"][0] 

595 return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass) 

596 

597 @property 

598 def attr___bases__(self): 

599 obj = node_classes.Tuple() 

600 context = InferenceContext() 

601 elts = list(self._instance._inferred_bases(context)) 

602 obj.postinit(elts=elts) 

603 return obj 

604 

605 @property 

606 def attr___class__(self): 

607 # pylint: disable=import-outside-toplevel; circular import 

608 from astroid import helpers 

609 

610 return helpers.object_type(self._instance) 

611 

612 @property 

613 def attr___subclasses__(self): 

614 """Get the subclasses of the underlying class. 

615 

616 This looks only in the current module for retrieving the subclasses, 

617 thus it might miss a couple of them. 

618 """ 

619 

620 qname = self._instance.qname() 

621 root = self._instance.root() 

622 classes = [ 

623 cls 

624 for cls in root.nodes_of_class(nodes.ClassDef) 

625 if cls != self._instance and cls.is_subtype_of(qname, context=self.context) 

626 ] 

627 

628 obj = node_classes.List(parent=self._instance) 

629 obj.postinit(classes) 

630 

631 class SubclassesBoundMethod(bases.BoundMethod): 

632 def infer_call_result( 

633 self, 

634 caller: SuccessfulInferenceResult | None, 

635 context: InferenceContext | None = None, 

636 ) -> Iterator[node_classes.List]: 

637 yield obj 

638 

639 implicit_metaclass = self._instance.implicit_metaclass() 

640 subclasses_method = implicit_metaclass.locals["__subclasses__"][0] 

641 return SubclassesBoundMethod(proxy=subclasses_method, bound=implicit_metaclass) 

642 

643 @property 

644 def attr___dict__(self): 

645 return node_classes.Dict( 

646 parent=self._instance, 

647 lineno=self._instance.lineno, 

648 col_offset=self._instance.col_offset, 

649 end_lineno=self._instance.end_lineno, 

650 end_col_offset=self._instance.end_col_offset, 

651 ) 

652 

653 @property 

654 def attr___call__(self): 

655 """Calling a class A() returns an instance of A.""" 

656 return self._instance.instantiate_class() 

657 

658 

659class SuperModel(ObjectModel): 

660 @property 

661 def attr___thisclass__(self): 

662 return self._instance.mro_pointer 

663 

664 @property 

665 def attr___self_class__(self): 

666 return self._instance._self_class 

667 

668 @property 

669 def attr___self__(self): 

670 return self._instance.type 

671 

672 @property 

673 def attr___class__(self): 

674 return self._instance._proxied 

675 

676 

677class UnboundMethodModel(FunctionModel): 

678 @property 

679 def attr___class__(self): 

680 # pylint: disable=import-outside-toplevel; circular import 

681 from astroid import helpers 

682 

683 return helpers.object_type(self._instance) 

684 

685 @property 

686 def attr___func__(self): 

687 return self._instance._proxied 

688 

689 @property 

690 def attr___self__(self): 

691 return node_classes.Const(value=None, parent=self._instance) 

692 

693 attr_im_func = attr___func__ 

694 attr_im_class = attr___class__ 

695 attr_im_self = attr___self__ 

696 

697 

698class ContextManagerModel(ObjectModel): 

699 """Model for context managers. 

700 

701 Based on 3.3.9 of the Data Model documentation: 

702 https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers 

703 """ 

704 

705 @property 

706 def attr___enter__(self) -> bases.BoundMethod: 

707 """Representation of the base implementation of __enter__. 

708 

709 As per Python documentation: 

710 Enter the runtime context related to this object. The with statement 

711 will bind this method's return value to the target(s) specified in the 

712 as clause of the statement, if any. 

713 """ 

714 from astroid import builder # pylint: disable=import-outside-toplevel 

715 

716 node: nodes.FunctionDef = builder.extract_node("""def __enter__(self): ...""") 

717 # We set the parent as being the ClassDef of 'object' as that 

718 # is where this method originally comes from 

719 node.parent = AstroidManager().builtins_module["object"] 

720 

721 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self)) 

722 

723 @property 

724 def attr___exit__(self) -> bases.BoundMethod: 

725 """Representation of the base implementation of __exit__. 

726 

727 As per Python documentation: 

728 Exit the runtime context related to this object. The parameters describe the 

729 exception that caused the context to be exited. If the context was exited 

730 without an exception, all three arguments will be None. 

731 """ 

732 from astroid import builder # pylint: disable=import-outside-toplevel 

733 

734 node: nodes.FunctionDef = builder.extract_node( 

735 """def __exit__(self, exc_type, exc_value, traceback): ...""" 

736 ) 

737 # We set the parent as being the ClassDef of 'object' as that 

738 # is where this method originally comes from 

739 node.parent = AstroidManager().builtins_module["object"] 

740 

741 return bases.BoundMethod(proxy=node, bound=_get_bound_node(self)) 

742 

743 

744class BoundMethodModel(FunctionModel): 

745 @property 

746 def attr___func__(self): 

747 # Proxy depth varies: an instance method accessed through an instance 

748 # proxies another method proxy, a classmethod on its class or a lambda 

749 # class attribute proxies the function directly. 

750 proxied = self._instance._proxied 

751 while isinstance(proxied, bases.UnboundMethod): 

752 proxied = proxied._proxied 

753 return proxied 

754 

755 @property 

756 def attr___self__(self): 

757 return self._instance.bound 

758 

759 

760class GeneratorBaseModel(FunctionModel, ContextManagerModel): 

761 def __init__(self, gen_module: nodes.Module): 

762 super().__init__() 

763 for name, values in gen_module.locals.items(): 

764 method = values[0] 

765 if isinstance(method, nodes.FunctionDef): 

766 method = bases.BoundMethod(method, _get_bound_node(self)) 

767 

768 def patched(cls, meth=method): 

769 return meth 

770 

771 setattr(type(self), IMPL_PREFIX + name, property(patched)) 

772 

773 @property 

774 def attr___name__(self): 

775 return node_classes.Const( 

776 value=self._instance.parent.name, parent=self._instance 

777 ) 

778 

779 @property 

780 def attr___doc__(self): 

781 return node_classes.Const( 

782 value=getattr(self._instance.parent.doc_node, "value", None), 

783 parent=self._instance, 

784 ) 

785 

786 

787class GeneratorModel(GeneratorBaseModel): 

788 def __init__(self): 

789 super().__init__(AstroidManager().builtins_module["generator"]) 

790 

791 

792class AsyncGeneratorModel(GeneratorBaseModel): 

793 def __init__(self): 

794 super().__init__(AstroidManager().builtins_module["async_generator"]) 

795 

796 

797class InstanceModel(ObjectModel): 

798 @property 

799 def attr___class__(self): 

800 return self._instance._proxied 

801 

802 @property 

803 def attr___module__(self): 

804 return node_classes.Const(self._instance.root().qname()) 

805 

806 @property 

807 def attr___doc__(self): 

808 return node_classes.Const(getattr(self._instance.doc_node, "value", None)) 

809 

810 @property 

811 def attr___dict__(self): 

812 return _dunder_dict(self._instance, self._instance.instance_attrs) 

813 

814 

815# Exception instances 

816 

817 

818class ExceptionInstanceModel(InstanceModel): 

819 @property 

820 def attr_args(self) -> nodes.Tuple: 

821 return nodes.Tuple(parent=self._instance) 

822 

823 @property 

824 def attr___traceback__(self): 

825 builtins_ast_module = AstroidManager().builtins_module 

826 traceback_type = builtins_ast_module[types.TracebackType.__name__] 

827 return traceback_type.instantiate_class() 

828 

829 

830class SyntaxErrorInstanceModel(ExceptionInstanceModel): 

831 @property 

832 def attr_text(self): 

833 return node_classes.Const("") 

834 

835 

836class GroupExceptionInstanceModel(ExceptionInstanceModel): 

837 @property 

838 def attr_exceptions(self) -> nodes.Tuple: 

839 return node_classes.Tuple(parent=self._instance) 

840 

841 

842class OSErrorInstanceModel(ExceptionInstanceModel): 

843 @property 

844 def attr_filename(self): 

845 return node_classes.Const("") 

846 

847 @property 

848 def attr_errno(self): 

849 return node_classes.Const(0) 

850 

851 @property 

852 def attr_strerror(self): 

853 return node_classes.Const("") 

854 

855 attr_filename2 = attr_filename 

856 

857 

858class ImportErrorInstanceModel(ExceptionInstanceModel): 

859 @property 

860 def attr_name(self): 

861 return node_classes.Const("") 

862 

863 @property 

864 def attr_path(self): 

865 return node_classes.Const("") 

866 

867 

868class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel): 

869 @property 

870 def attr_object(self): 

871 return node_classes.Const(b"") 

872 

873 

874BUILTIN_EXCEPTIONS = { 

875 "builtins.SyntaxError": SyntaxErrorInstanceModel, 

876 "builtins.ExceptionGroup": GroupExceptionInstanceModel, 

877 "builtins.ImportError": ImportErrorInstanceModel, 

878 "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel, 

879 # These are all similar to OSError in terms of attributes 

880 "builtins.OSError": OSErrorInstanceModel, 

881 "builtins.BlockingIOError": OSErrorInstanceModel, 

882 "builtins.BrokenPipeError": OSErrorInstanceModel, 

883 "builtins.ChildProcessError": OSErrorInstanceModel, 

884 "builtins.ConnectionAbortedError": OSErrorInstanceModel, 

885 "builtins.ConnectionError": OSErrorInstanceModel, 

886 "builtins.ConnectionRefusedError": OSErrorInstanceModel, 

887 "builtins.ConnectionResetError": OSErrorInstanceModel, 

888 "builtins.FileExistsError": OSErrorInstanceModel, 

889 "builtins.FileNotFoundError": OSErrorInstanceModel, 

890 "builtins.InterruptedError": OSErrorInstanceModel, 

891 "builtins.IsADirectoryError": OSErrorInstanceModel, 

892 "builtins.NotADirectoryError": OSErrorInstanceModel, 

893 "builtins.PermissionError": OSErrorInstanceModel, 

894 "builtins.ProcessLookupError": OSErrorInstanceModel, 

895 "builtins.TimeoutError": OSErrorInstanceModel, 

896} 

897 

898 

899class DictModel(ObjectModel): 

900 @property 

901 def attr___class__(self): 

902 return self._instance._proxied 

903 

904 def _generic_dict_attribute(self, obj, name): 

905 """Generate a bound method that can infer the given *obj*.""" 

906 

907 class DictMethodBoundMethod(astroid.BoundMethod): 

908 def infer_call_result( 

909 self, 

910 caller: SuccessfulInferenceResult | None, 

911 context: InferenceContext | None = None, 

912 ) -> Iterator[InferenceResult]: 

913 yield obj 

914 

915 meth = next(self._instance._proxied.igetattr(name), None) 

916 return DictMethodBoundMethod(proxy=meth, bound=self._instance) 

917 

918 @property 

919 def attr_items(self): 

920 from astroid import objects # pylint: disable=import-outside-toplevel 

921 

922 elems = [] 

923 obj = node_classes.List(parent=self._instance) 

924 for key, value in self._instance.items: 

925 elem = node_classes.Tuple(parent=obj) 

926 elem.postinit((key, value)) 

927 elems.append(elem) 

928 obj.postinit(elts=elems) 

929 

930 items_obj = objects.DictItems(obj) 

931 return self._generic_dict_attribute(items_obj, "items") 

932 

933 @property 

934 def attr_keys(self): 

935 from astroid import objects # pylint: disable=import-outside-toplevel 

936 

937 keys = [key for (key, _) in self._instance.items] 

938 obj = node_classes.List(parent=self._instance) 

939 obj.postinit(elts=keys) 

940 

941 keys_obj = objects.DictKeys(obj) 

942 return self._generic_dict_attribute(keys_obj, "keys") 

943 

944 @property 

945 def attr_values(self): 

946 from astroid import objects # pylint: disable=import-outside-toplevel 

947 

948 values = [value for (_, value) in self._instance.items] 

949 obj = node_classes.List(parent=self._instance) 

950 obj.postinit(values) 

951 

952 values_obj = objects.DictValues(obj) 

953 return self._generic_dict_attribute(values_obj, "values") 

954 

955 

956class PropertyModel(ObjectModel): 

957 """Model for a builtin property.""" 

958 

959 def _init_function(self, name): 

960 function = nodes.FunctionDef( 

961 name=name, 

962 parent=self._instance, 

963 lineno=self._instance.lineno, 

964 col_offset=self._instance.col_offset, 

965 end_lineno=self._instance.end_lineno, 

966 end_col_offset=self._instance.end_col_offset, 

967 ) 

968 

969 args = nodes.Arguments(parent=function, vararg=None, kwarg=None) 

970 args.postinit( 

971 args=[], 

972 defaults=[], 

973 kwonlyargs=[], 

974 kw_defaults=[], 

975 annotations=[], 

976 posonlyargs=[], 

977 posonlyargs_annotations=[], 

978 kwonlyargs_annotations=[], 

979 ) 

980 

981 function.postinit(args=args, body=[]) 

982 return function 

983 

984 @property 

985 def attr_fget(self): 

986 func = self._instance 

987 

988 class PropertyFuncAccessor(nodes.FunctionDef): 

989 def infer_call_result( 

990 self, 

991 caller: SuccessfulInferenceResult | None, 

992 context: InferenceContext | None = None, 

993 ) -> Iterator[InferenceResult]: 

994 nonlocal func 

995 if caller and len(caller.args) != 1: 

996 raise InferenceError( 

997 "fget() needs a single argument", target=self, context=context 

998 ) 

999 

1000 yield from func.function.infer_call_result( 

1001 caller=caller, context=context 

1002 ) 

1003 

1004 property_accessor = PropertyFuncAccessor( 

1005 name="fget", 

1006 parent=self._instance, 

1007 lineno=self._instance.lineno, 

1008 col_offset=self._instance.col_offset, 

1009 end_lineno=self._instance.end_lineno, 

1010 end_col_offset=self._instance.end_col_offset, 

1011 ) 

1012 property_accessor.postinit(args=func.args, body=func.body) 

1013 return property_accessor 

1014 

1015 @property 

1016 def attr_fset(self): 

1017 func = self._instance 

1018 

1019 def find_setter(func: Property) -> nodes.FunctionDef | None: 

1020 """ 

1021 Given a property, find the corresponding setter function and returns it. 

1022 

1023 :param func: property for which the setter has to be found 

1024 :return: the setter function or None 

1025 """ 

1026 for node in func.parent.body: 

1027 if isinstance(node, (nodes.FunctionDef, nodes.AsyncFunctionDef)): 

1028 for dec_name in node.decoratornames(): 

1029 if dec_name.endswith(func.function.name + ".setter"): 

1030 return node 

1031 return None 

1032 

1033 func_setter = find_setter(func) 

1034 if not func_setter: 

1035 raise InferenceError( 

1036 f"Unable to find the setter of property {func.function.name}" 

1037 ) 

1038 

1039 class PropertyFuncAccessor(nodes.FunctionDef): 

1040 def infer_call_result( 

1041 self, 

1042 caller: SuccessfulInferenceResult | None, 

1043 context: InferenceContext | None = None, 

1044 ) -> Iterator[InferenceResult]: 

1045 nonlocal func_setter 

1046 if caller and len(caller.args) != 2: 

1047 raise InferenceError( 

1048 "fset() needs two arguments", target=self, context=context 

1049 ) 

1050 yield from func_setter.infer_call_result(caller=caller, context=context) 

1051 

1052 property_accessor = PropertyFuncAccessor( 

1053 name="fset", 

1054 parent=self._instance, 

1055 lineno=self._instance.lineno, 

1056 col_offset=self._instance.col_offset, 

1057 end_lineno=self._instance.end_lineno, 

1058 end_col_offset=self._instance.end_col_offset, 

1059 ) 

1060 property_accessor.postinit(args=func_setter.args, body=func_setter.body) 

1061 return property_accessor 

1062 

1063 @property 

1064 def attr_setter(self): 

1065 return self._init_function("setter") 

1066 

1067 @property 

1068 def attr_deleter(self): 

1069 return self._init_function("deleter") 

1070 

1071 @property 

1072 def attr_getter(self): 

1073 return self._init_function("getter") 

1074 

1075 # pylint: enable=import-outside-toplevel