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

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

494 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 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