Coverage for src/griffe/_internal/models.py: 80.95%

995 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-14 23:10 +0200

1# This module contains our models definitions, 

2# to represent Python objects (and other aspects of Python APIs)... in Python. 

3 

4from __future__ import annotations 

5 

6import inspect 

7from collections import defaultdict 

8from contextlib import suppress 

9from pathlib import Path 

10from textwrap import dedent 

11from typing import TYPE_CHECKING, Any, Callable, Union, cast 

12 

13from griffe._internal.c3linear import c3linear_merge 

14from griffe._internal.docstrings.parsers import DocstringStyle, parse 

15from griffe._internal.enumerations import Kind, ParameterKind, Parser, TypeParameterKind 

16from griffe._internal.exceptions import AliasResolutionError, BuiltinModuleError, CyclicAliasError, NameResolutionError 

17from griffe._internal.expressions import ExprCall, ExprName, ExprTuple 

18from griffe._internal.logger import logger 

19from griffe._internal.mixins import ObjectAliasMixin 

20 

21if TYPE_CHECKING: 

22 from collections.abc import Sequence 

23 

24 from griffe._internal.collections import LinesCollection, ModulesCollection 

25 from griffe._internal.docstrings.models import DocstringSection 

26 from griffe._internal.expressions import Expr 

27 

28 

29from functools import cached_property 

30 

31 

32class Decorator: 

33 """This class represents decorators.""" 

34 

35 def __init__(self, value: str | Expr, *, lineno: int | None, endlineno: int | None) -> None: 

36 """Initialize the decorator. 

37 

38 Parameters: 

39 value: The decorator code. 

40 lineno: The starting line number. 

41 endlineno: The ending line number. 

42 """ 

43 self.value: str | Expr = value 

44 """The decorator value (as a Griffe expression or string).""" 

45 self.lineno: int | None = lineno 

46 """The starting line number of the decorator.""" 

47 self.endlineno: int | None = endlineno 

48 """The ending line number of the decorator.""" 

49 

50 @property 

51 def callable_path(self) -> str: 

52 """The path of the callable used as decorator.""" 

53 value = self.value.function if isinstance(self.value, ExprCall) else self.value 

54 return value if isinstance(value, str) else value.canonical_path 

55 

56 def as_dict(self, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002 

57 """Return this decorator's data as a dictionary. 

58 

59 Parameters: 

60 **kwargs: Additional serialization options. 

61 

62 Returns: 

63 A dictionary. 

64 """ 

65 return { 

66 "value": self.value, 

67 "lineno": self.lineno, 

68 "endlineno": self.endlineno, 

69 } 

70 

71 

72class Docstring: 

73 """This class represents docstrings.""" 

74 

75 def __init__( 

76 self, 

77 value: str, 

78 *, 

79 lineno: int | None = None, 

80 endlineno: int | None = None, 

81 parent: Object | None = None, 

82 parser: DocstringStyle | Parser | None = None, 

83 parser_options: dict[str, Any] | None = None, 

84 ) -> None: 

85 """Initialize the docstring. 

86 

87 Parameters: 

88 value: The docstring value. 

89 lineno: The starting line number. 

90 endlineno: The ending line number. 

91 parent: The parent object on which this docstring is attached. 

92 parser: The docstring parser to use. By default, no parsing is done. 

93 parser_options: Additional docstring parsing options. 

94 """ 

95 self.value: str = inspect.cleandoc(value.rstrip()) 

96 """The original value of the docstring, cleaned by `inspect.cleandoc`. 

97 

98 See also: [`source`][griffe.Docstring.source]. 

99 """ 

100 

101 self.lineno: int | None = lineno 

102 """The starting line number of the docstring. 

103 

104 See also: [`endlineno`][griffe.Docstring.endlineno].""" 

105 

106 self.endlineno: int | None = endlineno 

107 """The ending line number of the docstring. 

108 

109 See also: [`lineno`][griffe.Docstring.lineno].""" 

110 

111 self.parent: Object | None = parent 

112 """The object this docstring is attached to.""" 

113 

114 self.parser: DocstringStyle | Parser | None = parser 

115 """The selected docstring parser. 

116 

117 See also: [`parser_options`][griffe.Docstring.parser_options], 

118 [`parse`][griffe.Docstring.parse]. 

119 """ 

120 

121 self.parser_options: dict[str, Any] = parser_options or {} 

122 """The configured parsing options. 

123 

124 See also: [`parser`][griffe.Docstring.parser], 

125 [`parse`][griffe.Docstring.parse]. 

126 """ 

127 

128 @property 

129 def lines(self) -> list[str]: 

130 """The lines of the docstring. 

131 

132 See also: [`source`][griffe.Docstring.source]. 

133 """ 

134 return self.value.split("\n") 

135 

136 @property 

137 def source(self) -> str: 

138 """The original, uncleaned value of the docstring as written in the source. 

139 

140 See also: [`value`][griffe.Docstring.value]. 

141 """ 

142 if self.parent is None: 

143 raise ValueError("Cannot get original docstring without parent object") 

144 if isinstance(self.parent.filepath, list): 

145 raise ValueError("Cannot get original docstring for namespace package") # noqa: TRY004 

146 if self.lineno is None or self.endlineno is None: 

147 raise ValueError("Cannot get original docstring without line numbers") 

148 return "\n".join(self.parent.lines_collection[self.parent.filepath][self.lineno - 1 : self.endlineno]) 

149 

150 @cached_property 

151 def parsed(self) -> list[DocstringSection]: 

152 """The docstring sections, parsed into structured data.""" 

153 return self.parse() 

154 

155 def parse( 

156 self, 

157 parser: DocstringStyle | Parser | None = None, 

158 **options: Any, 

159 ) -> list[DocstringSection]: 

160 """Parse the docstring into structured data. 

161 

162 See also: [`parser`][griffe.Docstring.parser], 

163 [`parser_options`][griffe.Docstring.parser_options]. 

164 

165 Parameters: 

166 parser: The docstring parser to use. 

167 In order: use the given parser, or the self parser, or no parser (return a single text section). 

168 **options: Additional docstring parsing options. 

169 

170 Returns: 

171 The parsed docstring as a list of sections. 

172 """ 

173 return parse(self, parser or self.parser, **(options or self.parser_options)) 

174 

175 def as_dict( 

176 self, 

177 *, 

178 full: bool = False, 

179 **kwargs: Any, # noqa: ARG002 

180 ) -> dict[str, Any]: 

181 """Return this docstring's data as a dictionary. 

182 

183 Parameters: 

184 full: Whether to return full info, or just base info. 

185 **kwargs: Additional serialization options. 

186 

187 Returns: 

188 A dictionary. 

189 """ 

190 base: dict[str, Any] = { 

191 "value": self.value, 

192 "lineno": self.lineno, 

193 "endlineno": self.endlineno, 

194 } 

195 if full: 

196 base["parsed"] = self.parsed 

197 return base 

198 

199 

200class Parameter: # noqa: PLW1641 

201 """This class represent a function parameter. 

202 

203 See also: [`Parameters`][griffe.Parameters]. 

204 """ 

205 

206 def __init__( 

207 self, 

208 name: str, 

209 *, 

210 annotation: str | Expr | None = None, 

211 kind: ParameterKind | None = None, 

212 default: str | Expr | None = None, 

213 docstring: Docstring | None = None, 

214 ) -> None: 

215 """Initialize the parameter. 

216 

217 Parameters: 

218 name: The parameter name, without leading stars (`*` or `**`). 

219 annotation: The parameter annotation, if any. 

220 kind: The parameter kind. 

221 default: The parameter default, if any. 

222 docstring: The parameter docstring. 

223 """ 

224 self.name: str = name 

225 """The parameter name.""" 

226 self.annotation: str | Expr | None = annotation 

227 """The parameter type annotation.""" 

228 self.kind: ParameterKind | None = kind 

229 """The parameter kind.""" 

230 self.default: str | Expr | None = default 

231 """The parameter default value.""" 

232 self.docstring: Docstring | None = docstring 

233 """The parameter docstring.""" 

234 # The parent function is set in `Function.__init__`, 

235 # when the parameters are assigned to the function. 

236 self.function: Function | None = None 

237 """The parent function of the parameter.""" 

238 

239 def __str__(self) -> str: 

240 param = f"{self.name}: {self.annotation} = {self.default}" 

241 if self.kind: 

242 return f"[{self.kind.value}] {param}" 

243 return param 

244 

245 def __repr__(self) -> str: 

246 return f"Parameter(name={self.name!r}, annotation={self.annotation!r}, kind={self.kind!r}, default={self.default!r})" 

247 

248 def __eq__(self, value: object, /) -> bool: 

249 """Parameters are equal if all their attributes except `docstring` and `function` are equal.""" 

250 if not isinstance(value, Parameter): 250 ↛ 251line 250 didn't jump to line 251 because the condition on line 250 was never true

251 return NotImplemented 

252 return ( 

253 self.name == value.name 

254 and self.annotation == value.annotation 

255 and self.kind == value.kind 

256 and self.default == value.default 

257 ) 

258 

259 @property 

260 def required(self) -> bool: 

261 """Whether this parameter is required.""" 

262 return self.default is None 

263 

264 def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002 

265 """Return this parameter's data as a dictionary. 

266 

267 Parameters: 

268 **kwargs: Additional serialization options. 

269 

270 Returns: 

271 A dictionary. 

272 """ 

273 base: dict[str, Any] = { 

274 "name": self.name, 

275 "annotation": self.annotation, 

276 "kind": self.kind, 

277 "default": self.default, 

278 } 

279 if self.docstring: 

280 base["docstring"] = self.docstring.as_dict(full=full) 

281 return base 

282 

283 

284class Parameters: 

285 """This class is a container for parameters. 

286 

287 It allows to get parameters using their position (index) or their name: 

288 

289 ```pycon 

290 >>> parameters = Parameters(Parameter("hello")) 

291 >>> parameters[0] is parameters["hello"] 

292 True 

293 ``` 

294 

295 See also: [`Parameter`][griffe.Parameter]. 

296 """ 

297 

298 def __init__(self, *parameters: Parameter) -> None: 

299 """Initialize the parameters container. 

300 

301 Parameters: 

302 *parameters: The initial parameters to add to the container. 

303 """ 

304 self._params: list[Parameter] = list(parameters) 

305 

306 def __repr__(self) -> str: 

307 return f"Parameters({', '.join(repr(param) for param in self._params)})" 

308 

309 def __getitem__(self, name_or_index: int | str) -> Parameter: 

310 """Get a parameter by index or name.""" 

311 if isinstance(name_or_index, int): 

312 return self._params[name_or_index] 

313 name = name_or_index.lstrip("*") 

314 try: 

315 return next(param for param in self._params if param.name == name) 

316 except StopIteration as error: 

317 raise KeyError(f"parameter {name_or_index} not found") from error 

318 

319 def __setitem__(self, name_or_index: int | str, parameter: Parameter) -> None: 

320 """Set a parameter by index or name.""" 

321 if isinstance(name_or_index, int): 

322 self._params[name_or_index] = parameter 

323 else: 

324 name = name_or_index.lstrip("*") 

325 try: 

326 index = next(idx for idx, param in enumerate(self._params) if param.name == name) 

327 except StopIteration: 

328 self._params.append(parameter) 

329 else: 

330 self._params[index] = parameter 

331 

332 def __delitem__(self, name_or_index: int | str) -> None: 

333 """Delete a parameter by index or name.""" 

334 if isinstance(name_or_index, int): 

335 del self._params[name_or_index] 

336 else: 

337 name = name_or_index.lstrip("*") 

338 try: 

339 index = next(idx for idx, param in enumerate(self._params) if param.name == name) 

340 except StopIteration as error: 

341 raise KeyError(f"parameter {name_or_index} not found") from error 

342 del self._params[index] 

343 

344 def __len__(self): 

345 """The number of parameters.""" 

346 return len(self._params) 

347 

348 def __iter__(self): 

349 """Iterate over the parameters, in order.""" 

350 return iter(self._params) 

351 

352 def __contains__(self, param_name: str): 

353 """Whether a parameter with the given name is present.""" 

354 try: 

355 next(param for param in self._params if param.name == param_name.lstrip("*")) 

356 except StopIteration: 

357 return False 

358 return True 

359 

360 def add(self, parameter: Parameter) -> None: 

361 """Add a parameter to the container. 

362 

363 Parameters: 

364 parameter: The function parameter to add. 

365 

366 Raises: 

367 ValueError: When a parameter with the same name is already present. 

368 """ 

369 if parameter.name in self: 

370 raise ValueError(f"parameter {parameter.name} already present") 

371 self._params.append(parameter) 

372 

373 

374class TypeParameter: 

375 """This class represents a type parameter.""" 

376 

377 def __init__( 

378 self, 

379 name: str, 

380 *, 

381 kind: TypeParameterKind, 

382 bound: str | Expr | None = None, 

383 constraints: Sequence[str | Expr] | None = None, 

384 default: str | Expr | None = None, 

385 ) -> None: 

386 """Initialize the type parameter. 

387 

388 Parameters: 

389 name: The type parameter name, without leading stars (`*` or `**`). 

390 kind: The type parameter kind. 

391 bound: The type parameter bound, if any. 

392 Mutually exclusive with `constraints`. 

393 constraints: The type parameter constraints, if any. 

394 Mutually exclusive with `bound`. 

395 default: The type parameter default, if any. 

396 

397 Raises: 

398 ValueError: When more than one of `bound` and `constraints` is set. 

399 """ 

400 if bound is not None and constraints: 400 ↛ 401line 400 didn't jump to line 401 because the condition on line 400 was never true

401 raise ValueError("bound and constraints are mutually exclusive") 

402 

403 self.name: str = name 

404 """The type parameter name.""" 

405 

406 self.kind: TypeParameterKind = kind 

407 """The type parameter kind.""" 

408 

409 self.annotation: str | Expr | None 

410 """The type parameter bound or constraints.""" 

411 

412 if constraints: 

413 self.constraints = constraints 

414 else: 

415 self.bound = bound 

416 

417 self.default: str | Expr | None = default 

418 """The type parameter default value.""" 

419 

420 def __repr__(self) -> str: 

421 return f"TypeParameter(name={self.name!r}, kind={self.kind!r}, bound={self.annotation!r}, default={self.default!r})" 

422 

423 @property 

424 def bound(self) -> str | Expr | None: 

425 """The type parameter bound.""" 

426 if not isinstance(self.annotation, ExprTuple): 

427 return self.annotation 

428 return None 

429 

430 @bound.setter 

431 def bound(self, bound: str | Expr | None) -> None: 

432 self.annotation = bound 

433 

434 @property 

435 def constraints(self) -> tuple[str | Expr, ...] | None: 

436 """The type parameter constraints.""" 

437 if isinstance(self.annotation, ExprTuple): 

438 return tuple(self.annotation.elements) 

439 return None 

440 

441 @constraints.setter 

442 def constraints(self, constraints: Sequence[str | Expr] | None) -> None: 

443 if constraints is not None: 443 ↛ 446line 443 didn't jump to line 446 because the condition on line 443 was always true

444 self.annotation = ExprTuple(constraints) 

445 else: 

446 self.annotation = None 

447 

448 def as_dict(self, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002 

449 """Return this type parameter's data as a dictionary. 

450 

451 Parameters: 

452 **kwargs: Additional serialization options. 

453 

454 Returns: 

455 A dictionary. 

456 """ 

457 base: dict[str, Any] = { 

458 "name": self.name, 

459 "kind": self.kind, 

460 "annotation": self.annotation, 

461 "default": self.default, 

462 } 

463 return base 

464 

465 

466class TypeParameters: 

467 """This class is a container for type parameters. 

468 

469 It allows to get type parameters using their position (index) or their name: 

470 

471 ```pycon 

472 >>> type_parameters = TypeParameters(TypeParameter("hello"), kind=TypeParameterKind.type_var) 

473 >>> type_parameters[0] is type_parameters["hello"] 

474 True 

475 ``` 

476 """ 

477 

478 def __init__(self, *type_parameters: TypeParameter) -> None: 

479 """Initialize the type parameters container. 

480 

481 Parameters: 

482 *type_parameters: The initial type parameters to add to the container. 

483 """ 

484 self._type_params: list[TypeParameter] = list(type_parameters) 

485 

486 def __repr__(self) -> str: 

487 return f"TypeParameters({', '.join(repr(type_param) for type_param in self._type_params)})" 

488 

489 def __getitem__(self, name_or_index: int | str) -> TypeParameter: 

490 """Get a type parameter by index or name.""" 

491 if isinstance(name_or_index, int): 

492 return self._type_params[name_or_index] 

493 name = name_or_index.lstrip("*") 

494 try: 

495 return next(param for param in self._type_params if param.name == name) 

496 except StopIteration as error: 

497 raise KeyError(f"type parameter {name_or_index} not found") from error 

498 

499 def __setitem__(self, name_or_index: int | str, type_parameter: TypeParameter) -> None: 

500 """Set a type parameter by index or name.""" 

501 if isinstance(name_or_index, int): 

502 self._type_params[name_or_index] = type_parameter 

503 else: 

504 name = name_or_index.lstrip("*") 

505 try: 

506 index = next(idx for idx, param in enumerate(self._type_params) if param.name == name) 

507 except StopIteration: 

508 self._type_params.append(type_parameter) 

509 else: 

510 self._type_params[index] = type_parameter 

511 

512 def __delitem__(self, name_or_index: int | str) -> None: 

513 """Delete a type parameter by index or name.""" 

514 if isinstance(name_or_index, int): 

515 del self._type_params[name_or_index] 

516 else: 

517 name = name_or_index.lstrip("*") 

518 try: 

519 index = next(idx for idx, param in enumerate(self._type_params) if param.name == name) 

520 except StopIteration as error: 

521 raise KeyError(f"type parameter {name_or_index} not found") from error 

522 del self._type_params[index] 

523 

524 def __len__(self): 

525 """The number of type parameters.""" 

526 return len(self._type_params) 

527 

528 def __iter__(self): 

529 """Iterate over the type parameters, in order.""" 

530 return iter(self._type_params) 

531 

532 def __contains__(self, type_param_name: str): 

533 """Whether a type parameter with the given name is present.""" 

534 try: 

535 next(param for param in self._type_params if param.name == type_param_name.lstrip("*")) 

536 except StopIteration: 

537 return False 

538 return True 

539 

540 def add(self, type_parameter: TypeParameter) -> None: 

541 """Add a type parameter to the container. 

542 

543 Parameters: 

544 type_parameter: The function parameter to add. 

545 

546 Raises: 

547 ValueError: When a type parameter with the same name is already present. 

548 """ 

549 if type_parameter.name in self: 

550 raise ValueError(f"type parameter {type_parameter.name} already present") 

551 self._type_params.append(type_parameter) 

552 

553 

554class Object(ObjectAliasMixin): 

555 """An abstract class representing a Python object.""" 

556 

557 kind: Kind 

558 """The object kind.""" 

559 is_alias: bool = False 

560 """Always false for objects.""" 

561 is_collection: bool = False 

562 """Always false for objects.""" 

563 inherited: bool = False 

564 """Always false for objects. 

565 

566 Only aliases can be marked as inherited. 

567 """ 

568 

569 def __init__( 

570 self, 

571 name: str, 

572 *, 

573 lineno: int | None = None, 

574 endlineno: int | None = None, 

575 runtime: bool = True, 

576 docstring: Docstring | None = None, 

577 type_parameters: TypeParameters | None = None, 

578 parent: Module | Class | None = None, 

579 lines_collection: LinesCollection | None = None, 

580 modules_collection: ModulesCollection | None = None, 

581 ) -> None: 

582 """Initialize the object. 

583 

584 Parameters: 

585 name: The object name, as declared in the code. 

586 lineno: The object starting line, or None for modules. Lines start at 1. 

587 endlineno: The object ending line (inclusive), or None for modules. 

588 runtime: Whether this object is present at runtime or not. 

589 docstring: The object docstring. 

590 type_parameters: The object type parameters, if any. 

591 parent: The object parent. 

592 lines_collection: A collection of source code lines. 

593 modules_collection: A collection of modules. 

594 """ 

595 self.name: str = name 

596 """The object name.""" 

597 

598 self.lineno: int | None = lineno 

599 """The starting line number of the object. 

600 

601 See also: [`endlineno`][griffe.Object.endlineno]. 

602 """ 

603 

604 self.endlineno: int | None = endlineno 

605 """The ending line number of the object. 

606 

607 See also: [`lineno`][griffe.Object.lineno]. 

608 """ 

609 

610 self.docstring: Docstring | None = docstring 

611 """The object docstring. 

612 

613 See also: [`has_docstring`][griffe.Object.has_docstring], 

614 [`has_docstrings`][griffe.Object.has_docstrings]. 

615 """ 

616 

617 # TODO: Maybe move these into `Class` and `Function`. 

618 # Then always return them in `Class` and `Function`'s `as_dict` methods, 

619 # and remove the conditional in the `_load_class` and `_load_function` decoders. 

620 self.type_parameters: TypeParameters = type_parameters or TypeParameters() 

621 """The object type parameters.""" 

622 

623 self.parent: Module | Class | None = parent 

624 """The parent of the object (none if top module).""" 

625 

626 self.members: dict[str, Object | Alias] = {} 

627 """The object members (modules, classes, functions, attributes, type aliases). 

628 

629 See also: [`inherited_members`][griffe.Object.inherited_members], 

630 [`get_member`][griffe.Object.get_member], 

631 [`set_member`][griffe.Object.set_member], 

632 [`filter_members`][griffe.Object.filter_members]. 

633 """ 

634 

635 self.labels: set[str] = set() 

636 """The object labels (`property`, `dataclass`, etc.). 

637 

638 See also: [`has_labels`][griffe.Object.has_labels].""" 

639 

640 self.imports: dict[str, str] = {} 

641 """The other objects imported by this object. 

642 

643 Keys are the names within the object (`from ... import ... as AS_NAME`), 

644 while the values are the actual names of the objects (`from ... import REAL_NAME as ...`). 

645 """ 

646 

647 self.exports: list[str | ExprName] | None = None 

648 """The names of the objects exported by this (module) object through the `__all__` variable. 

649 

650 Exports can contain string (object names) or resolvable names, 

651 like other lists of exports coming from submodules: 

652 

653 ```python 

654 from .submodule import __all__ as submodule_all 

655 

656 __all__ = ["hello", *submodule_all] 

657 ``` 

658 

659 Exports get expanded by the loader before it expands wildcards and resolves aliases. 

660 

661 See also: [`GriffeLoader.expand_exports`][griffe.GriffeLoader.expand_exports]. 

662 """ 

663 

664 self.aliases: dict[str, Alias] = {} 

665 """The aliases pointing to this object.""" 

666 

667 self.runtime: bool = runtime 

668 """Whether this object is available at runtime. 

669 

670 Typically, type-guarded objects (under an `if TYPE_CHECKING` condition) 

671 are not available at runtime. 

672 """ 

673 

674 self.extra: dict[str, dict[str, Any]] = defaultdict(dict) 

675 """Namespaced dictionaries storing extra metadata for this object, used by extensions.""" 

676 

677 self.public: bool | None = None 

678 """Whether this object is public.""" 

679 

680 self.deprecated: bool | str | None = None 

681 """Whether this object is deprecated (boolean or deprecation message).""" 

682 

683 self._lines_collection: LinesCollection | None = lines_collection 

684 self._modules_collection: ModulesCollection | None = modules_collection 

685 

686 # Attach the docstring to this object. 

687 if docstring: 

688 docstring.parent = self 

689 

690 def __repr__(self) -> str: 

691 return f"{self.__class__.__name__}({self.name!r}, {self.lineno!r}, {self.endlineno!r})" 

692 

693 # Prevent using `__len__`. 

694 def __bool__(self) -> bool: 

695 """An object is always true-ish.""" 

696 return True 

697 

698 def __len__(self) -> int: 

699 """The number of members in this object, recursively.""" 

700 return len(self.members) + sum(len(member) for member in self.members.values()) 

701 

702 @property 

703 def has_docstring(self) -> bool: 

704 """Whether this object has a docstring (empty or not). 

705 

706 See also: [`docstring`][griffe.Object.docstring], 

707 [`has_docstrings`][griffe.Object.has_docstrings]. 

708 """ 

709 return bool(self.docstring) 

710 

711 # NOTE: (pawamoy) I'm not happy with `has_docstrings`. 

712 # It currently recurses into submodules, but that doesn't make sense 

713 # if downstream projects use it to know if they should render an init module 

714 # while not rendering submodules too: the property could tell them there are 

715 # docstrings, but they could be in submodules, not in the init module. 

716 # Maybe we should derive it into new properties: `has_local_docstrings`, 

717 # `has_docstrings`, `has_public_docstrings`... Maybe we should make it a function?` 

718 # For now it's used in mkdocstrings-python so we must be careful with changes. 

719 @property 

720 def has_docstrings(self) -> bool: 

721 """Whether this object or any of its members has a docstring (empty or not). 

722 

723 Inherited members are not considered. Imported members are not considered, 

724 unless they are also public. 

725 

726 See also: [`docstring`][griffe.Object.docstring], 

727 [`has_docstring`][griffe.Object.has_docstring]. 

728 """ 

729 if self.has_docstring: 

730 return True 

731 for member in self.members.values(): 

732 try: 

733 if (not member.is_imported or member.is_public) and member.has_docstrings: 

734 return True 

735 except AliasResolutionError: 

736 continue 

737 return False 

738 

739 def is_kind(self, kind: str | Kind | set[str | Kind]) -> bool: 

740 """Tell if this object is of the given kind. 

741 

742 See also: [`is_module`][griffe.Object.is_module], 

743 [`is_class`][griffe.Object.is_class], 

744 [`is_function`][griffe.Object.is_function], 

745 [`is_attribute`][griffe.Object.is_attribute], 

746 [`is_type_alias`][griffe.Object.is_type_alias], 

747 [`is_alias`][griffe.Object.is_alias]. 

748 

749 Parameters: 

750 kind: An instance or set of kinds (strings or enumerations). 

751 

752 Raises: 

753 ValueError: When an empty set is given as argument. 

754 

755 Returns: 

756 True or False. 

757 """ 

758 if isinstance(kind, set): 

759 if not kind: 

760 raise ValueError("kind must not be an empty set") 

761 return self.kind in (knd if isinstance(knd, Kind) else Kind(knd) for knd in kind) 

762 if isinstance(kind, str): 

763 kind = Kind(kind) 

764 return self.kind is kind 

765 

766 @property 

767 def inherited_members(self) -> dict[str, Alias]: 

768 """Members that are inherited from base classes. 

769 

770 This method is part of the consumer API: 

771 do not use when producing Griffe trees! 

772 

773 See also: [`members`][griffe.Object.members]. 

774 """ 

775 if not isinstance(self, Class): 775 ↛ 776line 775 didn't jump to line 776 because the condition on line 775 was never true

776 return {} 

777 try: 

778 mro = self.mro() 

779 except ValueError as error: 

780 logger.debug(error) 

781 return {} 

782 inherited_members = {} 

783 for base in reversed(mro): 

784 for name, member in base.members.items(): 

785 if name not in self.members: 

786 inherited_members[name] = Alias(name, member, parent=self, inherited=True) 

787 return inherited_members 

788 

789 @property 

790 def is_module(self) -> bool: 

791 """Whether this object is a module. 

792 

793 See also: [`is_init_module`][griffe.Object.is_init_module]. 

794 [`is_class`][griffe.Object.is_class], 

795 [`is_function`][griffe.Object.is_function], 

796 [`is_attribute`][griffe.Object.is_attribute], 

797 [`is_type_alias`][griffe.Object.is_type_alias], 

798 [`is_alias`][griffe.Object.is_alias], 

799 [`is_kind`][griffe.Object.is_kind]. 

800 """ 

801 return self.kind is Kind.MODULE 

802 

803 @property 

804 def is_class(self) -> bool: 

805 """Whether this object is a class. 

806 

807 See also: [`is_module`][griffe.Object.is_module]. 

808 [`is_function`][griffe.Object.is_function], 

809 [`is_attribute`][griffe.Object.is_attribute], 

810 [`is_type_alias`][griffe.Object.is_type_alias], 

811 [`is_alias`][griffe.Object.is_alias], 

812 [`is_kind`][griffe.Object.is_kind]. 

813 """ 

814 return self.kind is Kind.CLASS 

815 

816 @property 

817 def is_function(self) -> bool: 

818 """Whether this object is a function. 

819 

820 See also: [`is_module`][griffe.Object.is_module]. 

821 [`is_class`][griffe.Object.is_class], 

822 [`is_attribute`][griffe.Object.is_attribute], 

823 [`is_type_alias`][griffe.Object.is_type_alias], 

824 [`is_alias`][griffe.Object.is_alias], 

825 [`is_kind`][griffe.Object.is_kind]. 

826 """ 

827 return self.kind is Kind.FUNCTION 

828 

829 @property 

830 def is_attribute(self) -> bool: 

831 """Whether this object is an attribute. 

832 

833 See also: [`is_module`][griffe.Object.is_module]. 

834 [`is_class`][griffe.Object.is_class], 

835 [`is_function`][griffe.Object.is_function], 

836 [`is_type_alias`][griffe.Object.is_type_alias], 

837 [`is_alias`][griffe.Object.is_alias], 

838 [`is_kind`][griffe.Object.is_kind]. 

839 """ 

840 return self.kind is Kind.ATTRIBUTE 

841 

842 @property 

843 def is_type_alias(self) -> bool: 

844 """Whether this object is a type alias. 

845 

846 See also: [`is_module`][griffe.Object.is_module]. 

847 [`is_class`][griffe.Object.is_class], 

848 [`is_function`][griffe.Object.is_function], 

849 [`is_attribute`][griffe.Object.is_attribute], 

850 [`is_alias`][griffe.Object.is_alias], 

851 [`is_kind`][griffe.Object.is_kind]. 

852 """ 

853 return self.kind is Kind.TYPE_ALIAS 

854 

855 @property 

856 def is_init_module(self) -> bool: 

857 """Whether this object is an `__init__.py` module. 

858 

859 See also: [`is_module`][griffe.Object.is_module]. 

860 """ 

861 return False 

862 

863 @property 

864 def is_package(self) -> bool: 

865 """Whether this object is a package (top module). 

866 

867 See also: [`is_subpackage`][griffe.Object.is_subpackage]. 

868 """ 

869 return False 

870 

871 @property 

872 def is_subpackage(self) -> bool: 

873 """Whether this object is a subpackage. 

874 

875 See also: [`is_package`][griffe.Object.is_package]. 

876 """ 

877 return False 

878 

879 @property 

880 def is_namespace_package(self) -> bool: 

881 """Whether this object is a namespace package (top folder, no `__init__.py`). 

882 

883 See also: [`is_namespace_subpackage`][griffe.Object.is_namespace_subpackage]. 

884 """ 

885 return False 

886 

887 @property 

888 def is_namespace_subpackage(self) -> bool: 

889 """Whether this object is a namespace subpackage. 

890 

891 See also: [`is_namespace_package`][griffe.Object.is_namespace_package]. 

892 """ 

893 return False 

894 

895 def has_labels(self, *labels: str) -> bool: 

896 """Tell if this object has all the given labels. 

897 

898 See also: [`labels`][griffe.Object.labels]. 

899 

900 Parameters: 

901 *labels: Labels that must be present. 

902 

903 Returns: 

904 True or False. 

905 """ 

906 return set(labels).issubset(self.labels) 

907 

908 def filter_members(self, *predicates: Callable[[Object | Alias], bool]) -> dict[str, Object | Alias]: 

909 """Filter and return members based on predicates. 

910 

911 See also: [`members`][griffe.Object.members]. 

912 

913 Parameters: 

914 *predicates: A list of predicates, i.e. callables accepting a member as argument and returning a boolean. 

915 

916 Returns: 

917 A dictionary of members. 

918 """ 

919 if not predicates: 

920 return self.members 

921 members: dict[str, Object | Alias] = { 

922 name: member for name, member in self.members.items() if all(predicate(member) for predicate in predicates) 

923 } 

924 return members 

925 

926 @property 

927 def module(self) -> Module: 

928 """The parent module of this object. 

929 

930 See also: [`package`][griffe.Object.package]. 

931 

932 Examples: 

933 >>> import griffe 

934 >>> markdown = griffe.load("markdown") 

935 >>> markdown["core.Markdown.references"].module 

936 Module(PosixPath('~/project/.venv/lib/python3.11/site-packages/markdown/core.py')) 

937 >>> # The `module` of a module is itself. 

938 >>> markdown["core"].module 

939 Module(PosixPath('~/project/.venv/lib/python3.11/site-packages/markdown/core.py')) 

940 

941 Raises: 

942 ValueError: When the object is not a module and does not have a parent. 

943 """ 

944 if isinstance(self, Module): 

945 return self 

946 if self.parent is not None: 

947 return self.parent.module 

948 raise ValueError(f"Object {self.name} does not have a parent module") 

949 

950 @property 

951 def package(self) -> Module: 

952 """The absolute top module (the package) of this object. 

953 

954 See also: [`module`][griffe.Object.module]. 

955 

956 Examples: 

957 >>> import griffe 

958 >>> markdown = griffe.load("markdown") 

959 >>> markdown["core.Markdown.references"].package 

960 Module(PosixPath('~/project/.venv/lib/python3.11/site-packages/markdown/__init__.py')) 

961 """ 

962 module = self.module 

963 while module.parent: 

964 module = module.parent # type: ignore[assignment] 

965 return module 

966 

967 @property 

968 def filepath(self) -> Path | list[Path]: 

969 """The file path (or directory list for namespace packages) where this object was defined. 

970 

971 See also: [`relative_filepath`][griffe.Object.relative_filepath], 

972 [`relative_package_filepath`][griffe.Object.relative_package_filepath]. 

973 

974 Examples: 

975 >>> import griffe 

976 >>> markdown = griffe.load("markdown") 

977 >>> markdown.filepath 

978 PosixPath('~/project/.venv/lib/python3.11/site-packages/markdown/__init__.py') 

979 """ 

980 return self.module.filepath 

981 

982 @property 

983 def relative_package_filepath(self) -> Path: 

984 """The file path where this object was defined, relative to the top module path. 

985 

986 See also: [`filepath`][griffe.Object.filepath], 

987 [`relative_filepath`][griffe.Object.relative_filepath]. 

988 

989 Raises: 

990 ValueError: When the relative path could not be computed. 

991 """ 

992 package_path = self.package.filepath 

993 

994 # Current "module" is a namespace package. 

995 if isinstance(self.filepath, list): 995 ↛ 997line 995 didn't jump to line 997 because the condition on line 995 was never true

996 # Current package is a namespace package. 

997 if isinstance(package_path, list): 

998 for pkg_path in package_path: 

999 for self_path in self.filepath: 

1000 with suppress(ValueError): 

1001 return self_path.relative_to(pkg_path.parent) 

1002 

1003 # Current package is a regular package. 

1004 # NOTE: Technically it makes no sense to have a namespace package 

1005 # under a non-namespace one, so we should never enter this branch. 

1006 else: 

1007 for self_path in self.filepath: 

1008 with suppress(ValueError): 

1009 return self_path.relative_to(package_path.parent.parent) 

1010 raise ValueError 

1011 

1012 # Current package is a namespace package, 

1013 # and current module is a regular module or package. 

1014 if isinstance(package_path, list): 1014 ↛ 1015line 1014 didn't jump to line 1015 because the condition on line 1014 was never true

1015 for pkg_path in package_path: 

1016 with suppress(ValueError): 

1017 return self.filepath.relative_to(pkg_path.parent) 

1018 raise ValueError 

1019 

1020 # Current package is a regular package, 

1021 # and current module is a regular module or package, 

1022 # try to compute the path relative to the parent folder 

1023 # of the package (search path). 

1024 return self.filepath.relative_to(package_path.parent.parent) 

1025 

1026 @property 

1027 def relative_filepath(self) -> Path: 

1028 """The file path where this object was defined, relative to the current working directory. 

1029 

1030 If this object's file path is not relative to the current working directory, return its absolute path. 

1031 

1032 See also: [`filepath`][griffe.Object.filepath], 

1033 [`relative_package_filepath`][griffe.Object.relative_package_filepath]. 

1034 

1035 Raises: 

1036 ValueError: When the relative path could not be computed. 

1037 """ 

1038 cwd = Path.cwd() 

1039 if isinstance(self.filepath, list): 1039 ↛ 1040line 1039 didn't jump to line 1040 because the condition on line 1039 was never true

1040 for self_path in self.filepath: 

1041 with suppress(ValueError): 

1042 return self_path.relative_to(cwd) 

1043 raise ValueError(f"No directory in {self.filepath!r} is relative to the current working directory {cwd}") 

1044 try: 

1045 return self.filepath.relative_to(cwd) 

1046 except ValueError: 

1047 return self.filepath 

1048 

1049 @property 

1050 def path(self) -> str: 

1051 """The dotted path of this object. 

1052 

1053 On regular objects (not aliases), the path is the canonical path. 

1054 

1055 See also: [`canonical_path`][griffe.Object.canonical_path]. 

1056 

1057 Examples: 

1058 >>> import griffe 

1059 >>> markdown = griffe.load("markdown") 

1060 >>> markdown["core.Markdown.references"].path 

1061 'markdown.core.Markdown.references' 

1062 """ 

1063 return self.canonical_path 

1064 

1065 @property 

1066 def canonical_path(self) -> str: 

1067 """The full dotted path of this object. 

1068 

1069 The canonical path is the path where the object was defined (not imported). 

1070 

1071 See also: [`path`][griffe.Object.path]. 

1072 """ 

1073 if self.parent is None: 

1074 return self.name 

1075 return f"{self.parent.path}.{self.name}" 

1076 

1077 @property 

1078 def modules_collection(self) -> ModulesCollection: 

1079 """The modules collection attached to this object or its parents. 

1080 

1081 Raises: 

1082 ValueError: When no modules collection can be found in the object or its parents. 

1083 """ 

1084 if self._modules_collection is not None: 

1085 return self._modules_collection 

1086 if self.parent is None: 1086 ↛ 1087line 1086 didn't jump to line 1087 because the condition on line 1086 was never true

1087 raise ValueError("no modules collection in this object or its parents") 

1088 return self.parent.modules_collection 

1089 

1090 @property 

1091 def lines_collection(self) -> LinesCollection: 

1092 """The lines collection attached to this object or its parents. 

1093 

1094 See also: [`lines`][griffe.Object.lines], 

1095 [`source`][griffe.Object.source]. 

1096 

1097 Raises: 

1098 ValueError: When no modules collection can be found in the object or its parents. 

1099 """ 

1100 if self._lines_collection is not None: 

1101 return self._lines_collection 

1102 if self.parent is None: 1102 ↛ 1103line 1102 didn't jump to line 1103 because the condition on line 1102 was never true

1103 raise ValueError("no lines collection in this object or its parents") 

1104 return self.parent.lines_collection 

1105 

1106 @property 

1107 def lines(self) -> list[str]: 

1108 """The lines containing the source of this object. 

1109 

1110 See also: [`lines_collection`][griffe.Object.lines_collection], 

1111 [`source`][griffe.Object.source]. 

1112 """ 

1113 try: 

1114 filepath = self.filepath 

1115 except BuiltinModuleError: 

1116 return [] 

1117 if isinstance(filepath, list): 1117 ↛ 1118line 1117 didn't jump to line 1118 because the condition on line 1117 was never true

1118 return [] 

1119 try: 

1120 lines = self.lines_collection[filepath] 

1121 except KeyError: 

1122 return [] 

1123 if self.is_module: 

1124 return lines 

1125 if self.lineno is None or self.endlineno is None: 

1126 return [] 

1127 return lines[self.lineno - 1 : self.endlineno] 

1128 

1129 @property 

1130 def source(self) -> str: 

1131 """The source code of this object. 

1132 

1133 See also: [`lines`][griffe.Object.lines], 

1134 [`lines_collection`][griffe.Object.lines_collection]. 

1135 """ 

1136 return dedent("\n".join(self.lines)) 

1137 

1138 def resolve(self, name: str) -> str: 

1139 """Resolve a name within this object's and parents' scope. 

1140 

1141 Parameters: 

1142 name: The name to resolve. 

1143 

1144 Raises: 

1145 NameResolutionError: When the name could not be resolved. 

1146 

1147 Returns: 

1148 The resolved name. 

1149 """ 

1150 # TODO: Better match Python's own scoping rules? 

1151 # Also, maybe return regular paths instead of canonical ones? 

1152 

1153 # Name is a type parameter. 

1154 if name in self.type_parameters: 

1155 type_parameter = self.type_parameters[name] 

1156 if type_parameter.kind is TypeParameterKind.type_var_tuple: 1156 ↛ 1157line 1156 didn't jump to line 1157 because the condition on line 1156 was never true

1157 prefix = "*" 

1158 elif type_parameter.kind is TypeParameterKind.param_spec: 1158 ↛ 1159line 1158 didn't jump to line 1159 because the condition on line 1158 was never true

1159 prefix = "**" 

1160 else: 

1161 prefix = "" 

1162 return f"{self.path}[{prefix}{name}]" 

1163 

1164 # Name is a member of this object. 

1165 if name in self.members: 

1166 if self.members[name].is_alias: 

1167 return self.members[name].target_path # type: ignore[union-attr] 

1168 return self.members[name].path 

1169 

1170 # Name unknown and no more parent scope, could be a built-in. 

1171 if self.parent is None: 

1172 raise NameResolutionError(f"{name} could not be resolved in the scope of {self.path}") 

1173 

1174 # Name is parent, non-module object. 

1175 if name == self.parent.name and not self.parent.is_module: 

1176 return self.parent.path 

1177 

1178 # Recurse in parent. 

1179 return self.parent.resolve(name) 

1180 

1181 def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]: 

1182 """Return this object's data as a dictionary. 

1183 

1184 See also: [`as_json`][griffe.Object.as_json]. 

1185 

1186 Parameters: 

1187 full: Whether to return full info, or just base info. 

1188 **kwargs: Additional serialization options. 

1189 

1190 Returns: 

1191 A dictionary. 

1192 """ 

1193 base: dict[str, Any] = { 

1194 "kind": self.kind, 

1195 "name": self.name, 

1196 "runtime": self.runtime, 

1197 } 

1198 

1199 if self.public is not None: 1199 ↛ 1200line 1199 didn't jump to line 1200 because the condition on line 1199 was never true

1200 base["public"] = self.public 

1201 if self.exports is not None: 

1202 base["exports"] = [str(export) for export in self.exports] 

1203 if self.imports: 

1204 base["imports"] = self.imports 

1205 if self.deprecated is not None: 1205 ↛ 1206line 1205 didn't jump to line 1206 because the condition on line 1205 was never true

1206 base["deprecated"] = self.deprecated 

1207 if self.lineno is not None: 

1208 base["lineno"] = self.lineno 

1209 if self.endlineno is not None: 

1210 base["endlineno"] = self.endlineno 

1211 if self.docstring: 

1212 base["docstring"] = self.docstring 

1213 if self.type_parameters: 

1214 base["type_parameters"] = [type_param.as_dict(**kwargs) for type_param in self.type_parameters] 

1215 if self.labels: 

1216 base["labels"] = self.labels 

1217 if self.members: 

1218 base["members"] = {name: member.as_dict(full=full, **kwargs) for name, member in self.members.items()} 

1219 # TODO: Include `self.extra`? 

1220 

1221 if full: 

1222 base.update( 

1223 { 

1224 "path": self.path, 

1225 "filepath": self.filepath, 

1226 "relative_filepath": self.relative_filepath, 

1227 "relative_package_filepath": self.relative_package_filepath, 

1228 "is_public": self.is_public, 

1229 "is_deprecated": self.is_deprecated, 

1230 "is_private": self.is_private, 

1231 "is_class_private": self.is_class_private, 

1232 "is_special": self.is_special, 

1233 "is_imported": self.is_imported, 

1234 "is_exported": self.is_exported, 

1235 "is_wildcard_exposed": self.is_wildcard_exposed, 

1236 # TODO: Add these properties? 

1237 # "is_alias": self.is_alias, 

1238 # "is_collection": self.is_collection, 

1239 # "is_module": self.is_module, 

1240 # "is_class": self.is_class, 

1241 # "is_function": self.is_function, 

1242 # "is_attribute": self.is_attribute, 

1243 # "is_type_alias": self.is_type_alias, 

1244 # "is_init_module": self.is_init_module, 

1245 # "is_package": self.is_package, 

1246 # "is_subpackage": self.is_subpackage, 

1247 # "is_namespace_package": self.is_namespace_package, 

1248 # "is_namespace_subpackage": self.is_namespace_subpackage, 

1249 # "has_docstring": self.has_docstring, 

1250 # "has_docstrings": self.has_docstrings, 

1251 }, 

1252 ) 

1253 

1254 return base 

1255 

1256 

1257class Alias(ObjectAliasMixin): 

1258 """This class represents an alias, or indirection, to an object declared in another module. 

1259 

1260 Aliases represent objects that are in the scope of a module or class, 

1261 but were imported from another module. 

1262 

1263 They behave almost exactly like regular objects, to a few exceptions: 

1264 

1265 - line numbers are those of the alias, not the target 

1266 - the path is the alias path, not the canonical one 

1267 - the name can be different from the target's 

1268 - if the target can be resolved, the kind is the target's kind 

1269 - if the target cannot be resolved, the kind becomes [Kind.ALIAS][griffe.Kind] 

1270 """ 

1271 

1272 is_alias: bool = True 

1273 """Always true for aliases.""" 

1274 is_collection: bool = False 

1275 """Always false for aliases. 

1276 

1277 See also: [`ModulesCollection`][griffe.ModulesCollection]. 

1278 """ 

1279 

1280 def __init__( 

1281 self, 

1282 name: str, 

1283 target: str | Object | Alias, 

1284 *, 

1285 lineno: int | None = None, 

1286 endlineno: int | None = None, 

1287 runtime: bool = True, 

1288 parent: Module | Class | Alias | None = None, 

1289 inherited: bool = False, 

1290 ) -> None: 

1291 """Initialize the alias. 

1292 

1293 Parameters: 

1294 name: The alias name. 

1295 target: If it's a string, the target resolution is delayed until accessing the target property. 

1296 If it's an object, or even another alias, the target is immediately set. 

1297 lineno: The alias starting line number. 

1298 endlineno: The alias ending line number. 

1299 runtime: Whether this alias is present at runtime or not. 

1300 parent: The alias parent. 

1301 inherited: Whether this alias wraps an inherited member. 

1302 """ 

1303 self.name: str = name 

1304 """The alias name.""" 

1305 

1306 self.alias_lineno: int | None = lineno 

1307 """The starting line number of the alias.""" 

1308 

1309 self.alias_endlineno: int | None = endlineno 

1310 """The ending line number of the alias.""" 

1311 

1312 self.runtime: bool = runtime 

1313 """Whether this alias is available at runtime.""" 

1314 

1315 self.inherited: bool = inherited 

1316 """Whether this alias represents an inherited member.""" 

1317 

1318 self.public: bool | None = None 

1319 """Whether this alias is public.""" 

1320 

1321 self.deprecated: str | bool | None = None 

1322 """Whether this alias is deprecated (boolean or deprecation message).""" 

1323 

1324 self._parent: Module | Class | Alias | None = parent 

1325 self._passed_through: bool = False 

1326 

1327 self.target_path: str 

1328 """The path of this alias' target.""" 

1329 

1330 if isinstance(target, str): 

1331 self._target: Object | Alias | None = None 

1332 self.target_path = target 

1333 else: 

1334 self._target = target 

1335 self.target_path = target.path 

1336 self._update_target_aliases() 

1337 

1338 def __repr__(self) -> str: 

1339 return f"Alias({self.name!r}, {self.target_path!r})" 

1340 

1341 # Prevent using `__len__`. 

1342 def __bool__(self) -> bool: 

1343 """An alias is always true-ish.""" 

1344 return True 

1345 

1346 def __len__(self) -> int: 

1347 """The length of an alias is always 1.""" 

1348 return 1 

1349 

1350 # SPECIAL PROXIES ------------------------------- 

1351 # The following methods and properties exist on the target(s), 

1352 # but we must handle them in a special way. 

1353 

1354 @property 

1355 def kind(self) -> Kind: 

1356 """The target's kind, or `Kind.ALIAS` if the target cannot be resolved. 

1357 

1358 See also: [`is_kind`][griffe.Alias.is_kind]. 

1359 """ 

1360 # custom behavior to avoid raising exceptions 

1361 try: 

1362 return self.final_target.kind 

1363 except (AliasResolutionError, CyclicAliasError): 

1364 return Kind.ALIAS 

1365 

1366 @property 

1367 def has_docstring(self) -> bool: 

1368 """Whether this alias' target has a non-empty docstring. 

1369 

1370 See also: [`has_docstrings`][griffe.Alias.has_docstrings], 

1371 [`docstring`][griffe.Alias.docstring]. 

1372 """ 

1373 try: 

1374 return self.final_target.has_docstring 

1375 except (AliasResolutionError, CyclicAliasError): 

1376 return False 

1377 

1378 @property 

1379 def has_docstrings(self) -> bool: 

1380 """Whether this alias' target or any of its members has a non-empty docstring. 

1381 

1382 See also: [`has_docstring`][griffe.Alias.has_docstring], 

1383 [`docstring`][griffe.Alias.docstring]. 

1384 """ 

1385 try: 

1386 return self.final_target.has_docstrings 

1387 except (AliasResolutionError, CyclicAliasError): 

1388 return False 

1389 

1390 @property 

1391 def parent(self) -> Module | Class | Alias | None: 

1392 """The parent of this alias.""" 

1393 return self._parent 

1394 

1395 @parent.setter 

1396 def parent(self, value: Module | Class | Alias) -> None: 

1397 self._parent = value 

1398 self._update_target_aliases() 

1399 

1400 @property 

1401 def path(self) -> str: 

1402 """The dotted path / import path of this object. 

1403 

1404 See also: [`canonical_path`][griffe.Alias.canonical_path]. 

1405 """ 

1406 return f"{self.parent.path}.{self.name}" # type: ignore[union-attr] 

1407 

1408 @property 

1409 def modules_collection(self) -> ModulesCollection: 

1410 """The modules collection attached to the alias parents.""" 

1411 # No need to forward to the target. 

1412 return self.parent.modules_collection # type: ignore[union-attr] 

1413 

1414 @property 

1415 def members(self) -> dict[str, Object | Alias]: 

1416 """The target's members (modules, classes, functions, attributes, type aliases). 

1417 

1418 See also: [`inherited_members`][griffe.Alias.inherited_members], 

1419 [`get_member`][griffe.Alias.get_member], 

1420 [`set_member`][griffe.Alias.set_member], 

1421 [`filter_members`][griffe.Alias.filter_members]. 

1422 """ 

1423 final_target = self.final_target 

1424 

1425 # We recreate aliases to maintain a correct hierarchy, 

1426 # and therefore correct paths. The path of an alias member 

1427 # should be the path of the alias plus the member's name, 

1428 # not the original member's path. 

1429 return { 

1430 name: Alias(name, target=member, parent=self, inherited=False) 

1431 for name, member in final_target.members.items() 

1432 } 

1433 

1434 @property 

1435 def inherited_members(self) -> dict[str, Alias]: 

1436 """Members that are inherited from base classes. 

1437 

1438 Each inherited member of the target will be wrapped in an alias, 

1439 to preserve correct object access paths. 

1440 

1441 This method is part of the consumer API: 

1442 do not use when producing Griffe trees! 

1443 

1444 See also: [`members`][griffe.Alias.members]. 

1445 """ 

1446 final_target = self.final_target 

1447 

1448 # We recreate aliases to maintain a correct hierarchy, 

1449 # and therefore correct paths. The path of an alias member 

1450 # should be the path of the alias plus the member's name, 

1451 # not the original member's path. 

1452 return { 

1453 name: Alias(name, target=member, parent=self, inherited=True) 

1454 for name, member in final_target.inherited_members.items() 

1455 } 

1456 

1457 def as_json(self, *, full: bool = False, **kwargs: Any) -> str: 

1458 """Return this target's data as a JSON string. 

1459 

1460 See also: [`as_dict`][griffe.Alias.as_dict]. 

1461 

1462 Parameters: 

1463 full: Whether to return full info, or just base info. 

1464 **kwargs: Additional serialization options passed to encoder. 

1465 

1466 Returns: 

1467 A JSON string. 

1468 """ 

1469 try: 

1470 return self.final_target.as_json(full=full, **kwargs) 

1471 except (AliasResolutionError, CyclicAliasError): 

1472 return super().as_json(full=full, **kwargs) 

1473 

1474 # GENERIC OBJECT PROXIES -------------------------------- 

1475 # The following methods and properties exist on the target(s). 

1476 # We first try to reach the final target, triggering alias resolution errors 

1477 # and cyclic aliases errors early. We avoid recursing in the alias chain. 

1478 

1479 @property 

1480 def extra(self) -> dict: 

1481 """Namespaced dictionaries storing extra metadata for this object, used by extensions.""" 

1482 return self.final_target.extra 

1483 

1484 @property 

1485 def lineno(self) -> int | None: 

1486 """The starting line number of the target object. 

1487 

1488 See also: [`endlineno`][griffe.Alias.endlineno]. 

1489 """ 

1490 return self.final_target.lineno 

1491 

1492 @lineno.setter 

1493 def lineno(self, lineno: int | None) -> None: 

1494 self.final_target.lineno = lineno 

1495 

1496 @property 

1497 def endlineno(self) -> int | None: 

1498 """The ending line number of the target object. 

1499 

1500 See also: [`lineno`][griffe.Alias.lineno]. 

1501 """ 

1502 return self.final_target.endlineno 

1503 

1504 @endlineno.setter 

1505 def endlineno(self, endlineno: int | None) -> None: 

1506 self.final_target.endlineno = endlineno 

1507 

1508 @property 

1509 def docstring(self) -> Docstring | None: 

1510 """The target docstring. 

1511 

1512 See also: [`has_docstring`][griffe.Alias.has_docstring], 

1513 [`has_docstrings`][griffe.Alias.has_docstrings]. 

1514 """ 

1515 return self.final_target.docstring 

1516 

1517 @docstring.setter 

1518 def docstring(self, docstring: Docstring | None) -> None: 

1519 self.final_target.docstring = docstring 

1520 

1521 @property 

1522 def type_parameters(self) -> TypeParameters: 

1523 """The target type parameters.""" 

1524 return self.final_target.type_parameters 

1525 

1526 @property 

1527 def labels(self) -> set[str]: 

1528 """The target labels (`property`, `dataclass`, etc.). 

1529 

1530 See also: [`has_labels`][griffe.Alias.has_labels]. 

1531 """ 

1532 return self.final_target.labels 

1533 

1534 @property 

1535 def imports(self) -> dict[str, str]: 

1536 """The other objects imported by this alias' target. 

1537 

1538 Keys are the names within the object (`from ... import ... as AS_NAME`), 

1539 while the values are the actual names of the objects (`from ... import REAL_NAME as ...`). 

1540 

1541 See also: [`is_imported`][griffe.Alias.is_imported]. 

1542 """ 

1543 return self.final_target.imports 

1544 

1545 @property 

1546 def exports(self) -> list[str | ExprName] | None: 

1547 """The names of the objects exported by this (module) object through the `__all__` variable. 

1548 

1549 Exports can contain string (object names) or resolvable names, 

1550 like other lists of exports coming from submodules: 

1551 

1552 ```python 

1553 from .submodule import __all__ as submodule_all 

1554 

1555 __all__ = ["hello", *submodule_all] 

1556 ``` 

1557 

1558 Exports get expanded by the loader before it expands wildcards and resolves aliases. 

1559 

1560 See also: [`GriffeLoader.expand_exports`][griffe.GriffeLoader.expand_exports]. 

1561 """ 

1562 return self.final_target.exports 

1563 

1564 @property 

1565 def aliases(self) -> dict[str, Alias]: 

1566 """The aliases pointing to this object.""" 

1567 return self.final_target.aliases 

1568 

1569 def is_kind(self, kind: str | Kind | set[str | Kind]) -> bool: 

1570 """Tell if this object is of the given kind. 

1571 

1572 See also: [`is_module`][griffe.Alias.is_module], 

1573 [`is_class`][griffe.Alias.is_class], 

1574 [`is_function`][griffe.Alias.is_function], 

1575 [`is_attribute`][griffe.Alias.is_attribute], 

1576 [`is_type_alias`][griffe.Alias.is_type_alias], 

1577 [`is_alias`][griffe.Alias.is_alias]. 

1578 

1579 Parameters: 

1580 kind: An instance or set of kinds (strings or enumerations). 

1581 

1582 Raises: 

1583 ValueError: When an empty set is given as argument. 

1584 

1585 Returns: 

1586 True or False. 

1587 """ 

1588 return self.final_target.is_kind(kind) 

1589 

1590 @property 

1591 def is_module(self) -> bool: 

1592 """Whether this object is a module. 

1593 

1594 See also: [`is_init_module`][griffe.Alias.is_init_module]. 

1595 [`is_class`][griffe.Alias.is_class], 

1596 [`is_function`][griffe.Alias.is_function], 

1597 [`is_attribute`][griffe.Alias.is_attribute], 

1598 [`is_type_alias`][griffe.Alias.is_type_alias], 

1599 [`is_alias`][griffe.Alias.is_alias], 

1600 [`is_kind`][griffe.Alias.is_kind]. 

1601 """ 

1602 return self.final_target.is_module 

1603 

1604 @property 

1605 def is_class(self) -> bool: 

1606 """Whether this object is a class. 

1607 

1608 See also: [`is_module`][griffe.Alias.is_module], 

1609 [`is_function`][griffe.Alias.is_function], 

1610 [`is_attribute`][griffe.Alias.is_attribute], 

1611 [`is_type_alias`][griffe.Alias.is_type_alias], 

1612 [`is_alias`][griffe.Alias.is_alias], 

1613 [`is_kind`][griffe.Alias.is_kind]. 

1614 """ 

1615 return self.final_target.is_class 

1616 

1617 @property 

1618 def is_function(self) -> bool: 

1619 """Whether this object is a function. 

1620 

1621 See also: [`is_module`][griffe.Alias.is_module], 

1622 [`is_class`][griffe.Alias.is_class], 

1623 [`is_attribute`][griffe.Alias.is_attribute], 

1624 [`is_type_alias`][griffe.Alias.is_type_alias], 

1625 [`is_alias`][griffe.Alias.is_alias], 

1626 [`is_kind`][griffe.Alias.is_kind]. 

1627 """ 

1628 return self.final_target.is_function 

1629 

1630 @property 

1631 def is_attribute(self) -> bool: 

1632 """Whether this object is an attribute. 

1633 

1634 See also: [`is_module`][griffe.Alias.is_module], 

1635 [`is_class`][griffe.Alias.is_class], 

1636 [`is_function`][griffe.Alias.is_function], 

1637 [`is_type_alias`][griffe.Alias.is_type_alias], 

1638 [`is_alias`][griffe.Alias.is_alias], 

1639 [`is_kind`][griffe.Alias.is_kind]. 

1640 """ 

1641 return self.final_target.is_attribute 

1642 

1643 @property 

1644 def is_type_alias(self) -> bool: 

1645 """Whether this object is a type alias. 

1646 

1647 See also: [`is_module`][griffe.Alias.is_module], 

1648 [`is_class`][griffe.Alias.is_class], 

1649 [`is_function`][griffe.Alias.is_function], 

1650 [`is_attribute`][griffe.Alias.is_attribute], 

1651 [`is_alias`][griffe.Alias.is_alias], 

1652 [`is_kind`][griffe.Alias.is_kind]. 

1653 """ 

1654 return self.final_target.is_type_alias 

1655 

1656 def has_labels(self, *labels: str) -> bool: 

1657 """Tell if this object has all the given labels. 

1658 

1659 See also: [`labels`][griffe.Alias.labels]. 

1660 

1661 Parameters: 

1662 *labels: Labels that must be present. 

1663 

1664 Returns: 

1665 True or False. 

1666 """ 

1667 return self.final_target.has_labels(*labels) 

1668 

1669 def filter_members(self, *predicates: Callable[[Object | Alias], bool]) -> dict[str, Object | Alias]: 

1670 """Filter and return members based on predicates. 

1671 

1672 See also: [`members`][griffe.Alias.members], 

1673 [`get_member`][griffe.Alias.get_member], 

1674 [`set_member`][griffe.Alias.set_member]. 

1675 

1676 Parameters: 

1677 *predicates: A list of predicates, i.e. callables accepting a member as argument and returning a boolean. 

1678 

1679 Returns: 

1680 A dictionary of members. 

1681 """ 

1682 return self.final_target.filter_members(*predicates) 

1683 

1684 @property 

1685 def module(self) -> Module: 

1686 """The parent module of this object. 

1687 

1688 See also: [`package`][griffe.Alias.package]. 

1689 

1690 Raises: 

1691 ValueError: When the object is not a module and does not have a parent. 

1692 """ 

1693 return self.final_target.module 

1694 

1695 @property 

1696 def package(self) -> Module: 

1697 """The absolute top module (the package) of this object. 

1698 

1699 See also: [`module`][griffe.Alias.module]. 

1700 """ 

1701 return self.final_target.package 

1702 

1703 @property 

1704 def filepath(self) -> Path | list[Path]: 

1705 """The file path (or directory list for namespace packages) where this object was defined. 

1706 

1707 See also: [`relative_filepath`][griffe.Alias.relative_filepath], 

1708 [`relative_package_filepath`][griffe.Alias.relative_package_filepath]. 

1709 """ 

1710 return self.final_target.filepath 

1711 

1712 @property 

1713 def relative_filepath(self) -> Path: 

1714 """The file path where this object was defined, relative to the current working directory. 

1715 

1716 If this object's file path is not relative to the current working directory, return its absolute path. 

1717 

1718 See also: [`filepath`][griffe.Alias.filepath], 

1719 [`relative_package_filepath`][griffe.Alias.relative_package_filepath]. 

1720 

1721 Raises: 

1722 ValueError: When the relative path could not be computed. 

1723 """ 

1724 return self.final_target.relative_filepath 

1725 

1726 @property 

1727 def relative_package_filepath(self) -> Path: 

1728 """The file path where this object was defined, relative to the top module path. 

1729 

1730 See also: [`filepath`][griffe.Alias.filepath], 

1731 [`relative_filepath`][griffe.Alias.relative_filepath]. 

1732 

1733 Raises: 

1734 ValueError: When the relative path could not be computed. 

1735 """ 

1736 return self.final_target.relative_package_filepath 

1737 

1738 @property 

1739 def canonical_path(self) -> str: 

1740 """The full dotted path of this object. 

1741 

1742 The canonical path is the path where the object was defined (not imported). 

1743 

1744 See also: [`path`][griffe.Alias.path]. 

1745 """ 

1746 return self.final_target.canonical_path 

1747 

1748 @property 

1749 def lines_collection(self) -> LinesCollection: 

1750 """The lines collection attached to this object or its parents. 

1751 

1752 See also: [`lines`][griffe.Alias.lines], 

1753 [`source`][griffe.Alias.source]. 

1754 

1755 Raises: 

1756 ValueError: When no modules collection can be found in the object or its parents. 

1757 """ 

1758 return self.final_target.lines_collection 

1759 

1760 @property 

1761 def lines(self) -> list[str]: 

1762 """The lines containing the source of this object. 

1763 

1764 See also: [`source`][griffe.Alias.source], 

1765 [`lines_collection`][griffe.Alias.lines_collection]. 

1766 """ 

1767 return self.final_target.lines 

1768 

1769 @property 

1770 def source(self) -> str: 

1771 """The source code of this object. 

1772 

1773 See also: [`lines`][griffe.Alias.lines], 

1774 [`lines_collection`][griffe.Alias.lines_collection]. 

1775 """ 

1776 return self.final_target.source 

1777 

1778 def resolve(self, name: str) -> str: 

1779 """Resolve a name within this object's and parents' scope. 

1780 

1781 Parameters: 

1782 name: The name to resolve. 

1783 

1784 Raises: 

1785 NameResolutionError: When the name could not be resolved. 

1786 

1787 Returns: 

1788 The resolved name. 

1789 """ 

1790 return self.final_target.resolve(name) 

1791 

1792 # SPECIFIC MODULE/CLASS/FUNCTION/ATTRIBUTE/TYPE ALIAS PROXIES --------------- 

1793 # These methods and properties exist on targets of specific kind. 

1794 # We first try to reach the final target, triggering alias resolution errors 

1795 # and cyclic aliases errors early. We avoid recursing in the alias chain. 

1796 

1797 @property 

1798 def _filepath(self) -> Path | list[Path] | None: 

1799 return cast("Module", self.final_target)._filepath 

1800 

1801 @property 

1802 def bases(self) -> list[Expr | str]: 

1803 """The class bases. 

1804 

1805 See also: [`Class`][griffe.Class], 

1806 [`resolved_bases`][griffe.Alias.resolved_bases], 

1807 [`mro`][griffe.Alias.mro]. 

1808 """ 

1809 return cast("Class", self.final_target).bases 

1810 

1811 @property 

1812 def decorators(self) -> list[Decorator]: 

1813 """The class/function decorators. 

1814 

1815 See also: [`Function`][griffe.Function], 

1816 [`Class`][griffe.Class]. 

1817 """ 

1818 return cast("Union[Class, Function]", self.target).decorators 

1819 

1820 @property 

1821 def imports_future_annotations(self) -> bool: 

1822 """Whether this module import future annotations.""" 

1823 return cast("Module", self.final_target).imports_future_annotations 

1824 

1825 @property 

1826 def is_init_module(self) -> bool: 

1827 """Whether this module is an `__init__.py` module. 

1828 

1829 See also: [`is_module`][griffe.Alias.is_module]. 

1830 """ 

1831 return cast("Module", self.final_target).is_init_module 

1832 

1833 @property 

1834 def is_package(self) -> bool: 

1835 """Whether this module is a package (top module). 

1836 

1837 See also: [`is_subpackage`][griffe.Alias.is_subpackage]. 

1838 """ 

1839 return cast("Module", self.final_target).is_package 

1840 

1841 @property 

1842 def is_subpackage(self) -> bool: 

1843 """Whether this module is a subpackage. 

1844 

1845 See also: [`is_package`][griffe.Alias.is_package]. 

1846 """ 

1847 return cast("Module", self.final_target).is_subpackage 

1848 

1849 @property 

1850 def is_namespace_package(self) -> bool: 

1851 """Whether this module is a namespace package (top folder, no `__init__.py`). 

1852 

1853 See also: [`is_namespace_subpackage`][griffe.Alias.is_namespace_subpackage]. 

1854 """ 

1855 return cast("Module", self.final_target).is_namespace_package 

1856 

1857 @property 

1858 def is_namespace_subpackage(self) -> bool: 

1859 """Whether this module is a namespace subpackage. 

1860 

1861 See also: [`is_namespace_package`][griffe.Alias.is_namespace_package]. 

1862 """ 

1863 return cast("Module", self.final_target).is_namespace_subpackage 

1864 

1865 @property 

1866 def overloads(self) -> dict[str, list[Function]] | list[Function] | None: 

1867 """The overloaded signatures declared in this class/module or for this function.""" 

1868 return cast("Union[Module, Class, Function]", self.final_target).overloads 

1869 

1870 @overloads.setter 

1871 def overloads(self, overloads: list[Function] | None) -> None: 

1872 cast("Union[Module, Class, Function]", self.final_target).overloads = overloads 

1873 

1874 @property 

1875 def parameters(self) -> Parameters: 

1876 """The parameters of the current function or `__init__` method for classes. 

1877 

1878 This property can fetch inherited members, 

1879 and therefore is part of the consumer API: 

1880 do not use when producing Griffe trees! 

1881 """ 

1882 return cast("Union[Class, Function]", self.final_target).parameters 

1883 

1884 @property 

1885 def returns(self) -> str | Expr | None: 

1886 """The function return type annotation.""" 

1887 return cast("Function", self.final_target).returns 

1888 

1889 @returns.setter 

1890 def returns(self, returns: str | Expr | None) -> None: 

1891 cast("Function", self.final_target).returns = returns 

1892 

1893 @property 

1894 def setter(self) -> Function | None: 

1895 """The setter linked to this function (property).""" 

1896 return cast("Attribute", self.final_target).setter 

1897 

1898 @property 

1899 def deleter(self) -> Function | None: 

1900 """The deleter linked to this function (property).""" 

1901 return cast("Attribute", self.final_target).deleter 

1902 

1903 @property 

1904 def value(self) -> str | Expr | None: 

1905 """The attribute or type alias value.""" 

1906 return cast("Union[Attribute, TypeAlias]", self.final_target).value 

1907 

1908 @value.setter 

1909 def value(self, value: str | Expr | None) -> None: 

1910 cast("Attribute", self.final_target).value = value 

1911 

1912 @property 

1913 def annotation(self) -> str | Expr | None: 

1914 """The attribute type annotation.""" 

1915 return cast("Attribute", self.final_target).annotation 

1916 

1917 @annotation.setter 

1918 def annotation(self, annotation: str | Expr | None) -> None: 

1919 cast("Attribute", self.final_target).annotation = annotation 

1920 

1921 @property 

1922 def resolved_bases(self) -> list[Object]: 

1923 """Resolved class bases. 

1924 

1925 This method is part of the consumer API: 

1926 do not use when producing Griffe trees! 

1927 """ 

1928 return cast("Class", self.final_target).resolved_bases 

1929 

1930 def mro(self) -> list[Class]: 

1931 """Return a list of classes in order corresponding to Python's MRO.""" 

1932 return cast("Class", self.final_target).mro() 

1933 

1934 def signature(self, *, return_type: bool = False, name: str | None = None) -> str: 

1935 """Construct the class/function signature. 

1936 

1937 Parameters: 

1938 return_type: Whether to include the return type in the signature. 

1939 name: The name of the class/function to use in the signature. 

1940 

1941 Returns: 

1942 A string representation of the class/function signature. 

1943 """ 

1944 return cast("Union[Class, Function]", self.final_target).signature(return_type=return_type, name=name) 

1945 

1946 # SPECIFIC ALIAS METHOD AND PROPERTIES ----------------- 

1947 # These methods and properties do not exist on targets, 

1948 # they are specific to aliases. 

1949 

1950 @property 

1951 def target(self) -> Object | Alias: 

1952 """The resolved target (actual object), if possible. 

1953 

1954 Upon accessing this property, if the target is not already resolved, 

1955 a lookup is done using the modules collection to find the target. 

1956 

1957 See also: [`final_target`][griffe.Alias.final_target], 

1958 [`resolve_target`][griffe.Alias.resolve_target], 

1959 [`resolved`][griffe.Alias.resolved]. 

1960 """ 

1961 if not self.resolved: 

1962 self.resolve_target() 

1963 return self._target # type: ignore[return-value] 

1964 

1965 @target.setter 

1966 def target(self, value: Object | Alias) -> None: 

1967 if value is self or value.path == self.path: 

1968 raise CyclicAliasError([self.target_path]) 

1969 self._target = value 

1970 self.target_path = value.path 

1971 if self.parent is not None: 

1972 self._target.aliases[self.path] = self 

1973 

1974 @property 

1975 def final_target(self) -> Object: 

1976 """The final, resolved target, if possible. 

1977 

1978 This will iterate through the targets until a non-alias object is found. 

1979 

1980 See also: [`target`][griffe.Alias.target], 

1981 [`resolve_target`][griffe.Alias.resolve_target], 

1982 [`resolved`][griffe.Alias.resolved]. 

1983 """ 

1984 # Here we quickly iterate on the alias chain, 

1985 # remembering which path we've seen already to detect cycles. 

1986 

1987 # The cycle detection is needed because alias chains can be created 

1988 # as already resolved, and can contain cycles. 

1989 

1990 # Using a dict as an ordered set. 

1991 paths_seen: dict[str, None] = {} 

1992 target = self 

1993 while target.is_alias: 

1994 if target.path in paths_seen: 1994 ↛ 1995line 1994 didn't jump to line 1995 because the condition on line 1994 was never true

1995 raise CyclicAliasError([*paths_seen, target.path]) 

1996 paths_seen[target.path] = None 

1997 target = target.target # type: ignore[assignment] 

1998 return target # type: ignore[return-value] 

1999 

2000 def resolve_target(self) -> None: 

2001 """Resolve the target. 

2002 

2003 See also: [`target`][griffe.Alias.target], 

2004 [`final_target`][griffe.Alias.final_target], 

2005 [`resolved`][griffe.Alias.resolved]. 

2006 

2007 Raises: 

2008 AliasResolutionError: When the target cannot be resolved. 

2009 It happens when the target does not exist, 

2010 or could not be loaded (unhandled dynamic object?), 

2011 or when the target is from a module that was not loaded 

2012 and added to the collection. 

2013 CyclicAliasError: When the resolved target is the alias itself. 

2014 """ 

2015 # Here we try to resolve the whole alias chain recursively. 

2016 # We detect cycles by setting a "passed through" state variable 

2017 # on each alias as we pass through it. Passing a second time 

2018 # through an alias will raise a CyclicAliasError. 

2019 

2020 # If a single link of the chain cannot be resolved, 

2021 # the whole chain stays unresolved. This prevents 

2022 # bad surprises later, in code that checks if 

2023 # an alias is resolved by checking only 

2024 # the first link of the chain. 

2025 if self._passed_through: 2025 ↛ 2026line 2025 didn't jump to line 2026 because the condition on line 2025 was never true

2026 raise CyclicAliasError([self.target_path]) 

2027 self._passed_through = True 

2028 try: 

2029 self._resolve_target() 

2030 finally: 

2031 self._passed_through = False 

2032 

2033 def _resolve_target(self) -> None: 

2034 try: 

2035 resolved = self.modules_collection.get_member(self.target_path) 

2036 except KeyError as error: 

2037 raise AliasResolutionError(self) from error 

2038 if resolved is self: 2038 ↛ 2039line 2038 didn't jump to line 2039 because the condition on line 2038 was never true

2039 raise CyclicAliasError([self.target_path]) 

2040 if resolved.is_alias and not resolved.resolved: 

2041 try: 

2042 resolved.resolve_target() 

2043 except CyclicAliasError as error: 

2044 raise CyclicAliasError([self.target_path, *error.chain]) from error 

2045 self._target = resolved 

2046 if self.parent is not None: 2046 ↛ exitline 2046 didn't return from function '_resolve_target' because the condition on line 2046 was always true

2047 self._target.aliases[self.path] = self 

2048 

2049 def _update_target_aliases(self) -> None: 

2050 with suppress(AttributeError, AliasResolutionError, CyclicAliasError): 

2051 self._target.aliases[self.path] = self # type: ignore[union-attr] 

2052 

2053 @property 

2054 def resolved(self) -> bool: 

2055 """Whether this alias' target is resolved.""" 

2056 return self._target is not None 

2057 

2058 @property 

2059 def wildcard(self) -> str | None: 

2060 """The module on which the wildcard import is performed (if any). 

2061 

2062 See also: [`GriffeLoader.expand_wildcards`][griffe.GriffeLoader.expand_wildcards]. 

2063 """ 

2064 if self.name.endswith("/*"): 

2065 return self.target_path 

2066 return None 

2067 

2068 def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002 

2069 """Return this alias' data as a dictionary. 

2070 

2071 See also: [`as_json`][griffe.Alias.as_json]. 

2072 

2073 Parameters: 

2074 full: Whether to return full info, or just base info. 

2075 **kwargs: Additional serialization options. 

2076 

2077 Returns: 

2078 A dictionary. 

2079 """ 

2080 base: dict[str, Any] = { 

2081 "kind": Kind.ALIAS, 

2082 "name": self.name, 

2083 "target_path": self.target_path, 

2084 "runtime": self.runtime, 

2085 "inherited": self.inherited, 

2086 } 

2087 

2088 if self.public is not None: 2088 ↛ 2089line 2088 didn't jump to line 2089 because the condition on line 2088 was never true

2089 base["public"] = self.public 

2090 if self.deprecated is not None: 2090 ↛ 2091line 2090 didn't jump to line 2091 because the condition on line 2090 was never true

2091 base["deprecated"] = self.deprecated 

2092 if self.alias_lineno: 2092 ↛ 2094line 2092 didn't jump to line 2094 because the condition on line 2092 was always true

2093 base["lineno"] = self.alias_lineno 

2094 if self.alias_endlineno: 2094 ↛ 2097line 2094 didn't jump to line 2097 because the condition on line 2094 was always true

2095 base["endlineno"] = self.alias_endlineno 

2096 

2097 if full: 

2098 base.update( 

2099 { 

2100 "path": self.path, 

2101 "is_public": self.is_public, 

2102 "is_deprecated": self.is_deprecated, 

2103 "is_private": self.is_private, 

2104 "is_class_private": self.is_class_private, 

2105 "is_special": self.is_special, 

2106 "is_imported": self.is_imported, 

2107 "is_exported": self.is_exported, 

2108 "is_wildcard_exposed": self.is_wildcard_exposed, 

2109 }, 

2110 ) 

2111 

2112 return base 

2113 

2114 

2115class Module(Object): 

2116 """The class representing a Python module.""" 

2117 

2118 kind = Kind.MODULE 

2119 

2120 def __init__(self, *args: Any, filepath: Path | list[Path] | None = None, **kwargs: Any) -> None: 

2121 """Initialize the module. 

2122 

2123 Parameters: 

2124 *args: See [`griffe.Object`][]. 

2125 filepath: The module file path (directory for namespace [sub]packages, none for builtin modules). 

2126 **kwargs: See [`griffe.Object`][]. 

2127 """ 

2128 super().__init__(*args, **kwargs) 

2129 self._filepath: Path | list[Path] | None = filepath 

2130 self.overloads: dict[str, list[Function]] = defaultdict(list) 

2131 """The overloaded signatures declared in this module.""" 

2132 

2133 def __repr__(self) -> str: 

2134 try: 

2135 return f"Module({self.filepath!r})" 

2136 except BuiltinModuleError: 

2137 return f"Module({self.name!r})" 

2138 

2139 @property 

2140 def filepath(self) -> Path | list[Path]: 

2141 """The file path of this module. 

2142 

2143 Raises: 

2144 BuiltinModuleError: When the instance filepath is None. 

2145 """ 

2146 if self._filepath is None: 

2147 raise BuiltinModuleError(self.name) 

2148 return self._filepath 

2149 

2150 @property 

2151 def imports_future_annotations(self) -> bool: 

2152 """Whether this module import future annotations.""" 

2153 return ( 

2154 "annotations" in self.members 

2155 and self.members["annotations"].is_alias 

2156 and self.members["annotations"].target_path == "__future__.annotations" # type: ignore[union-attr] 

2157 ) 

2158 

2159 @property 

2160 def is_init_module(self) -> bool: 

2161 """Whether this module is an `__init__.py` module. 

2162 

2163 See also: [`is_module`][griffe.Module.is_module]. 

2164 """ 

2165 if isinstance(self.filepath, list): 2165 ↛ 2166line 2165 didn't jump to line 2166 because the condition on line 2165 was never true

2166 return False 

2167 try: 

2168 return self.filepath.name.split(".", 1)[0] == "__init__" 

2169 except BuiltinModuleError: 

2170 return False 

2171 

2172 @property 

2173 def is_package(self) -> bool: 

2174 """Whether this module is a package (top module). 

2175 

2176 See also: [`is_subpackage`][griffe.Module.is_subpackage]. 

2177 """ 

2178 return not bool(self.parent) and self.is_init_module 

2179 

2180 @property 

2181 def is_subpackage(self) -> bool: 

2182 """Whether this module is a subpackage. 

2183 

2184 See also: [`is_package`][griffe.Module.is_package]. 

2185 """ 

2186 return bool(self.parent) and self.is_init_module 

2187 

2188 @property 

2189 def is_namespace_package(self) -> bool: 

2190 """Whether this module is a namespace package (top folder, no `__init__.py`). 

2191 

2192 See also: [`is_namespace_subpackage`][griffe.Module.is_namespace_subpackage]. 

2193 """ 

2194 try: 

2195 return self.parent is None and isinstance(self.filepath, list) 

2196 except BuiltinModuleError: 

2197 return False 

2198 

2199 @property 

2200 def is_namespace_subpackage(self) -> bool: 

2201 """Whether this module is a namespace subpackage. 

2202 

2203 See also: [`is_namespace_package`][griffe.Module.is_namespace_package]. 

2204 """ 

2205 try: 

2206 return ( 

2207 self.parent is not None 

2208 and isinstance(self.filepath, list) 

2209 and ( 

2210 cast("Module", self.parent).is_namespace_package 

2211 or cast("Module", self.parent).is_namespace_subpackage 

2212 ) 

2213 ) 

2214 except BuiltinModuleError: 

2215 return False 

2216 

2217 def as_dict(self, **kwargs: Any) -> dict[str, Any]: 

2218 """Return this module's data as a dictionary. 

2219 

2220 See also: [`as_json`][griffe.Module.as_json]. 

2221 

2222 Parameters: 

2223 **kwargs: Additional serialization options. 

2224 

2225 Returns: 

2226 A dictionary. 

2227 """ 

2228 base = super().as_dict(**kwargs) 

2229 if isinstance(self._filepath, list): 2229 ↛ 2230line 2229 didn't jump to line 2230 because the condition on line 2229 was never true

2230 base["filepath"] = [str(path) for path in self._filepath] 

2231 elif self._filepath: 2231 ↛ 2234line 2231 didn't jump to line 2234 because the condition on line 2231 was always true

2232 base["filepath"] = str(self._filepath) 

2233 else: 

2234 base["filepath"] = None 

2235 return base 

2236 

2237 

2238class Class(Object): 

2239 """The class representing a Python class.""" 

2240 

2241 kind = Kind.CLASS 

2242 

2243 def __init__( 

2244 self, 

2245 *args: Any, 

2246 bases: Sequence[Expr | str] | None = None, 

2247 decorators: list[Decorator] | None = None, 

2248 **kwargs: Any, 

2249 ) -> None: 

2250 """Initialize the class. 

2251 

2252 Parameters: 

2253 *args: See [`griffe.Object`][]. 

2254 bases: The list of base classes, if any. 

2255 decorators: The class decorators, if any. 

2256 **kwargs: See [`griffe.Object`][]. 

2257 """ 

2258 super().__init__(*args, **kwargs) 

2259 

2260 self.bases: list[Expr | str] = list(bases) if bases else [] 

2261 """The class bases. 

2262 

2263 See also: [`resolved_bases`][griffe.Class.resolved_bases], 

2264 [`mro`][griffe.Class.mro]. 

2265 """ 

2266 

2267 self.decorators: list[Decorator] = decorators or [] 

2268 """The class decorators.""" 

2269 

2270 self.overloads: dict[str, list[Function]] = defaultdict(list) 

2271 """The overloaded signatures declared in this class.""" 

2272 

2273 @property 

2274 def parameters(self) -> Parameters: 

2275 """The parameters of this class' `__init__` method, if any. 

2276 

2277 This property fetches inherited members, 

2278 and therefore is part of the consumer API: 

2279 do not use when producing Griffe trees! 

2280 """ 

2281 try: 

2282 return self.all_members["__init__"].parameters # type: ignore[union-attr] 

2283 except KeyError: 

2284 return Parameters() 

2285 

2286 def signature(self, *, return_type: bool = False, name: str | None = None) -> str: 

2287 """Construct the class signature. 

2288 

2289 Parameters: 

2290 return_type: Whether to include the return type in the signature. 

2291 name: The name of the class to use in the signature. 

2292 

2293 Returns: 

2294 A string representation of the class signature. 

2295 """ 

2296 all_members = self.all_members 

2297 if "__init__" in all_members: 2297 ↛ 2301line 2297 didn't jump to line 2301 because the condition on line 2297 was always true

2298 init = all_members["__init__"] 

2299 if isinstance(init, Function): 2299 ↛ 2301line 2299 didn't jump to line 2301 because the condition on line 2299 was always true

2300 return init.signature(return_type=return_type, name=name or self.name) 

2301 return "" 

2302 

2303 @property 

2304 def resolved_bases(self) -> list[Object]: 

2305 """Resolved class bases. 

2306 

2307 This method is part of the consumer API: 

2308 do not use when producing Griffe trees! 

2309 

2310 See also: [`bases`][griffe.Class.bases], 

2311 [`mro`][griffe.Class.mro]. 

2312 """ 

2313 resolved_bases = [] 

2314 for base in self.bases: 

2315 base_path = base if isinstance(base, str) else base.canonical_path 

2316 try: 

2317 resolved_base = self.modules_collection.get_member(base_path) 

2318 if resolved_base.is_alias: 

2319 resolved_base = resolved_base.final_target 

2320 except (AliasResolutionError, CyclicAliasError, KeyError): 

2321 logger.debug("Base class %s is not loaded, or not static, it cannot be resolved", base_path) 

2322 else: 

2323 resolved_bases.append(resolved_base) 

2324 return resolved_bases 

2325 

2326 def _mro(self, seen: tuple[str, ...] = ()) -> list[Class]: 

2327 seen = (*seen, self.path) 

2328 bases: list[Class] = [base for base in self.resolved_bases if base.is_class] # type: ignore[misc] 

2329 if not bases: 

2330 return [self] 

2331 for base in bases: 

2332 if base.path in seen: 

2333 cycle = " -> ".join(seen) + f" -> {base.path}" 

2334 raise ValueError(f"Cannot compute C3 linearization, inheritance cycle detected: {cycle}") 

2335 return [self, *c3linear_merge(*[base._mro(seen) for base in bases], bases)] 

2336 

2337 def mro(self) -> list[Class]: 

2338 """Return a list of classes in order corresponding to Python's MRO. 

2339 

2340 See also: [`bases`][griffe.Class.bases], 

2341 [`resolved_bases`][griffe.Class.resolved_bases]. 

2342 """ 

2343 return self._mro()[1:] # Remove self. 

2344 

2345 def as_dict(self, **kwargs: Any) -> dict[str, Any]: 

2346 """Return this class' data as a dictionary. 

2347 

2348 See also: [`as_json`][griffe.Class.as_json]. 

2349 

2350 Parameters: 

2351 **kwargs: Additional serialization options. 

2352 

2353 Returns: 

2354 A dictionary. 

2355 """ 

2356 base = super().as_dict(**kwargs) 

2357 base["bases"] = self.bases 

2358 base["decorators"] = [dec.as_dict(**kwargs) for dec in self.decorators] 

2359 return base 

2360 

2361 

2362class Function(Object): 

2363 """The class representing a Python function.""" 

2364 

2365 kind = Kind.FUNCTION 

2366 

2367 def __init__( 

2368 self, 

2369 *args: Any, 

2370 parameters: Parameters | None = None, 

2371 returns: str | Expr | None = None, 

2372 decorators: list[Decorator] | None = None, 

2373 **kwargs: Any, 

2374 ) -> None: 

2375 """Initialize the function. 

2376 

2377 Parameters: 

2378 *args: See [`griffe.Object`][]. 

2379 parameters: The function parameters. 

2380 returns: The function return annotation. 

2381 decorators: The function decorators, if any. 

2382 **kwargs: See [`griffe.Object`][]. 

2383 """ 

2384 super().__init__(*args, **kwargs) 

2385 self.parameters: Parameters = parameters or Parameters() 

2386 """The function parameters.""" 

2387 self.returns: str | Expr | None = returns 

2388 """The function return type annotation.""" 

2389 self.decorators: list[Decorator] = decorators or [] 

2390 """The function decorators.""" 

2391 self.overloads: list[Function] | None = None 

2392 """The overloaded signatures of this function.""" 

2393 

2394 for parameter in self.parameters: 

2395 parameter.function = self 

2396 

2397 @property 

2398 def annotation(self) -> str | Expr | None: 

2399 """The type annotation of the returned value.""" 

2400 return self.returns 

2401 

2402 def resolve(self, name: str) -> str: 

2403 """Resolve a name within this object's and parents' scope. 

2404 

2405 Parameters: 

2406 name: The name to resolve. 

2407 

2408 Raises: 

2409 NameResolutionError: When the name could not be resolved. 

2410 

2411 Returns: 

2412 The resolved name. 

2413 """ 

2414 # We're in an `__init__` method... 

2415 if self.parent and self.name == "__init__": 

2416 # ...and name is a parameter name: resolve to the parameter. 

2417 if name in self.parameters: 

2418 return f"{self.parent.path}({name})" 

2419 

2420 # Kind of a special case: we avoid resolving to instance-attributes from a function scope. 

2421 # See issue https://github.com/mkdocstrings/griffe/issues/367. 

2422 resolved = super().resolve(name) 

2423 try: 

2424 obj = self.modules_collection.get_member(resolved) 

2425 except KeyError: 

2426 return resolved 

2427 try: 

2428 if obj.is_attribute and "instance-attribute" in obj.labels: 2428 ↛ 2432line 2428 didn't jump to line 2432 because the condition on line 2428 was always true

2429 raise NameResolutionError(name) 

2430 except AliasResolutionError: 

2431 pass 

2432 return resolved 

2433 return super().resolve(name) 

2434 

2435 def as_dict(self, **kwargs: Any) -> dict[str, Any]: 

2436 """Return this function's data as a dictionary. 

2437 

2438 See also: [`as_json`][griffe.Function.as_json]. 

2439 

2440 Parameters: 

2441 **kwargs: Additional serialization options. 

2442 

2443 Returns: 

2444 A dictionary. 

2445 """ 

2446 base = super().as_dict(**kwargs) 

2447 base["decorators"] = [dec.as_dict(**kwargs) for dec in self.decorators] 

2448 base["parameters"] = [param.as_dict(**kwargs) for param in self.parameters] 

2449 base["returns"] = self.returns 

2450 return base 

2451 

2452 def signature(self, *, return_type: bool = True, name: str | None = None) -> str: 

2453 """Construct the function signature. 

2454 

2455 Parameters: 

2456 return_type: Whether to include the return type in the signature. 

2457 name: The name of the function to use in the signature. 

2458 

2459 Returns: 

2460 A string representation of the function signature. 

2461 """ 

2462 signature = f"{name or self.name}(" 

2463 

2464 has_pos_only = any(p.kind == ParameterKind.positional_only for p in self.parameters) 

2465 render_pos_only_separator = True 

2466 render_kw_only_separator = True 

2467 

2468 param_strs = [] 

2469 

2470 for index, param in enumerate(self.parameters): 

2471 # Skip 'self' or 'cls' for class methods if it's the first parameter. 

2472 if index == 0 and param.name in ("self", "cls") and self.parent and self.parent.is_class: 2472 ↛ 2473line 2472 didn't jump to line 2473 because the condition on line 2472 was never true

2473 continue 

2474 

2475 param_str = "" 

2476 

2477 # Handle parameter kind and separators. 

2478 if param.kind != ParameterKind.positional_only: 

2479 if has_pos_only and render_pos_only_separator: 

2480 render_pos_only_separator = False 

2481 param_strs.append("/") 

2482 

2483 if param.kind == ParameterKind.keyword_only and render_kw_only_separator: 2483 ↛ 2484line 2483 didn't jump to line 2484 because the condition on line 2483 was never true

2484 render_kw_only_separator = False 

2485 param_strs.append("*") 

2486 

2487 # Handle variadic parameters. 

2488 if param.kind == ParameterKind.var_positional: 

2489 param_str = "*" 

2490 render_kw_only_separator = False 

2491 elif param.kind == ParameterKind.var_keyword: 

2492 param_str = "**" 

2493 

2494 # Add parameter name. 

2495 param_str += param.name 

2496 

2497 # Handle type annotation 

2498 if param.annotation is not None: 

2499 param_str += f": {param.annotation}" 

2500 equal = " = " # Space around equal when annotation is present. 

2501 else: 

2502 equal = "=" # No space when no annotation. 

2503 

2504 # Handle default value. 

2505 if param.default is not None and param.kind not in { 

2506 ParameterKind.var_positional, 

2507 ParameterKind.var_keyword, 

2508 }: 

2509 param_str += f"{equal}{param.default}" 

2510 

2511 param_strs.append(param_str) 

2512 

2513 # If we have positional-only parameters but no '/' was added yet 

2514 if has_pos_only and render_pos_only_separator: 2514 ↛ 2515line 2514 didn't jump to line 2515 because the condition on line 2514 was never true

2515 param_strs.append("/") 

2516 

2517 signature += ", ".join(param_strs) 

2518 signature += ")" 

2519 

2520 # Add return type if present. 

2521 if return_type and self.annotation: 

2522 signature += f" -> {self.annotation}" 

2523 

2524 return signature 

2525 

2526 

2527class Attribute(Object): 

2528 """The class representing a Python module/class/instance attribute.""" 

2529 

2530 kind = Kind.ATTRIBUTE 

2531 

2532 def __init__( 

2533 self, 

2534 *args: Any, 

2535 value: str | Expr | None = None, 

2536 annotation: str | Expr | None = None, 

2537 **kwargs: Any, 

2538 ) -> None: 

2539 """Initialize the function. 

2540 

2541 Parameters: 

2542 *args: See [`griffe.Object`][]. 

2543 value: The attribute value, if any. 

2544 annotation: The attribute annotation, if any. 

2545 **kwargs: See [`griffe.Object`][]. 

2546 """ 

2547 super().__init__(*args, **kwargs) 

2548 self.value: str | Expr | None = value 

2549 """The attribute value.""" 

2550 self.annotation: str | Expr | None = annotation 

2551 """The attribute type annotation.""" 

2552 self.setter: Function | None = None 

2553 """The setter linked to this property.""" 

2554 self.deleter: Function | None = None 

2555 """The deleter linked to this property.""" 

2556 

2557 def as_dict(self, **kwargs: Any) -> dict[str, Any]: 

2558 """Return this attribute's data as a dictionary. 

2559 

2560 See also: [`as_json`][griffe.Attribute.as_json]. 

2561 

2562 Parameters: 

2563 **kwargs: Additional serialization options. 

2564 

2565 Returns: 

2566 A dictionary. 

2567 """ 

2568 base = super().as_dict(**kwargs) 

2569 if self.value is not None: 

2570 base["value"] = self.value 

2571 if self.annotation is not None: 

2572 base["annotation"] = self.annotation 

2573 return base 

2574 

2575 

2576class TypeAlias(Object): 

2577 """The class representing a Python type alias.""" 

2578 

2579 kind = Kind.TYPE_ALIAS 

2580 

2581 def __init__( 

2582 self, 

2583 *args: Any, 

2584 value: str | Expr | None = None, 

2585 **kwargs: Any, 

2586 ) -> None: 

2587 """Initialize the function. 

2588 

2589 Parameters: 

2590 *args: See [`griffe.Object`][]. 

2591 value: The type alias value. 

2592 **kwargs: See [`griffe.Object`][]. 

2593 """ 

2594 super().__init__(*args, **kwargs, runtime=False) 

2595 self.value: str | Expr | None = value 

2596 """The type alias value.""" 

2597 

2598 def as_dict(self, **kwargs: Any) -> dict[str, Any]: 

2599 """Return this type alias's data as a dictionary. 

2600 

2601 Parameters: 

2602 **kwargs: Additional serialization options. 

2603 

2604 Returns: 

2605 A dictionary. 

2606 """ 

2607 base = super().as_dict(**kwargs) 

2608 base["value"] = self.value 

2609 return base