Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jedi-0.19.2-py3.11.egg/jedi/inference/value/klass.py: 26%

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

331 statements  

1""" 

2Like described in the :mod:`parso.python.tree` module, 

3there's a need for an ast like module to represent the states of parsed 

4modules. 

5 

6But now there are also structures in Python that need a little bit more than 

7that. An ``Instance`` for example is only a ``Class`` before it is 

8instantiated. This class represents these cases. 

9 

10So, why is there also a ``Class`` class here? Well, there are decorators and 

11they change classes in Python 3. 

12 

13Representation modules also define "magic methods". Those methods look like 

14``py__foo__`` and are typically mappable to the Python equivalents ``__call__`` 

15and others. Here's a list: 

16 

17====================================== ======================================== 

18**Method** **Description** 

19-------------------------------------- ---------------------------------------- 

20py__call__(arguments: Array) On callable objects, returns types. 

21py__bool__() Returns True/False/None; None means that 

22 there's no certainty. 

23py__bases__() Returns a list of base classes. 

24py__iter__() Returns a generator of a set of types. 

25py__class__() Returns the class of an instance. 

26py__simple_getitem__(index: int/str) Returns a a set of types of the index. 

27 Can raise an IndexError/KeyError. 

28py__getitem__(indexes: ValueSet) Returns a a set of types of the index. 

29py__file__() Only on modules. Returns None if does 

30 not exist. 

31py__package__() -> List[str] Only on modules. For the import system. 

32py__path__() Only on modules. For the import system. 

33py__get__(call_object) Only on instances. Simulates 

34 descriptors. 

35py__doc__() Returns the docstring for a value. 

36====================================== ======================================== 

37 

38""" 

39from __future__ import annotations 

40 

41from typing import List, Optional, Tuple 

42 

43from jedi import debug 

44from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \ 

45 function_is_property 

46from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \ 

47 inference_state_method_generator_cache 

48from jedi.inference import compiled 

49from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue 

50from jedi.inference.filters import ParserTreeFilter 

51from jedi.inference.names import TreeNameDefinition, ValueName 

52from jedi.inference.arguments import unpack_arglist, ValuesArguments 

53from jedi.inference.base_value import ValueSet, iterator_to_value_set, \ 

54 NO_VALUES, ValueWrapper 

55from jedi.inference.context import ClassContext 

56from jedi.inference.value.function import FunctionAndClassBase, FunctionMixin 

57from jedi.inference.value.decorator import Decoratee 

58from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager 

59from jedi.plugins import plugin_manager 

60from inspect import Parameter 

61from jedi.inference.names import BaseTreeParamName 

62from jedi.inference.signature import AbstractSignature 

63 

64 

65class ClassName(TreeNameDefinition): 

66 def __init__(self, class_value, tree_name, name_context, apply_decorators): 

67 super().__init__(name_context, tree_name) 

68 self._apply_decorators = apply_decorators 

69 self._class_value = class_value 

70 

71 @iterator_to_value_set 

72 def infer(self): 

73 # We're using a different value to infer, so we cannot call super(). 

74 from jedi.inference.syntax_tree import tree_name_to_values 

75 inferred = tree_name_to_values( 

76 self.parent_context.inference_state, self.parent_context, self.tree_name) 

77 

78 for result_value in inferred: 

79 if self._apply_decorators: 

80 yield from result_value.py__get__(instance=None, class_value=self._class_value) 

81 else: 

82 yield result_value 

83 

84 @property 

85 def api_type(self): 

86 type_ = super().api_type 

87 if type_ == 'function': 

88 definition = self.tree_name.get_definition() 

89 if definition is None: 

90 return type_ 

91 if function_is_property(definition): 

92 # This essentially checks if there is an @property before 

93 # the function. @property could be something different, but 

94 # any programmer that redefines property as something that 

95 # is not really a property anymore, should be shot. (i.e. 

96 # this is a heuristic). 

97 return 'property' 

98 return type_ 

99 

100 

101class ClassFilter(ParserTreeFilter): 

102 def __init__(self, class_value, node_context=None, until_position=None, 

103 origin_scope=None, is_instance=False): 

104 super().__init__( 

105 class_value.as_context(), node_context, 

106 until_position=until_position, 

107 origin_scope=origin_scope, 

108 ) 

109 self._class_value = class_value 

110 self._is_instance = is_instance 

111 

112 def _convert_names(self, names): 

113 return [ 

114 ClassName( 

115 class_value=self._class_value, 

116 tree_name=name, 

117 name_context=self._node_context, 

118 apply_decorators=not self._is_instance, 

119 ) for name in names 

120 ] 

121 

122 def _equals_origin_scope(self): 

123 node = self._origin_scope 

124 while node is not None: 

125 if node == self._parser_scope or node == self.parent_context: 

126 return True 

127 node = get_cached_parent_scope(self._parso_cache_node, node) 

128 return False 

129 

130 def _access_possible(self, name): 

131 # Filter for name mangling of private variables like __foo 

132 return not name.value.startswith('__') or name.value.endswith('__') \ 

133 or self._equals_origin_scope() 

134 

135 def _filter(self, names): 

136 names = super()._filter(names) 

137 return [name for name in names if self._access_possible(name)] 

138 

139 

140def init_param_value(arg_nodes) -> Optional[bool]: 

141 """ 

142 Returns: 

143 

144 - ``True`` if ``@dataclass(init=True)`` 

145 - ``False`` if ``@dataclass(init=False)`` 

146 - ``None`` if not specified ``@dataclass()`` 

147 """ 

148 for arg_node in arg_nodes: 

149 if ( 

150 arg_node.type == "argument" 

151 and arg_node.children[0].value == "init" 

152 ): 

153 if arg_node.children[2].value == "False": 

154 return False 

155 elif arg_node.children[2].value == "True": 

156 return True 

157 

158 return None 

159 

160 

161def get_dataclass_param_names(cls) -> List[DataclassParamName]: 

162 """ 

163 ``cls`` is a :class:`ClassMixin`. The type is only documented as mypy would 

164 complain that some fields are missing. 

165 

166 .. code:: python 

167 

168 @dataclass 

169 class A: 

170 a: int 

171 b: str = "toto" 

172 

173 For the previous example, the param names would be ``a`` and ``b``. 

174 """ 

175 param_names = [] 

176 filter_ = cls.as_context().get_global_filter() 

177 for name in sorted(filter_.values(), key=lambda name: name.start_pos): 

178 d = name.tree_name.get_definition() 

179 annassign = d.children[1] 

180 if d.type == 'expr_stmt' and annassign.type == 'annassign': 

181 node = annassign.children[1] 

182 if node.type == "atom_expr" and node.children[0].value == "ClassVar": 

183 continue 

184 

185 if len(annassign.children) < 4: 

186 default = None 

187 else: 

188 default = annassign.children[3] 

189 

190 param_names.append(DataclassParamName( 

191 parent_context=cls.parent_context, 

192 tree_name=name.tree_name, 

193 annotation_node=annassign.children[1], 

194 default_node=default, 

195 )) 

196 return param_names 

197 

198 

199class ClassMixin: 

200 def is_class(self): 

201 return True 

202 

203 def is_class_mixin(self): 

204 return True 

205 

206 def py__call__(self, arguments): 

207 from jedi.inference.value import TreeInstance 

208 

209 from jedi.inference.gradual.typing import TypedDict 

210 if self.is_typeddict(): 

211 return ValueSet([TypedDict(self)]) 

212 return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)]) 

213 

214 def py__class__(self): 

215 return compiled.builtin_from_name(self.inference_state, 'type') 

216 

217 @property 

218 def name(self): 

219 return ValueName(self, self.tree_node.name) 

220 

221 def py__name__(self): 

222 return self.name.string_name 

223 

224 @inference_state_method_generator_cache() 

225 def py__mro__(self): 

226 mro = [self] 

227 yield self 

228 # TODO Do a proper mro resolution. Currently we are just listing 

229 # classes. However, it's a complicated algorithm. 

230 for lazy_cls in self.py__bases__(): 

231 # TODO there's multiple different mro paths possible if this yields 

232 # multiple possibilities. Could be changed to be more correct. 

233 for cls in lazy_cls.infer(): 

234 # TODO detect for TypeError: duplicate base class str, 

235 # e.g. `class X(str, str): pass` 

236 try: 

237 mro_method = cls.py__mro__ 

238 except AttributeError: 

239 # TODO add a TypeError like: 

240 """ 

241 >>> class Y(lambda: test): pass 

242 Traceback (most recent call last): 

243 File "<stdin>", line 1, in <module> 

244 TypeError: function() argument 1 must be code, not str 

245 >>> class Y(1): pass 

246 Traceback (most recent call last): 

247 File "<stdin>", line 1, in <module> 

248 TypeError: int() takes at most 2 arguments (3 given) 

249 """ 

250 debug.warning('Super class of %s is not a class: %s', self, cls) 

251 else: 

252 for cls_new in mro_method(): 

253 if cls_new not in mro: 

254 mro.append(cls_new) 

255 yield cls_new 

256 

257 def get_filters(self, origin_scope=None, is_instance=False, 

258 include_metaclasses=True, include_type_when_class=True): 

259 if include_metaclasses: 

260 metaclasses = self.get_metaclasses() 

261 if metaclasses: 

262 yield from self.get_metaclass_filters(metaclasses, is_instance) 

263 

264 for cls in self.py__mro__(): 

265 if cls.is_compiled(): 

266 yield from cls.get_filters(is_instance=is_instance) 

267 else: 

268 yield ClassFilter( 

269 self, node_context=cls.as_context(), 

270 origin_scope=origin_scope, 

271 is_instance=is_instance 

272 ) 

273 if not is_instance and include_type_when_class: 

274 from jedi.inference.compiled import builtin_from_name 

275 type_ = builtin_from_name(self.inference_state, 'type') 

276 assert isinstance(type_, ClassValue) 

277 if type_ != self: 

278 # We are not using execute_with_values here, because the 

279 # plugin function for type would get executed instead of an 

280 # instance creation. 

281 args = ValuesArguments([]) 

282 for instance in type_.py__call__(args): 

283 instance_filters = instance.get_filters() 

284 # Filter out self filters 

285 next(instance_filters, None) 

286 next(instance_filters, None) 

287 x = next(instance_filters, None) 

288 assert x is not None 

289 yield x 

290 

291 def _has_dataclass_transform_metaclasses(self) -> Tuple[bool, Optional[bool]]: 

292 for meta in self.get_metaclasses(): # type: ignore[attr-defined] 

293 if ( 

294 isinstance(meta, Decoratee) 

295 # Internal leakage :| 

296 and isinstance(meta._wrapped_value, DataclassTransformer) 

297 ): 

298 return True, meta._wrapped_value.init_mode_from_new() 

299 

300 return False, None 

301 

302 def _get_dataclass_transform_signatures(self) -> List[DataclassSignature]: 

303 """ 

304 Returns: A non-empty list if the class has dataclass semantics else an 

305 empty list. 

306 

307 The dataclass-like semantics will be assumed for any class that directly 

308 or indirectly derives from the decorated class or uses the decorated 

309 class as a metaclass. 

310 """ 

311 param_names = [] 

312 is_dataclass_transform = False 

313 default_init_mode: Optional[bool] = None 

314 for cls in reversed(list(self.py__mro__())): 

315 if not is_dataclass_transform: 

316 

317 # If dataclass_transform is applied to a class, dataclass-like semantics 

318 # will be assumed for any class that directly or indirectly derives from 

319 # the decorated class or uses the decorated class as a metaclass. 

320 if ( 

321 isinstance(cls, DataclassTransformer) 

322 and cls.init_mode_from_init_subclass 

323 ): 

324 is_dataclass_transform = True 

325 default_init_mode = cls.init_mode_from_init_subclass 

326 

327 elif ( 

328 # Some object like CompiledValues would not be compatible 

329 isinstance(cls, ClassMixin) 

330 ): 

331 is_dataclass_transform, default_init_mode = ( 

332 cls._has_dataclass_transform_metaclasses() 

333 ) 

334 

335 # Attributes on the decorated class and its base classes are not 

336 # considered to be fields. 

337 if is_dataclass_transform: 

338 continue 

339 

340 # All inherited classes behave like dataclass semantics 

341 if ( 

342 is_dataclass_transform 

343 and isinstance(cls, ClassValue) 

344 and ( 

345 cls.init_param_mode() 

346 or (cls.init_param_mode() is None and default_init_mode) 

347 ) 

348 ): 

349 param_names.extend( 

350 get_dataclass_param_names(cls) 

351 ) 

352 

353 if is_dataclass_transform: 

354 return [DataclassSignature(cls, param_names)] 

355 else: 

356 return [] 

357 

358 def get_signatures(self): 

359 # Since calling staticmethod without a function is illegal, the Jedi 

360 # plugin doesn't return anything. Therefore call directly and get what 

361 # we want: An instance of staticmethod. 

362 metaclasses = self.get_metaclasses() 

363 if metaclasses: 

364 sigs = self.get_metaclass_signatures(metaclasses) 

365 if sigs: 

366 return sigs 

367 args = ValuesArguments([]) 

368 init_funcs = self.py__call__(args).py__getattribute__('__init__') 

369 

370 dataclass_sigs = self._get_dataclass_transform_signatures() 

371 if dataclass_sigs: 

372 return dataclass_sigs 

373 else: 

374 return [sig.bind(self) for sig in init_funcs.get_signatures()] 

375 

376 def _as_context(self): 

377 return ClassContext(self) 

378 

379 def get_type_hint(self, add_class_info=True): 

380 if add_class_info: 

381 return 'Type[%s]' % self.py__name__() 

382 return self.py__name__() 

383 

384 @inference_state_method_cache(default=False) 

385 def is_typeddict(self): 

386 # TODO Do a proper mro resolution. Currently we are just listing 

387 # classes. However, it's a complicated algorithm. 

388 from jedi.inference.gradual.typing import TypedDictClass 

389 for lazy_cls in self.py__bases__(): 

390 if not isinstance(lazy_cls, LazyTreeValue): 

391 return False 

392 tree_node = lazy_cls.data 

393 # Only resolve simple classes, stuff like Iterable[str] are more 

394 # intensive to resolve and if generics are involved, we know it's 

395 # not a TypedDict. 

396 if not expr_is_dotted(tree_node): 

397 return False 

398 

399 for cls in lazy_cls.infer(): 

400 if isinstance(cls, TypedDictClass): 

401 return True 

402 try: 

403 method = cls.is_typeddict 

404 except AttributeError: 

405 # We're only dealing with simple classes, so just returning 

406 # here should be fine. This only happens with e.g. compiled 

407 # classes. 

408 return False 

409 else: 

410 if method(): 

411 return True 

412 return False 

413 

414 def py__getitem__(self, index_value_set, contextualized_node): 

415 from jedi.inference.gradual.base import GenericClass 

416 if not index_value_set: 

417 debug.warning('Class indexes inferred to nothing. Returning class instead') 

418 return ValueSet([self]) 

419 return ValueSet( 

420 GenericClass( 

421 self, 

422 LazyGenericManager( 

423 context_of_index=contextualized_node.context, 

424 index_value=index_value, 

425 ) 

426 ) 

427 for index_value in index_value_set 

428 ) 

429 

430 def with_generics(self, generics_tuple): 

431 from jedi.inference.gradual.base import GenericClass 

432 return GenericClass( 

433 self, 

434 TupleGenericManager(generics_tuple) 

435 ) 

436 

437 def define_generics(self, type_var_dict): 

438 from jedi.inference.gradual.base import GenericClass 

439 

440 def remap_type_vars(): 

441 """ 

442 The TypeVars in the resulting classes have sometimes different names 

443 and we need to check for that, e.g. a signature can be: 

444 

445 def iter(iterable: Iterable[_T]) -> Iterator[_T]: ... 

446 

447 However, the iterator is defined as Iterator[_T_co], which means it has 

448 a different type var name. 

449 """ 

450 for type_var in self.list_type_vars(): 

451 yield type_var_dict.get(type_var.py__name__(), NO_VALUES) 

452 

453 if type_var_dict: 

454 return ValueSet([GenericClass( 

455 self, 

456 TupleGenericManager(tuple(remap_type_vars())) 

457 )]) 

458 return ValueSet({self}) 

459 

460 

461class DataclassParamName(BaseTreeParamName): 

462 """ 

463 Represent a field declaration on a class with dataclass semantics. 

464 """ 

465 

466 def __init__(self, parent_context, tree_name, annotation_node, default_node): 

467 super().__init__(parent_context, tree_name) 

468 self.annotation_node = annotation_node 

469 self.default_node = default_node 

470 

471 def get_kind(self): 

472 return Parameter.POSITIONAL_OR_KEYWORD 

473 

474 def infer(self): 

475 if self.annotation_node is None: 

476 return NO_VALUES 

477 else: 

478 return self.parent_context.infer_node(self.annotation_node) 

479 

480 

481class DataclassSignature(AbstractSignature): 

482 """ 

483 It represents the ``__init__`` signature of a class with dataclass semantics. 

484 

485 .. code:: python 

486 

487 """ 

488 def __init__(self, value, param_names): 

489 super().__init__(value) 

490 self._param_names = param_names 

491 

492 def get_param_names(self, resolve_stars=False): 

493 return self._param_names 

494 

495 

496class DataclassDecorator(ValueWrapper, FunctionMixin): 

497 """ 

498 A dataclass(-like) decorator with custom parameters. 

499 

500 .. code:: python 

501 

502 @dataclass(init=True) # this 

503 class A: ... 

504 

505 @dataclass_transform 

506 def create_model(*, init=False): pass 

507 

508 @create_model(init=False) # or this 

509 class B: ... 

510 """ 

511 

512 def __init__(self, function, arguments, default_init: bool = True): 

513 """ 

514 Args: 

515 function: Decoratee | function 

516 arguments: The parameters to the dataclass function decorator 

517 default_init: Boolean to indicate the default init value 

518 """ 

519 super().__init__(function) 

520 argument_init = self._init_param_value(arguments) 

521 self.init_param_mode = ( 

522 argument_init if argument_init is not None else default_init 

523 ) 

524 

525 def _init_param_value(self, arguments) -> Optional[bool]: 

526 if not arguments.argument_node: 

527 return None 

528 

529 arg_nodes = ( 

530 arguments.argument_node.children 

531 if arguments.argument_node.type == "arglist" 

532 else [arguments.argument_node] 

533 ) 

534 

535 return init_param_value(arg_nodes) 

536 

537 

538class DataclassTransformer(ValueWrapper, ClassMixin): 

539 """ 

540 A class decorated with the ``dataclass_transform`` decorator. dataclass-like 

541 semantics will be assumed for any class that directly or indirectly derives 

542 from the decorated class or uses the decorated class as a metaclass. 

543 Attributes on the decorated class and its base classes are not considered to 

544 be fields. 

545 """ 

546 def __init__(self, wrapped_value): 

547 super().__init__(wrapped_value) 

548 

549 def init_mode_from_new(self) -> bool: 

550 """Default value if missing is ``True``""" 

551 new_methods = self._wrapped_value.py__getattribute__("__new__") 

552 

553 if not new_methods: 

554 return True 

555 

556 new_method = list(new_methods)[0] 

557 

558 for param in new_method.get_param_names(): 

559 if ( 

560 param.string_name == "init" 

561 and param.default_node 

562 and param.default_node.type == "keyword" 

563 ): 

564 if param.default_node.value == "False": 

565 return False 

566 elif param.default_node.value == "True": 

567 return True 

568 

569 return True 

570 

571 @property 

572 def init_mode_from_init_subclass(self) -> Optional[bool]: 

573 # def __init_subclass__(cls) -> None: ... is hardcoded in the typeshed 

574 # so the extra parameters can not be inferred. 

575 return True 

576 

577 

578class DataclassWrapper(ValueWrapper, ClassMixin): 

579 """ 

580 A class with dataclass semantics from a decorator. The init parameters are 

581 only from the current class and parent classes decorated where the ``init`` 

582 parameter was ``True``. 

583 

584 .. code:: python 

585 

586 @dataclass 

587 class A: ... # this 

588 

589 @dataclass_transform 

590 def create_model(): pass 

591 

592 @create_model() 

593 class B: ... # or this 

594 """ 

595 

596 def __init__( 

597 self, wrapped_value, should_generate_init: bool 

598 ): 

599 super().__init__(wrapped_value) 

600 self.should_generate_init = should_generate_init 

601 

602 def get_signatures(self): 

603 param_names = [] 

604 for cls in reversed(list(self.py__mro__())): 

605 if ( 

606 isinstance(cls, DataclassWrapper) 

607 and cls.should_generate_init 

608 ): 

609 param_names.extend(get_dataclass_param_names(cls)) 

610 return [DataclassSignature(cls, param_names)] 

611 

612 

613class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass): 

614 api_type = 'class' 

615 

616 @inference_state_method_cache() 

617 def list_type_vars(self): 

618 found = [] 

619 arglist = self.tree_node.get_super_arglist() 

620 if arglist is None: 

621 return [] 

622 

623 for stars, node in unpack_arglist(arglist): 

624 if stars: 

625 continue # These are not relevant for this search. 

626 

627 from jedi.inference.gradual.annotation import find_unknown_type_vars 

628 for type_var in find_unknown_type_vars(self.parent_context, node): 

629 if type_var not in found: 

630 # The order matters and it's therefore a list. 

631 found.append(type_var) 

632 return found 

633 

634 def _get_bases_arguments(self): 

635 arglist = self.tree_node.get_super_arglist() 

636 if arglist: 

637 from jedi.inference import arguments 

638 return arguments.TreeArguments(self.inference_state, self.parent_context, arglist) 

639 return None 

640 

641 @inference_state_method_cache(default=()) 

642 def py__bases__(self): 

643 args = self._get_bases_arguments() 

644 if args is not None: 

645 lst = [value for key, value in args.unpack() if key is None] 

646 if lst: 

647 return lst 

648 

649 if self.py__name__() == 'object' \ 

650 and self.parent_context.is_builtins_module(): 

651 return [] 

652 return [LazyKnownValues( 

653 self.inference_state.builtins_module.py__getattribute__('object') 

654 )] 

655 

656 @plugin_manager.decorate() 

657 def get_metaclass_filters(self, metaclasses, is_instance): 

658 debug.warning('Unprocessed metaclass %s', metaclasses) 

659 return [] 

660 

661 @inference_state_method_cache(default=NO_VALUES) 

662 def get_metaclasses(self): 

663 args = self._get_bases_arguments() 

664 if args is not None: 

665 m = [value for key, value in args.unpack() if key == 'metaclass'] 

666 metaclasses = ValueSet.from_sets(lazy_value.infer() for lazy_value in m) 

667 metaclasses = ValueSet(m for m in metaclasses if m.is_class()) 

668 if metaclasses: 

669 return metaclasses 

670 

671 for lazy_base in self.py__bases__(): 

672 for value in lazy_base.infer(): 

673 if value.is_class(): 

674 values = value.get_metaclasses() 

675 if values: 

676 return values 

677 return NO_VALUES 

678 

679 def init_param_mode(self) -> Optional[bool]: 

680 """ 

681 It returns ``True`` if ``class X(init=False):`` else ``False``. 

682 """ 

683 bases_arguments = self._get_bases_arguments() 

684 

685 if bases_arguments.argument_node.type != "arglist": 

686 # If it is not inheriting from the base model and having 

687 # extra parameters, then init behavior is not changed. 

688 return None 

689 

690 return init_param_value(bases_arguments.argument_node.children) 

691 

692 @plugin_manager.decorate() 

693 def get_metaclass_signatures(self, metaclasses): 

694 return []