Coverage for src/_griffe/models.py: 79.40%

779 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-16 15:54 +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, Sequence, Union, cast 

12 

13from _griffe.c3linear import c3linear_merge 

14from _griffe.docstrings.parsers import DocstringStyle, parse 

15from _griffe.enumerations import Kind, ParameterKind, Parser 

16from _griffe.exceptions import AliasResolutionError, BuiltinModuleError, CyclicAliasError, NameResolutionError 

17from _griffe.expressions import ExprCall, ExprName 

18from _griffe.logger import logger 

19from _griffe.mixins import ObjectAliasMixin 

20 

21if TYPE_CHECKING: 

22 from _griffe.collections import LinesCollection, ModulesCollection 

23 from _griffe.docstrings.models import DocstringSection 

24 from _griffe.expressions import Expr 

25 

26from functools import cached_property 

27 

28 

29class Decorator: 

30 """This class represents decorators.""" 

31 

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

33 """Initialize the decorator. 

34 

35 Parameters: 

36 value: The decorator code. 

37 lineno: The starting line number. 

38 endlineno: The ending line number. 

39 """ 

40 self.value: str | Expr = value 

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

42 self.lineno: int | None = lineno 

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

44 self.endlineno: int | None = endlineno 

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

46 

47 @property 

48 def callable_path(self) -> str: 

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

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

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

52 

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

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

55 

56 Parameters: 

57 **kwargs: Additional serialization options. 

58 

59 Returns: 

60 A dictionary. 

61 """ 

62 return { 

63 "value": self.value, 

64 "lineno": self.lineno, 

65 "endlineno": self.endlineno, 

66 } 

67 

68 

69class Docstring: 

70 """This class represents docstrings.""" 

71 

72 def __init__( 

73 self, 

74 value: str, 

75 *, 

76 lineno: int | None = None, 

77 endlineno: int | None = None, 

78 parent: Object | None = None, 

79 parser: DocstringStyle | Parser | None = None, 

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

81 ) -> None: 

82 """Initialize the docstring. 

83 

84 Parameters: 

85 value: The docstring value. 

86 lineno: The starting line number. 

87 endlineno: The ending line number. 

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

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

90 parser_options: Additional docstring parsing options. 

91 """ 

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

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

94 self.lineno: int | None = lineno 

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

96 self.endlineno: int | None = endlineno 

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

98 self.parent: Object | None = parent 

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

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

101 """The selected docstring parser.""" 

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

103 """The configured parsing options.""" 

104 

105 @property 

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

107 """The lines of the docstring.""" 

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

109 

110 @property 

111 def source(self) -> str: 

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

113 if self.parent is None: 

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

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

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

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

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

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

120 

121 @cached_property 

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

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

124 return self.parse() 

125 

126 def parse( 

127 self, 

128 parser: DocstringStyle | Parser | None = None, 

129 **options: Any, 

130 ) -> list[DocstringSection]: 

131 """Parse the docstring into structured data. 

132 

133 Parameters: 

134 parser: The docstring parser to use. 

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

136 **options: Additional docstring parsing options. 

137 

138 Returns: 

139 The parsed docstring as a list of sections. 

140 """ 

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

142 

143 def as_dict( 

144 self, 

145 *, 

146 full: bool = False, 

147 **kwargs: Any, # noqa: ARG002 

148 ) -> dict[str, Any]: 

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

150 

151 Parameters: 

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

153 **kwargs: Additional serialization options. 

154 

155 Returns: 

156 A dictionary. 

157 """ 

158 base: dict[str, Any] = { 

159 "value": self.value, 

160 "lineno": self.lineno, 

161 "endlineno": self.endlineno, 

162 } 

163 if full: 

164 base["parsed"] = self.parsed 

165 return base 

166 

167 

168class Parameter: 

169 """This class represent a function parameter.""" 

170 

171 def __init__( 

172 self, 

173 name: str, 

174 *, 

175 annotation: str | Expr | None = None, 

176 kind: ParameterKind | None = None, 

177 default: str | Expr | None = None, 

178 docstring: Docstring | None = None, 

179 ) -> None: 

180 """Initialize the parameter. 

181 

182 Parameters: 

183 name: The parameter name. 

184 annotation: The parameter annotation, if any. 

185 kind: The parameter kind. 

186 default: The parameter default, if any. 

187 docstring: The parameter docstring. 

188 """ 

189 self.name: str = name 

190 """The parameter name.""" 

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

192 """The parameter type annotation.""" 

193 self.kind: ParameterKind | None = kind 

194 """The parameter kind.""" 

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

196 """The parameter default value.""" 

197 self.docstring: Docstring | None = docstring 

198 """The parameter docstring.""" 

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

200 # when the parameters are assigned to the function. 

201 self.function: Function | None = None 

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

203 

204 def __str__(self) -> str: 

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

206 if self.kind: 

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

208 return param 

209 

210 def __repr__(self) -> str: 

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

212 

213 def __eq__(self, __value: object) -> bool: 

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

215 if not isinstance(__value, Parameter): 215 ↛ 216line 215 didn't jump to line 216 because the condition on line 215 was never true

216 return NotImplemented 

217 return ( 

218 self.name == __value.name 

219 and self.annotation == __value.annotation 

220 and self.kind == __value.kind 

221 and self.default == __value.default 

222 ) 

223 

224 @property 

225 def required(self) -> bool: 

226 """Whether this parameter is required.""" 

227 return self.default is None 

228 

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

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

231 

232 Parameters: 

233 **kwargs: Additional serialization options. 

234 

235 Returns: 

236 A dictionary. 

237 """ 

238 base: dict[str, Any] = { 

239 "name": self.name, 

240 "annotation": self.annotation, 

241 "kind": self.kind, 

242 "default": self.default, 

243 } 

244 if self.docstring: 

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

246 return base 

247 

248 

249class Parameters: 

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

251 

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

253 

254 ```pycon 

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

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

257 True 

258 ``` 

259 """ 

260 

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

262 """Initialize the parameters container. 

263 

264 Parameters: 

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

266 """ 

267 self._parameters_list: list[Parameter] = [] 

268 self._parameters_dict: dict[str, Parameter] = {} 

269 for parameter in parameters: 

270 self.add(parameter) 

271 

272 def __repr__(self) -> str: 

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

274 

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

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

277 if isinstance(name_or_index, int): 

278 return self._parameters_list[name_or_index] 

279 return self._parameters_dict[name_or_index.lstrip("*")] 

280 

281 def __len__(self): 

282 """The number of parameters.""" 

283 return len(self._parameters_list) 

284 

285 def __iter__(self): 

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

287 return iter(self._parameters_list) 

288 

289 def __contains__(self, param_name: str): 

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

291 return param_name.lstrip("*") in self._parameters_dict 

292 

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

294 """Add a parameter to the container. 

295 

296 Parameters: 

297 parameter: The function parameter to add. 

298 

299 Raises: 

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

301 """ 

302 if parameter.name not in self._parameters_dict: 302 ↛ 306line 302 didn't jump to line 306 because the condition on line 302 was always true

303 self._parameters_dict[parameter.name] = parameter 

304 self._parameters_list.append(parameter) 

305 else: 

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

307 

308 

309class Object(ObjectAliasMixin): 

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

311 

312 kind: Kind 

313 """The object kind.""" 

314 is_alias: bool = False 

315 """Always false for objects.""" 

316 is_collection: bool = False 

317 """Always false for objects.""" 

318 inherited: bool = False 

319 """Always false for objects. 

320 

321 Only aliases can be marked as inherited. 

322 """ 

323 

324 def __init__( 

325 self, 

326 name: str, 

327 *, 

328 lineno: int | None = None, 

329 endlineno: int | None = None, 

330 runtime: bool = True, 

331 docstring: Docstring | None = None, 

332 parent: Module | Class | None = None, 

333 lines_collection: LinesCollection | None = None, 

334 modules_collection: ModulesCollection | None = None, 

335 ) -> None: 

336 """Initialize the object. 

337 

338 Parameters: 

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

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

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

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

343 docstring: The object docstring. 

344 parent: The object parent. 

345 lines_collection: A collection of source code lines. 

346 modules_collection: A collection of modules. 

347 """ 

348 self.name: str = name 

349 """The object name.""" 

350 

351 self.lineno: int | None = lineno 

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

353 

354 self.endlineno: int | None = endlineno 

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

356 

357 self.docstring: Docstring | None = docstring 

358 """The object docstring.""" 

359 

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

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

362 

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

364 """The object members (modules, classes, functions, attributes).""" 

365 

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

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

368 

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

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

371 

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

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

374 """ 

375 

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

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

378 

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

380 like other lists of exports coming from submodules: 

381 

382 ```python 

383 from .submodule import __all__ as submodule_all 

384 

385 __all__ = ["hello", *submodule_all] 

386 ``` 

387 

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

389 """ 

390 

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

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

393 

394 self.runtime: bool = runtime 

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

396 

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

398 are not available at runtime. 

399 """ 

400 

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

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

403 

404 self.public: bool | None = None 

405 """Whether this object is public.""" 

406 

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

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

409 

410 self._lines_collection: LinesCollection | None = lines_collection 

411 self._modules_collection: ModulesCollection | None = modules_collection 

412 

413 # attach the docstring to this object 

414 if docstring: 

415 docstring.parent = self 

416 

417 def __repr__(self) -> str: 

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

419 

420 # Prevent using `__len__`. 

421 def __bool__(self) -> bool: 

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

423 return True 

424 

425 def __len__(self) -> int: 

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

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

428 

429 @property 

430 def has_docstring(self) -> bool: 

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

432 return bool(self.docstring) 

433 

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

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

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

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

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

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

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

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

442 @property 

443 def has_docstrings(self) -> bool: 

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

445 

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

447 unless they are also public. 

448 """ 

449 if self.has_docstring: 

450 return True 

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

452 try: 

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

454 return True 

455 except AliasResolutionError: 

456 continue 

457 return False 

458 

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

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

461 

462 Parameters: 

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

464 

465 Raises: 

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

467 

468 Returns: 

469 True or False. 

470 """ 

471 if isinstance(kind, set): 

472 if not kind: 

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

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

475 if isinstance(kind, str): 

476 kind = Kind(kind) 

477 return self.kind is kind 

478 

479 @cached_property 

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

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

482 

483 This method is part of the consumer API: 

484 do not use when producing Griffe trees! 

485 """ 

486 if not isinstance(self, Class): 

487 return {} 

488 try: 

489 mro = self.mro() 

490 except ValueError as error: 

491 logger.debug(error) 

492 return {} 

493 inherited_members = {} 

494 for base in reversed(mro): 

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

496 if name not in self.members: 

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

498 return inherited_members 

499 

500 @property 

501 def is_module(self) -> bool: 

502 """Whether this object is a module.""" 

503 return self.kind is Kind.MODULE 

504 

505 @property 

506 def is_class(self) -> bool: 

507 """Whether this object is a class.""" 

508 return self.kind is Kind.CLASS 

509 

510 @property 

511 def is_function(self) -> bool: 

512 """Whether this object is a function.""" 

513 return self.kind is Kind.FUNCTION 

514 

515 @property 

516 def is_attribute(self) -> bool: 

517 """Whether this object is an attribute.""" 

518 return self.kind is Kind.ATTRIBUTE 

519 

520 @property 

521 def is_init_module(self) -> bool: 

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

523 return False 

524 

525 @property 

526 def is_package(self) -> bool: 

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

528 return False 

529 

530 @property 

531 def is_subpackage(self) -> bool: 

532 """Whether this object is a subpackage.""" 

533 return False 

534 

535 @property 

536 def is_namespace_package(self) -> bool: 

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

538 return False 

539 

540 @property 

541 def is_namespace_subpackage(self) -> bool: 

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

543 return False 

544 

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

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

547 

548 Parameters: 

549 *labels: Labels that must be present. 

550 

551 Returns: 

552 True or False. 

553 """ 

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

555 

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

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

558 

559 Parameters: 

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

561 

562 Returns: 

563 A dictionary of members. 

564 """ 

565 if not predicates: 

566 return self.members 

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

568 for name, member in self.members.items(): 

569 if all(predicate(member) for predicate in predicates): 

570 members[name] = member 

571 return members 

572 

573 @property 

574 def module(self) -> Module: 

575 """The parent module of this object. 

576 

577 Examples: 

578 >>> import griffe 

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

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

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

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

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

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

585 

586 Raises: 

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

588 """ 

589 if isinstance(self, Module): 

590 return self 

591 if self.parent is not None: 

592 return self.parent.module 

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

594 

595 @property 

596 def package(self) -> Module: 

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

598 

599 Examples: 

600 >>> import griffe 

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

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

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

604 """ 

605 module = self.module 

606 while module.parent: 

607 module = module.parent # type: ignore[assignment] # always a module 

608 return module 

609 

610 @property 

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

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

613 

614 Examples: 

615 >>> import griffe 

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

617 >>> markdown.filepath 

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

619 """ 

620 return self.module.filepath 

621 

622 @property 

623 def relative_package_filepath(self) -> Path: 

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

625 

626 Raises: 

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

628 """ 

629 package_path = self.package.filepath 

630 

631 # Current "module" is a namespace package. 

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

633 # Current package is a namespace package. 

634 if isinstance(package_path, list): 

635 for pkg_path in package_path: 

636 for self_path in self.filepath: 

637 with suppress(ValueError): 

638 return self_path.relative_to(pkg_path.parent) 

639 

640 # Current package is a regular package. 

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

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

643 else: 

644 for self_path in self.filepath: 

645 with suppress(ValueError): 

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

647 raise ValueError 

648 

649 # Current package is a namespace package, 

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

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

652 for pkg_path in package_path: 

653 with suppress(ValueError): 

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

655 raise ValueError 

656 

657 # Current package is a regular package, 

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

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

660 # of the package (search path). 

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

662 

663 @property 

664 def relative_filepath(self) -> Path: 

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

666 

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

668 

669 Raises: 

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

671 """ 

672 cwd = Path.cwd() 

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

674 for self_path in self.filepath: 

675 with suppress(ValueError): 

676 return self_path.relative_to(cwd) 

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

678 try: 

679 return self.filepath.relative_to(cwd) 

680 except ValueError: 

681 return self.filepath 

682 

683 @property 

684 def path(self) -> str: 

685 """The dotted path of this object. 

686 

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

688 

689 Examples: 

690 >>> import griffe 

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

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

693 'markdown.core.Markdown.references' 

694 """ 

695 return self.canonical_path 

696 

697 @property 

698 def canonical_path(self) -> str: 

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

700 

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

702 """ 

703 if self.parent is None: 

704 return self.name 

705 return ".".join((self.parent.path, self.name)) 

706 

707 @property 

708 def modules_collection(self) -> ModulesCollection: 

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

710 

711 Raises: 

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

713 """ 

714 if self._modules_collection is not None: 

715 return self._modules_collection 

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

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

718 return self.parent.modules_collection 

719 

720 @property 

721 def lines_collection(self) -> LinesCollection: 

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

723 

724 Raises: 

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

726 """ 

727 if self._lines_collection is not None: 

728 return self._lines_collection 

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

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

731 return self.parent.lines_collection 

732 

733 @property 

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

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

736 try: 

737 filepath = self.filepath 

738 except BuiltinModuleError: 

739 return [] 

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

741 return [] 

742 try: 

743 lines = self.lines_collection[filepath] 

744 except KeyError: 

745 return [] 

746 if self.is_module: 

747 return lines 

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

749 return [] 

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

751 

752 @property 

753 def source(self) -> str: 

754 """The source code of this object.""" 

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

756 

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

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

759 

760 Parameters: 

761 name: The name to resolve. 

762 

763 Raises: 

764 NameResolutionError: When the name could not be resolved. 

765 

766 Returns: 

767 The resolved name. 

768 """ 

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

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

771 

772 # Name is a member this object. 

773 if name in self.members: 

774 if self.members[name].is_alias: 

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

776 return self.members[name].path 

777 

778 # Name was imported. 

779 if name in self.imports: 779 ↛ 780line 779 didn't jump to line 780 because the condition on line 779 was never true

780 return self.imports[name] 

781 

782 # Name unknown and no more parent scope. 

783 if self.parent is None: 

784 # could be a built-in 

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

786 

787 # Name is parent, non-module object. 

788 # NOTE: possibly useless branch. 

789 if name == self.parent.name and not self.parent.is_module: 789 ↛ 790line 789 didn't jump to line 790 because the condition on line 789 was never true

790 return self.parent.path 

791 

792 # Recurse in parent. 

793 return self.parent.resolve(name) 

794 

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

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

797 

798 Parameters: 

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

800 **kwargs: Additional serialization options. 

801 

802 Returns: 

803 A dictionary. 

804 """ 

805 base: dict[str, Any] = { 

806 "kind": self.kind, 

807 "name": self.name, 

808 } 

809 

810 if full: 

811 base.update( 

812 { 

813 "path": self.path, 

814 "filepath": self.filepath, 

815 "relative_filepath": self.relative_filepath, 

816 "relative_package_filepath": self.relative_package_filepath, 

817 }, 

818 ) 

819 

820 if self.lineno is not None: 

821 base["lineno"] = self.lineno 

822 if self.endlineno is not None: 

823 base["endlineno"] = self.endlineno 

824 if self.docstring: 

825 base["docstring"] = self.docstring 

826 

827 base["labels"] = self.labels 

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

829 

830 return base 

831 

832 

833class Alias(ObjectAliasMixin): 

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

835 

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

837 but were imported from another module. 

838 

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

840 

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

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

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

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

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

846 """ 

847 

848 is_alias: bool = True 

849 """Always true for aliases.""" 

850 is_collection: bool = False 

851 """Always false for aliases.""" 

852 

853 def __init__( 

854 self, 

855 name: str, 

856 target: str | Object | Alias, 

857 *, 

858 lineno: int | None = None, 

859 endlineno: int | None = None, 

860 runtime: bool = True, 

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

862 inherited: bool = False, 

863 ) -> None: 

864 """Initialize the alias. 

865 

866 Parameters: 

867 name: The alias name. 

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

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

870 lineno: The alias starting line number. 

871 endlineno: The alias ending line number. 

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

873 parent: The alias parent. 

874 inherited: Whether this alias wraps an inherited member. 

875 """ 

876 self.name: str = name 

877 """The alias name.""" 

878 

879 self.alias_lineno: int | None = lineno 

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

881 

882 self.alias_endlineno: int | None = endlineno 

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

884 

885 self.runtime: bool = runtime 

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

887 

888 self.inherited: bool = inherited 

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

890 

891 self.public: bool | None = None 

892 """Whether this alias is public.""" 

893 

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

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

896 

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

898 self._passed_through: bool = False 

899 

900 self.target_path: str 

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

902 

903 if isinstance(target, str): 

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

905 self.target_path = target 

906 else: 

907 self._target = target 

908 self.target_path = target.path 

909 self._update_target_aliases() 

910 

911 def __repr__(self) -> str: 

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

913 

914 # Prevent using `__len__`. 

915 def __bool__(self) -> bool: 

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

917 return True 

918 

919 def __len__(self) -> int: 

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

921 return 1 

922 

923 # SPECIAL PROXIES ------------------------------- 

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

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

926 

927 @property 

928 def kind(self) -> Kind: 

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

930 # custom behavior to avoid raising exceptions 

931 try: 

932 return self.final_target.kind 

933 except (AliasResolutionError, CyclicAliasError): 

934 return Kind.ALIAS 

935 

936 @property 

937 def has_docstring(self) -> bool: 

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

939 try: 

940 return self.final_target.has_docstring 

941 except (AliasResolutionError, CyclicAliasError): 

942 return False 

943 

944 @property 

945 def has_docstrings(self) -> bool: 

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

947 try: 

948 return self.final_target.has_docstrings 

949 except (AliasResolutionError, CyclicAliasError): 

950 return False 

951 

952 @property 

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

954 """The parent of this alias.""" 

955 return self._parent 

956 

957 @parent.setter 

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

959 self._parent = value 

960 self._update_target_aliases() 

961 

962 @property 

963 def path(self) -> str: 

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

965 return ".".join((self.parent.path, self.name)) # type: ignore[union-attr] # we assume there's always a parent 

966 

967 @property 

968 def modules_collection(self) -> ModulesCollection: 

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

970 # no need to forward to the target 

971 return self.parent.modules_collection # type: ignore[union-attr] # we assume there's always a parent 

972 

973 @cached_property 

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

975 """The target's members (modules, classes, functions, attributes).""" 

976 final_target = self.final_target 

977 

978 # We recreate aliases to maintain a correct hierarchy, 

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

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

981 # not the original member's path. 

982 return { 

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

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

985 } 

986 

987 @cached_property 

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

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

990 

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

992 to preserve correct object access paths. 

993 

994 This method is part of the consumer API: 

995 do not use when producing Griffe trees! 

996 """ 

997 final_target = self.final_target 

998 

999 # We recreate aliases to maintain a correct hierarchy, 

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

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

1002 # not the original member's path. 

1003 return { 

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

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

1006 } 

1007 

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

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

1010 

1011 Parameters: 

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

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

1014 

1015 Returns: 

1016 A JSON string. 

1017 """ 

1018 try: 

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

1020 except (AliasResolutionError, CyclicAliasError): 

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

1022 

1023 # GENERIC OBJECT PROXIES -------------------------------- 

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

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

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

1027 

1028 @property 

1029 def extra(self) -> dict: 

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

1031 return self.final_target.extra 

1032 

1033 @property 

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

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

1036 return self.final_target.lineno 

1037 

1038 @property 

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

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

1041 return self.final_target.endlineno 

1042 

1043 @property 

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

1045 """The target docstring.""" 

1046 return self.final_target.docstring 

1047 

1048 @docstring.setter 

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

1050 self.final_target.docstring = docstring 

1051 

1052 @property 

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

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

1055 return self.final_target.labels 

1056 

1057 @property 

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

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

1060 

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

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

1063 """ 

1064 return self.final_target.imports 

1065 

1066 @property 

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

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

1069 

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

1071 like other lists of exports coming from submodules: 

1072 

1073 ```python 

1074 from .submodule import __all__ as submodule_all 

1075 

1076 __all__ = ["hello", *submodule_all] 

1077 ``` 

1078 

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

1080 """ 

1081 return self.final_target.exports 

1082 

1083 @property 

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

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

1086 return self.final_target.aliases 

1087 

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

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

1090 

1091 Parameters: 

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

1093 

1094 Raises: 

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

1096 

1097 Returns: 

1098 True or False. 

1099 """ 

1100 return self.final_target.is_kind(kind) 

1101 

1102 @property 

1103 def is_module(self) -> bool: 

1104 """Whether this object is a module.""" 

1105 return self.final_target.is_module 

1106 

1107 @property 

1108 def is_class(self) -> bool: 

1109 """Whether this object is a class.""" 

1110 return self.final_target.is_class 

1111 

1112 @property 

1113 def is_function(self) -> bool: 

1114 """Whether this object is a function.""" 

1115 return self.final_target.is_function 

1116 

1117 @property 

1118 def is_attribute(self) -> bool: 

1119 """Whether this object is an attribute.""" 

1120 return self.final_target.is_attribute 

1121 

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

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

1124 

1125 Parameters: 

1126 *labels: Labels that must be present. 

1127 

1128 Returns: 

1129 True or False. 

1130 """ 

1131 return self.final_target.has_labels(*labels) 

1132 

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

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

1135 

1136 Parameters: 

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

1138 

1139 Returns: 

1140 A dictionary of members. 

1141 """ 

1142 return self.final_target.filter_members(*predicates) 

1143 

1144 @property 

1145 def module(self) -> Module: 

1146 """The parent module of this object. 

1147 

1148 Raises: 

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

1150 """ 

1151 return self.final_target.module 

1152 

1153 @property 

1154 def package(self) -> Module: 

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

1156 return self.final_target.package 

1157 

1158 @property 

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

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

1161 return self.final_target.filepath 

1162 

1163 @property 

1164 def relative_filepath(self) -> Path: 

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

1166 

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

1168 

1169 Raises: 

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

1171 """ 

1172 return self.final_target.relative_filepath 

1173 

1174 @property 

1175 def relative_package_filepath(self) -> Path: 

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

1177 

1178 Raises: 

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

1180 """ 

1181 return self.final_target.relative_package_filepath 

1182 

1183 @property 

1184 def canonical_path(self) -> str: 

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

1186 

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

1188 """ 

1189 return self.final_target.canonical_path 

1190 

1191 @property 

1192 def lines_collection(self) -> LinesCollection: 

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

1194 

1195 Raises: 

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

1197 """ 

1198 return self.final_target.lines_collection 

1199 

1200 @property 

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

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

1203 return self.final_target.lines 

1204 

1205 @property 

1206 def source(self) -> str: 

1207 """The source code of this object.""" 

1208 return self.final_target.source 

1209 

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

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

1212 

1213 Parameters: 

1214 name: The name to resolve. 

1215 

1216 Raises: 

1217 NameResolutionError: When the name could not be resolved. 

1218 

1219 Returns: 

1220 The resolved name. 

1221 """ 

1222 return self.final_target.resolve(name) 

1223 

1224 # SPECIFIC MODULE/CLASS/FUNCTION/ATTRIBUTE PROXIES --------------- 

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

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

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

1228 

1229 @property 

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

1231 return cast(Module, self.final_target)._filepath 

1232 

1233 @property 

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

1235 """The class bases.""" 

1236 return cast(Class, self.final_target).bases 

1237 

1238 @property 

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

1240 """The class/function decorators.""" 

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

1242 

1243 @property 

1244 def imports_future_annotations(self) -> bool: 

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

1246 return cast(Module, self.final_target).imports_future_annotations 

1247 

1248 @property 

1249 def is_init_module(self) -> bool: 

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

1251 return cast(Module, self.final_target).is_init_module 

1252 

1253 @property 

1254 def is_package(self) -> bool: 

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

1256 return cast(Module, self.final_target).is_package 

1257 

1258 @property 

1259 def is_subpackage(self) -> bool: 

1260 """Whether this module is a subpackage.""" 

1261 return cast(Module, self.final_target).is_subpackage 

1262 

1263 @property 

1264 def is_namespace_package(self) -> bool: 

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

1266 return cast(Module, self.final_target).is_namespace_package 

1267 

1268 @property 

1269 def is_namespace_subpackage(self) -> bool: 

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

1271 return cast(Module, self.final_target).is_namespace_subpackage 

1272 

1273 @property 

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

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

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

1277 

1278 @overloads.setter 

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

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

1281 

1282 @property 

1283 def parameters(self) -> Parameters: 

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

1285 

1286 This property can fetch inherited members, 

1287 and therefore is part of the consumer API: 

1288 do not use when producing Griffe trees! 

1289 """ 

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

1291 

1292 @property 

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

1294 """The function return type annotation.""" 

1295 return cast(Function, self.final_target).returns 

1296 

1297 @returns.setter 

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

1299 cast(Function, self.final_target).returns = returns 

1300 

1301 @property 

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

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

1304 return cast(Attribute, self.final_target).setter 

1305 

1306 @property 

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

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

1309 return cast(Attribute, self.final_target).deleter 

1310 

1311 @property 

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

1313 """The attribute value.""" 

1314 return cast(Attribute, self.final_target).value 

1315 

1316 @property 

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

1318 """The attribute type annotation.""" 

1319 return cast(Attribute, self.final_target).annotation 

1320 

1321 @annotation.setter 

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

1323 cast(Attribute, self.final_target).annotation = annotation 

1324 

1325 @property 

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

1327 """Resolved class bases. 

1328 

1329 This method is part of the consumer API: 

1330 do not use when producing Griffe trees! 

1331 """ 

1332 return cast(Class, self.final_target).resolved_bases 

1333 

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

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

1336 return cast(Class, self.final_target).mro() 

1337 

1338 # SPECIFIC ALIAS METHOD AND PROPERTIES ----------------- 

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

1340 # they are specific to aliases. 

1341 

1342 @property 

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

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

1345 

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

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

1348 """ 

1349 if not self.resolved: 

1350 self.resolve_target() 

1351 return self._target # type: ignore[return-value] # cannot return None, exception is raised 

1352 

1353 @target.setter 

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

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

1356 raise CyclicAliasError([self.target_path]) 

1357 self._target = value 

1358 self.target_path = value.path 

1359 if self.parent is not None: 

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

1361 

1362 @property 

1363 def final_target(self) -> Object: 

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

1365 

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

1367 """ 

1368 # Here we quickly iterate on the alias chain, 

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

1370 

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

1372 # as already resolved, and can contain cycles. 

1373 

1374 # using a dict as an ordered set 

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

1376 target = self 

1377 while target.is_alias: 

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

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

1380 paths_seen[target.path] = None 

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

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

1383 

1384 def resolve_target(self) -> None: 

1385 """Resolve the target. 

1386 

1387 Raises: 

1388 AliasResolutionError: When the target cannot be resolved. 

1389 It happens when the target does not exist, 

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

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

1392 and added to the collection. 

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

1394 """ 

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

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

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

1398 # through an alias will raise a CyclicAliasError. 

1399 

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

1401 # the whole chain stays unresolved. This prevents 

1402 # bad surprises later, in code that checks if 

1403 # an alias is resolved by checking only 

1404 # the first link of the chain. 

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

1406 raise CyclicAliasError([self.target_path]) 

1407 self._passed_through = True 

1408 try: 

1409 self._resolve_target() 

1410 finally: 

1411 self._passed_through = False 

1412 

1413 def _resolve_target(self) -> None: 

1414 try: 

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

1416 except KeyError as error: 

1417 raise AliasResolutionError(self) from error 

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

1419 raise CyclicAliasError([self.target_path]) 

1420 if resolved.is_alias and not resolved.resolved: 

1421 try: 

1422 resolved.resolve_target() 

1423 except CyclicAliasError as error: 

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

1425 self._target = resolved 

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

1427 self._target.aliases[self.path] = self # type: ignore[union-attr] # we just set the target 

1428 

1429 def _update_target_aliases(self) -> None: 

1430 with suppress(AttributeError, AliasResolutionError, CyclicAliasError): 

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

1432 

1433 @property 

1434 def resolved(self) -> bool: 

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

1436 return self._target is not None 

1437 

1438 @property 

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

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

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

1442 return self.target_path 

1443 return None 

1444 

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

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

1447 

1448 Parameters: 

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

1450 **kwargs: Additional serialization options. 

1451 

1452 Returns: 

1453 A dictionary. 

1454 """ 

1455 base: dict[str, Any] = { 

1456 "kind": Kind.ALIAS, 

1457 "name": self.name, 

1458 "target_path": self.target_path, 

1459 } 

1460 

1461 if full: 

1462 base["path"] = self.path 

1463 

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

1465 base["lineno"] = self.alias_lineno 

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

1467 base["endlineno"] = self.alias_endlineno 

1468 

1469 return base 

1470 

1471 

1472class Module(Object): 

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

1474 

1475 kind = Kind.MODULE 

1476 

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

1478 """Initialize the module. 

1479 

1480 Parameters: 

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

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

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

1484 """ 

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

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

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

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

1489 

1490 def __repr__(self) -> str: 

1491 try: 

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

1493 except BuiltinModuleError: 

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

1495 

1496 @property 

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

1498 """The file path of this module. 

1499 

1500 Raises: 

1501 BuiltinModuleError: When the instance filepath is None. 

1502 """ 

1503 if self._filepath is None: 

1504 raise BuiltinModuleError(self.name) 

1505 return self._filepath 

1506 

1507 @property 

1508 def imports_future_annotations(self) -> bool: 

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

1510 return ( 

1511 "annotations" in self.members 

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

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

1514 ) 

1515 

1516 @property 

1517 def is_init_module(self) -> bool: 

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

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

1520 return False 

1521 try: 

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

1523 except BuiltinModuleError: 

1524 return False 

1525 

1526 @property 

1527 def is_package(self) -> bool: 

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

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

1530 

1531 @property 

1532 def is_subpackage(self) -> bool: 

1533 """Whether this module is a subpackage.""" 

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

1535 

1536 @property 

1537 def is_namespace_package(self) -> bool: 

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

1539 try: 

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

1541 except BuiltinModuleError: 

1542 return False 

1543 

1544 @property 

1545 def is_namespace_subpackage(self) -> bool: 

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

1547 try: 

1548 return ( 

1549 self.parent is not None 

1550 and isinstance(self.filepath, list) 

1551 and ( 

1552 cast(Module, self.parent).is_namespace_package or cast(Module, self.parent).is_namespace_subpackage 

1553 ) 

1554 ) 

1555 except BuiltinModuleError: 

1556 return False 

1557 

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

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

1560 

1561 Parameters: 

1562 **kwargs: Additional serialization options. 

1563 

1564 Returns: 

1565 A dictionary. 

1566 """ 

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

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

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

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

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

1572 else: 

1573 base["filepath"] = None 

1574 return base 

1575 

1576 

1577class Class(Object): 

1578 """The class representing a Python class.""" 

1579 

1580 kind = Kind.CLASS 

1581 

1582 def __init__( 

1583 self, 

1584 *args: Any, 

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

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

1587 **kwargs: Any, 

1588 ) -> None: 

1589 """Initialize the class. 

1590 

1591 Parameters: 

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

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

1594 decorators: The class decorators, if any. 

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

1596 """ 

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

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

1599 """The class bases.""" 

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

1601 """The class decorators.""" 

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

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

1604 

1605 @property 

1606 def parameters(self) -> Parameters: 

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

1608 

1609 This property fetches inherited members, 

1610 and therefore is part of the consumer API: 

1611 do not use when producing Griffe trees! 

1612 """ 

1613 try: 

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

1615 except KeyError: 

1616 return Parameters() 

1617 

1618 @cached_property 

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

1620 """Resolved class bases. 

1621 

1622 This method is part of the consumer API: 

1623 do not use when producing Griffe trees! 

1624 """ 

1625 resolved_bases = [] 

1626 for base in self.bases: 

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

1628 try: 

1629 resolved_base = self.modules_collection[base_path] 

1630 if resolved_base.is_alias: 

1631 resolved_base = resolved_base.final_target 

1632 except (AliasResolutionError, CyclicAliasError, KeyError): 

1633 logger.debug(f"Base class {base_path} is not loaded, or not static, it cannot be resolved") 

1634 else: 

1635 resolved_bases.append(resolved_base) 

1636 return resolved_bases 

1637 

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

1639 seen = (*seen, self.path) 

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

1641 if not bases: 

1642 return [self] 

1643 for base in bases: 

1644 if base.path in seen: 

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

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

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

1648 

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

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

1651 return self._mro()[1:] # remove self 

1652 

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

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

1655 

1656 Parameters: 

1657 **kwargs: Additional serialization options. 

1658 

1659 Returns: 

1660 A dictionary. 

1661 """ 

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

1663 base["bases"] = self.bases 

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

1665 return base 

1666 

1667 

1668class Function(Object): 

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

1670 

1671 kind = Kind.FUNCTION 

1672 

1673 def __init__( 

1674 self, 

1675 *args: Any, 

1676 parameters: Parameters | None = None, 

1677 returns: str | Expr | None = None, 

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

1679 **kwargs: Any, 

1680 ) -> None: 

1681 """Initialize the function. 

1682 

1683 Parameters: 

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

1685 parameters: The function parameters. 

1686 returns: The function return annotation. 

1687 decorators: The function decorators, if any. 

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

1689 """ 

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

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

1692 """The function parameters.""" 

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

1694 """The function return type annotation.""" 

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

1696 """The function decorators.""" 

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

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

1699 

1700 for parameter in self.parameters: 

1701 parameter.function = self 

1702 

1703 @property 

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

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

1706 return self.returns 

1707 

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

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

1710 

1711 Parameters: 

1712 **kwargs: Additional serialization options. 

1713 

1714 Returns: 

1715 A dictionary. 

1716 """ 

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

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

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

1720 base["returns"] = self.returns 

1721 return base 

1722 

1723 

1724class Attribute(Object): 

1725 """The class representing a Python module/class/instance attribute.""" 

1726 

1727 kind = Kind.ATTRIBUTE 

1728 

1729 def __init__( 

1730 self, 

1731 *args: Any, 

1732 value: str | Expr | None = None, 

1733 annotation: str | Expr | None = None, 

1734 **kwargs: Any, 

1735 ) -> None: 

1736 """Initialize the function. 

1737 

1738 Parameters: 

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

1740 value: The attribute value, if any. 

1741 annotation: The attribute annotation, if any. 

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

1743 """ 

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

1745 self.value: str | Expr | None = value 

1746 """The attribute value.""" 

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

1748 """The attribute type annotation.""" 

1749 self.setter: Function | None = None 

1750 """The setter linked to this property.""" 

1751 self.deleter: Function | None = None 

1752 """The deleter linked to this property.""" 

1753 

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

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

1756 

1757 Parameters: 

1758 **kwargs: Additional serialization options. 

1759 

1760 Returns: 

1761 A dictionary. 

1762 """ 

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

1764 if self.value is not None: 1764 ↛ 1766line 1764 didn't jump to line 1766 because the condition on line 1764 was always true

1765 base["value"] = self.value 

1766 if self.annotation is not None: 1766 ↛ 1767line 1766 didn't jump to line 1767 because the condition on line 1766 was never true

1767 base["annotation"] = self.annotation 

1768 return base