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

774 statements  

« prev     ^ index     » next       coverage.py v7.6.2, created at 2024-10-12 01:34 +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.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 collections.abc import Sequence 

23 

24 from _griffe.collections import LinesCollection, ModulesCollection 

25 from _griffe.docstrings.models import DocstringSection 

26 from _griffe.expressions import Expr 

27 

28from functools import cached_property 

29 

30 

31class Decorator: 

32 """This class represents decorators.""" 

33 

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

35 """Initialize the decorator. 

36 

37 Parameters: 

38 value: The decorator code. 

39 lineno: The starting line number. 

40 endlineno: The ending line number. 

41 """ 

42 self.value: str | Expr = value 

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

44 self.lineno: int | None = lineno 

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

46 self.endlineno: int | None = endlineno 

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

48 

49 @property 

50 def callable_path(self) -> str: 

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

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

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

54 

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

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

57 

58 Parameters: 

59 **kwargs: Additional serialization options. 

60 

61 Returns: 

62 A dictionary. 

63 """ 

64 return { 

65 "value": self.value, 

66 "lineno": self.lineno, 

67 "endlineno": self.endlineno, 

68 } 

69 

70 

71class Docstring: 

72 """This class represents docstrings.""" 

73 

74 def __init__( 

75 self, 

76 value: str, 

77 *, 

78 lineno: int | None = None, 

79 endlineno: int | None = None, 

80 parent: Object | None = None, 

81 parser: DocstringStyle | Parser | None = None, 

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

83 ) -> None: 

84 """Initialize the docstring. 

85 

86 Parameters: 

87 value: The docstring value. 

88 lineno: The starting line number. 

89 endlineno: The ending line number. 

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

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

92 parser_options: Additional docstring parsing options. 

93 """ 

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

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

96 self.lineno: int | None = lineno 

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

98 self.endlineno: int | None = endlineno 

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

100 self.parent: Object | None = parent 

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

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

103 """The selected docstring parser.""" 

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

105 """The configured parsing options.""" 

106 

107 @property 

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

109 """The lines of the docstring.""" 

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

111 

112 @property 

113 def source(self) -> str: 

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

115 if self.parent is None: 

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

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

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

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

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

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

122 

123 @cached_property 

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

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

126 return self.parse() 

127 

128 def parse( 

129 self, 

130 parser: DocstringStyle | Parser | None = None, 

131 **options: Any, 

132 ) -> list[DocstringSection]: 

133 """Parse the docstring into structured data. 

134 

135 Parameters: 

136 parser: The docstring parser to use. 

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

138 **options: Additional docstring parsing options. 

139 

140 Returns: 

141 The parsed docstring as a list of sections. 

142 """ 

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

144 

145 def as_dict( 

146 self, 

147 *, 

148 full: bool = False, 

149 **kwargs: Any, # noqa: ARG002 

150 ) -> dict[str, Any]: 

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

152 

153 Parameters: 

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

155 **kwargs: Additional serialization options. 

156 

157 Returns: 

158 A dictionary. 

159 """ 

160 base: dict[str, Any] = { 

161 "value": self.value, 

162 "lineno": self.lineno, 

163 "endlineno": self.endlineno, 

164 } 

165 if full: 

166 base["parsed"] = self.parsed 

167 return base 

168 

169 

170class Parameter: 

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

172 

173 def __init__( 

174 self, 

175 name: str, 

176 *, 

177 annotation: str | Expr | None = None, 

178 kind: ParameterKind | None = None, 

179 default: str | Expr | None = None, 

180 docstring: Docstring | None = None, 

181 ) -> None: 

182 """Initialize the parameter. 

183 

184 Parameters: 

185 name: The parameter name. 

186 annotation: The parameter annotation, if any. 

187 kind: The parameter kind. 

188 default: The parameter default, if any. 

189 docstring: The parameter docstring. 

190 """ 

191 self.name: str = name 

192 """The parameter name.""" 

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

194 """The parameter type annotation.""" 

195 self.kind: ParameterKind | None = kind 

196 """The parameter kind.""" 

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

198 """The parameter default value.""" 

199 self.docstring: Docstring | None = docstring 

200 """The parameter docstring.""" 

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

202 # when the parameters are assigned to the function. 

203 self.function: Function | None = None 

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

205 

206 def __str__(self) -> str: 

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

208 if self.kind: 

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

210 return param 

211 

212 def __repr__(self) -> str: 

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

214 

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

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

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

218 return NotImplemented 

219 return ( 

220 self.name == __value.name 

221 and self.annotation == __value.annotation 

222 and self.kind == __value.kind 

223 and self.default == __value.default 

224 ) 

225 

226 @property 

227 def required(self) -> bool: 

228 """Whether this parameter is required.""" 

229 return self.default is None 

230 

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

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

233 

234 Parameters: 

235 **kwargs: Additional serialization options. 

236 

237 Returns: 

238 A dictionary. 

239 """ 

240 base: dict[str, Any] = { 

241 "name": self.name, 

242 "annotation": self.annotation, 

243 "kind": self.kind, 

244 "default": self.default, 

245 } 

246 if self.docstring: 

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

248 return base 

249 

250 

251class Parameters: 

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

253 

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

255 

256 ```pycon 

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

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

259 True 

260 ``` 

261 """ 

262 

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

264 """Initialize the parameters container. 

265 

266 Parameters: 

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

268 """ 

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

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

271 for parameter in parameters: 

272 self.add(parameter) 

273 

274 def __repr__(self) -> str: 

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

276 

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

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

279 if isinstance(name_or_index, int): 

280 return self._parameters_list[name_or_index] 

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

282 

283 def __len__(self): 

284 """The number of parameters.""" 

285 return len(self._parameters_list) 

286 

287 def __iter__(self): 

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

289 return iter(self._parameters_list) 

290 

291 def __contains__(self, param_name: str): 

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

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

294 

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

296 """Add a parameter to the container. 

297 

298 Parameters: 

299 parameter: The function parameter to add. 

300 

301 Raises: 

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

303 """ 

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

305 self._parameters_dict[parameter.name] = parameter 

306 self._parameters_list.append(parameter) 

307 else: 

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

309 

310 

311class Object(ObjectAliasMixin): 

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

313 

314 kind: Kind 

315 """The object kind.""" 

316 is_alias: bool = False 

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

318 is_collection: bool = False 

319 """Always false for objects.""" 

320 inherited: bool = False 

321 """Always false for objects. 

322 

323 Only aliases can be marked as inherited. 

324 """ 

325 

326 def __init__( 

327 self, 

328 name: str, 

329 *, 

330 lineno: int | None = None, 

331 endlineno: int | None = None, 

332 runtime: bool = True, 

333 docstring: Docstring | None = None, 

334 parent: Module | Class | None = None, 

335 lines_collection: LinesCollection | None = None, 

336 modules_collection: ModulesCollection | None = None, 

337 ) -> None: 

338 """Initialize the object. 

339 

340 Parameters: 

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

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

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

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

345 docstring: The object docstring. 

346 parent: The object parent. 

347 lines_collection: A collection of source code lines. 

348 modules_collection: A collection of modules. 

349 """ 

350 self.name: str = name 

351 """The object name.""" 

352 

353 self.lineno: int | None = lineno 

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

355 

356 self.endlineno: int | None = endlineno 

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

358 

359 self.docstring: Docstring | None = docstring 

360 """The object docstring.""" 

361 

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

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

364 

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

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

367 

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

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

370 

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

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

373 

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

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

376 """ 

377 

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

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

380 

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

382 like other lists of exports coming from submodules: 

383 

384 ```python 

385 from .submodule import __all__ as submodule_all 

386 

387 __all__ = ["hello", *submodule_all] 

388 ``` 

389 

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

391 """ 

392 

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

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

395 

396 self.runtime: bool = runtime 

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

398 

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

400 are not available at runtime. 

401 """ 

402 

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

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

405 

406 self.public: bool | None = None 

407 """Whether this object is public.""" 

408 

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

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

411 

412 self._lines_collection: LinesCollection | None = lines_collection 

413 self._modules_collection: ModulesCollection | None = modules_collection 

414 

415 # attach the docstring to this object 

416 if docstring: 

417 docstring.parent = self 

418 

419 def __repr__(self) -> str: 

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

421 

422 # Prevent using `__len__`. 

423 def __bool__(self) -> bool: 

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

425 return True 

426 

427 def __len__(self) -> int: 

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

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

430 

431 @property 

432 def has_docstring(self) -> bool: 

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

434 return bool(self.docstring) 

435 

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

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

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

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

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

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

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

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

444 @property 

445 def has_docstrings(self) -> bool: 

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

447 

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

449 unless they are also public. 

450 """ 

451 if self.has_docstring: 

452 return True 

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

454 try: 

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

456 return True 

457 except AliasResolutionError: 

458 continue 

459 return False 

460 

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

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

463 

464 Parameters: 

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

466 

467 Raises: 

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

469 

470 Returns: 

471 True or False. 

472 """ 

473 if isinstance(kind, set): 

474 if not kind: 

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

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

477 if isinstance(kind, str): 

478 kind = Kind(kind) 

479 return self.kind is kind 

480 

481 @cached_property 

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

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

484 

485 This method is part of the consumer API: 

486 do not use when producing Griffe trees! 

487 """ 

488 if not isinstance(self, Class): 

489 return {} 

490 try: 

491 mro = self.mro() 

492 except ValueError as error: 

493 logger.debug(error) 

494 return {} 

495 inherited_members = {} 

496 for base in reversed(mro): 

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

498 if name not in self.members: 

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

500 return inherited_members 

501 

502 @property 

503 def is_module(self) -> bool: 

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

505 return self.kind is Kind.MODULE 

506 

507 @property 

508 def is_class(self) -> bool: 

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

510 return self.kind is Kind.CLASS 

511 

512 @property 

513 def is_function(self) -> bool: 

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

515 return self.kind is Kind.FUNCTION 

516 

517 @property 

518 def is_attribute(self) -> bool: 

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

520 return self.kind is Kind.ATTRIBUTE 

521 

522 @property 

523 def is_init_module(self) -> bool: 

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

525 return False 

526 

527 @property 

528 def is_package(self) -> bool: 

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

530 return False 

531 

532 @property 

533 def is_subpackage(self) -> bool: 

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

535 return False 

536 

537 @property 

538 def is_namespace_package(self) -> bool: 

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

540 return False 

541 

542 @property 

543 def is_namespace_subpackage(self) -> bool: 

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

545 return False 

546 

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

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

549 

550 Parameters: 

551 *labels: Labels that must be present. 

552 

553 Returns: 

554 True or False. 

555 """ 

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

557 

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

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

560 

561 Parameters: 

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

563 

564 Returns: 

565 A dictionary of members. 

566 """ 

567 if not predicates: 

568 return self.members 

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

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

571 } 

572 return members 

573 

574 @property 

575 def module(self) -> Module: 

576 """The parent module of this object. 

577 

578 Examples: 

579 >>> import griffe 

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

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

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

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

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

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

586 

587 Raises: 

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

589 """ 

590 if isinstance(self, Module): 

591 return self 

592 if self.parent is not None: 

593 return self.parent.module 

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

595 

596 @property 

597 def package(self) -> Module: 

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

599 

600 Examples: 

601 >>> import griffe 

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

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

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

605 """ 

606 module = self.module 

607 while module.parent: 

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

609 return module 

610 

611 @property 

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

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

614 

615 Examples: 

616 >>> import griffe 

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

618 >>> markdown.filepath 

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

620 """ 

621 return self.module.filepath 

622 

623 @property 

624 def relative_package_filepath(self) -> Path: 

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

626 

627 Raises: 

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

629 """ 

630 package_path = self.package.filepath 

631 

632 # Current "module" is a namespace package. 

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

634 # Current package is a namespace package. 

635 if isinstance(package_path, list): 

636 for pkg_path in package_path: 

637 for self_path in self.filepath: 

638 with suppress(ValueError): 

639 return self_path.relative_to(pkg_path.parent) 

640 

641 # Current package is a regular package. 

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

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

644 else: 

645 for self_path in self.filepath: 

646 with suppress(ValueError): 

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

648 raise ValueError 

649 

650 # Current package is a namespace package, 

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

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

653 for pkg_path in package_path: 

654 with suppress(ValueError): 

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

656 raise ValueError 

657 

658 # Current package is a regular package, 

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

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

661 # of the package (search path). 

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

663 

664 @property 

665 def relative_filepath(self) -> Path: 

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

667 

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

669 

670 Raises: 

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

672 """ 

673 cwd = Path.cwd() 

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

675 for self_path in self.filepath: 

676 with suppress(ValueError): 

677 return self_path.relative_to(cwd) 

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

679 try: 

680 return self.filepath.relative_to(cwd) 

681 except ValueError: 

682 return self.filepath 

683 

684 @property 

685 def path(self) -> str: 

686 """The dotted path of this object. 

687 

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

689 

690 Examples: 

691 >>> import griffe 

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

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

694 'markdown.core.Markdown.references' 

695 """ 

696 return self.canonical_path 

697 

698 @property 

699 def canonical_path(self) -> str: 

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

701 

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

703 """ 

704 if self.parent is None: 

705 return self.name 

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

707 

708 @property 

709 def modules_collection(self) -> ModulesCollection: 

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

711 

712 Raises: 

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

714 """ 

715 if self._modules_collection is not None: 

716 return self._modules_collection 

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

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

719 return self.parent.modules_collection 

720 

721 @property 

722 def lines_collection(self) -> LinesCollection: 

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

724 

725 Raises: 

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

727 """ 

728 if self._lines_collection is not None: 

729 return self._lines_collection 

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

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

732 return self.parent.lines_collection 

733 

734 @property 

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

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

737 try: 

738 filepath = self.filepath 

739 except BuiltinModuleError: 

740 return [] 

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

742 return [] 

743 try: 

744 lines = self.lines_collection[filepath] 

745 except KeyError: 

746 return [] 

747 if self.is_module: 

748 return lines 

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

750 return [] 

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

752 

753 @property 

754 def source(self) -> str: 

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

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

757 

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

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

760 

761 Parameters: 

762 name: The name to resolve. 

763 

764 Raises: 

765 NameResolutionError: When the name could not be resolved. 

766 

767 Returns: 

768 The resolved name. 

769 """ 

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

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

772 

773 # Name is a member this object. 

774 if name in self.members: 

775 if self.members[name].is_alias: 

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

777 return self.members[name].path 

778 

779 # Name unknown and no more parent scope. 

780 if self.parent is None: 

781 # could be a built-in 

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

783 

784 # Name is parent, non-module object. 

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

786 return self.parent.path 

787 

788 # Recurse in parent. 

789 return self.parent.resolve(name) 

790 

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

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

793 

794 Parameters: 

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

796 **kwargs: Additional serialization options. 

797 

798 Returns: 

799 A dictionary. 

800 """ 

801 base: dict[str, Any] = { 

802 "kind": self.kind, 

803 "name": self.name, 

804 } 

805 

806 if full: 

807 base.update( 

808 { 

809 "path": self.path, 

810 "filepath": self.filepath, 

811 "relative_filepath": self.relative_filepath, 

812 "relative_package_filepath": self.relative_package_filepath, 

813 }, 

814 ) 

815 

816 if self.lineno is not None: 

817 base["lineno"] = self.lineno 

818 if self.endlineno is not None: 

819 base["endlineno"] = self.endlineno 

820 if self.docstring: 

821 base["docstring"] = self.docstring 

822 

823 base["labels"] = self.labels 

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

825 

826 return base 

827 

828 

829class Alias(ObjectAliasMixin): 

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

831 

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

833 but were imported from another module. 

834 

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

836 

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

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

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

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

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

842 """ 

843 

844 is_alias: bool = True 

845 """Always true for aliases.""" 

846 is_collection: bool = False 

847 """Always false for aliases.""" 

848 

849 def __init__( 

850 self, 

851 name: str, 

852 target: str | Object | Alias, 

853 *, 

854 lineno: int | None = None, 

855 endlineno: int | None = None, 

856 runtime: bool = True, 

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

858 inherited: bool = False, 

859 ) -> None: 

860 """Initialize the alias. 

861 

862 Parameters: 

863 name: The alias name. 

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

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

866 lineno: The alias starting line number. 

867 endlineno: The alias ending line number. 

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

869 parent: The alias parent. 

870 inherited: Whether this alias wraps an inherited member. 

871 """ 

872 self.name: str = name 

873 """The alias name.""" 

874 

875 self.alias_lineno: int | None = lineno 

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

877 

878 self.alias_endlineno: int | None = endlineno 

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

880 

881 self.runtime: bool = runtime 

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

883 

884 self.inherited: bool = inherited 

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

886 

887 self.public: bool | None = None 

888 """Whether this alias is public.""" 

889 

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

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

892 

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

894 self._passed_through: bool = False 

895 

896 self.target_path: str 

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

898 

899 if isinstance(target, str): 

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

901 self.target_path = target 

902 else: 

903 self._target = target 

904 self.target_path = target.path 

905 self._update_target_aliases() 

906 

907 def __repr__(self) -> str: 

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

909 

910 # Prevent using `__len__`. 

911 def __bool__(self) -> bool: 

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

913 return True 

914 

915 def __len__(self) -> int: 

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

917 return 1 

918 

919 # SPECIAL PROXIES ------------------------------- 

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

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

922 

923 @property 

924 def kind(self) -> Kind: 

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

926 # custom behavior to avoid raising exceptions 

927 try: 

928 return self.final_target.kind 

929 except (AliasResolutionError, CyclicAliasError): 

930 return Kind.ALIAS 

931 

932 @property 

933 def has_docstring(self) -> bool: 

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

935 try: 

936 return self.final_target.has_docstring 

937 except (AliasResolutionError, CyclicAliasError): 

938 return False 

939 

940 @property 

941 def has_docstrings(self) -> bool: 

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

943 try: 

944 return self.final_target.has_docstrings 

945 except (AliasResolutionError, CyclicAliasError): 

946 return False 

947 

948 @property 

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

950 """The parent of this alias.""" 

951 return self._parent 

952 

953 @parent.setter 

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

955 self._parent = value 

956 self._update_target_aliases() 

957 

958 @property 

959 def path(self) -> str: 

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

961 return f"{self.parent.path}.{self.name}" # type: ignore[union-attr] # we assume there's always a parent 

962 

963 @property 

964 def modules_collection(self) -> ModulesCollection: 

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

966 # no need to forward to the target 

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

968 

969 @cached_property 

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

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

972 final_target = self.final_target 

973 

974 # We recreate aliases to maintain a correct hierarchy, 

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

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

977 # not the original member's path. 

978 return { 

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

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

981 } 

982 

983 @cached_property 

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

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

986 

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

988 to preserve correct object access paths. 

989 

990 This method is part of the consumer API: 

991 do not use when producing Griffe trees! 

992 """ 

993 final_target = self.final_target 

994 

995 # We recreate aliases to maintain a correct hierarchy, 

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

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

998 # not the original member's path. 

999 return { 

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

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

1002 } 

1003 

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

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

1006 

1007 Parameters: 

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

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

1010 

1011 Returns: 

1012 A JSON string. 

1013 """ 

1014 try: 

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

1016 except (AliasResolutionError, CyclicAliasError): 

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

1018 

1019 # GENERIC OBJECT PROXIES -------------------------------- 

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

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

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

1023 

1024 @property 

1025 def extra(self) -> dict: 

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

1027 return self.final_target.extra 

1028 

1029 @property 

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

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

1032 return self.final_target.lineno 

1033 

1034 @property 

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

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

1037 return self.final_target.endlineno 

1038 

1039 @property 

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

1041 """The target docstring.""" 

1042 return self.final_target.docstring 

1043 

1044 @docstring.setter 

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

1046 self.final_target.docstring = docstring 

1047 

1048 @property 

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

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

1051 return self.final_target.labels 

1052 

1053 @property 

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

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

1056 

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

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

1059 """ 

1060 return self.final_target.imports 

1061 

1062 @property 

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

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

1065 

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

1067 like other lists of exports coming from submodules: 

1068 

1069 ```python 

1070 from .submodule import __all__ as submodule_all 

1071 

1072 __all__ = ["hello", *submodule_all] 

1073 ``` 

1074 

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

1076 """ 

1077 return self.final_target.exports 

1078 

1079 @property 

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

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

1082 return self.final_target.aliases 

1083 

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

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

1086 

1087 Parameters: 

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

1089 

1090 Raises: 

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

1092 

1093 Returns: 

1094 True or False. 

1095 """ 

1096 return self.final_target.is_kind(kind) 

1097 

1098 @property 

1099 def is_module(self) -> bool: 

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

1101 return self.final_target.is_module 

1102 

1103 @property 

1104 def is_class(self) -> bool: 

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

1106 return self.final_target.is_class 

1107 

1108 @property 

1109 def is_function(self) -> bool: 

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

1111 return self.final_target.is_function 

1112 

1113 @property 

1114 def is_attribute(self) -> bool: 

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

1116 return self.final_target.is_attribute 

1117 

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

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

1120 

1121 Parameters: 

1122 *labels: Labels that must be present. 

1123 

1124 Returns: 

1125 True or False. 

1126 """ 

1127 return self.final_target.has_labels(*labels) 

1128 

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

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

1131 

1132 Parameters: 

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

1134 

1135 Returns: 

1136 A dictionary of members. 

1137 """ 

1138 return self.final_target.filter_members(*predicates) 

1139 

1140 @property 

1141 def module(self) -> Module: 

1142 """The parent module of this object. 

1143 

1144 Raises: 

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

1146 """ 

1147 return self.final_target.module 

1148 

1149 @property 

1150 def package(self) -> Module: 

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

1152 return self.final_target.package 

1153 

1154 @property 

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

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

1157 return self.final_target.filepath 

1158 

1159 @property 

1160 def relative_filepath(self) -> Path: 

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

1162 

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

1164 

1165 Raises: 

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

1167 """ 

1168 return self.final_target.relative_filepath 

1169 

1170 @property 

1171 def relative_package_filepath(self) -> Path: 

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

1173 

1174 Raises: 

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

1176 """ 

1177 return self.final_target.relative_package_filepath 

1178 

1179 @property 

1180 def canonical_path(self) -> str: 

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

1182 

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

1184 """ 

1185 return self.final_target.canonical_path 

1186 

1187 @property 

1188 def lines_collection(self) -> LinesCollection: 

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

1190 

1191 Raises: 

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

1193 """ 

1194 return self.final_target.lines_collection 

1195 

1196 @property 

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

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

1199 return self.final_target.lines 

1200 

1201 @property 

1202 def source(self) -> str: 

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

1204 return self.final_target.source 

1205 

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

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

1208 

1209 Parameters: 

1210 name: The name to resolve. 

1211 

1212 Raises: 

1213 NameResolutionError: When the name could not be resolved. 

1214 

1215 Returns: 

1216 The resolved name. 

1217 """ 

1218 return self.final_target.resolve(name) 

1219 

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

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

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

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

1224 

1225 @property 

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

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

1228 

1229 @property 

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

1231 """The class bases.""" 

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

1233 

1234 @property 

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

1236 """The class/function decorators.""" 

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

1238 

1239 @property 

1240 def imports_future_annotations(self) -> bool: 

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

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

1243 

1244 @property 

1245 def is_init_module(self) -> bool: 

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

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

1248 

1249 @property 

1250 def is_package(self) -> bool: 

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

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

1253 

1254 @property 

1255 def is_subpackage(self) -> bool: 

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

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

1258 

1259 @property 

1260 def is_namespace_package(self) -> bool: 

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

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

1263 

1264 @property 

1265 def is_namespace_subpackage(self) -> bool: 

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

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

1268 

1269 @property 

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

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

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

1273 

1274 @overloads.setter 

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

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

1277 

1278 @property 

1279 def parameters(self) -> Parameters: 

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

1281 

1282 This property can fetch inherited members, 

1283 and therefore is part of the consumer API: 

1284 do not use when producing Griffe trees! 

1285 """ 

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

1287 

1288 @property 

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

1290 """The function return type annotation.""" 

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

1292 

1293 @returns.setter 

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

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

1296 

1297 @property 

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

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

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

1301 

1302 @property 

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

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

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

1306 

1307 @property 

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

1309 """The attribute value.""" 

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

1311 

1312 @property 

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

1314 """The attribute type annotation.""" 

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

1316 

1317 @annotation.setter 

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

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

1320 

1321 @property 

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

1323 """Resolved class bases. 

1324 

1325 This method is part of the consumer API: 

1326 do not use when producing Griffe trees! 

1327 """ 

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

1329 

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

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

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

1333 

1334 # SPECIFIC ALIAS METHOD AND PROPERTIES ----------------- 

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

1336 # they are specific to aliases. 

1337 

1338 @property 

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

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

1341 

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

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

1344 """ 

1345 if not self.resolved: 

1346 self.resolve_target() 

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

1348 

1349 @target.setter 

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

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

1352 raise CyclicAliasError([self.target_path]) 

1353 self._target = value 

1354 self.target_path = value.path 

1355 if self.parent is not None: 

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

1357 

1358 @property 

1359 def final_target(self) -> Object: 

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

1361 

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

1363 """ 

1364 # Here we quickly iterate on the alias chain, 

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

1366 

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

1368 # as already resolved, and can contain cycles. 

1369 

1370 # using a dict as an ordered set 

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

1372 target = self 

1373 while target.is_alias: 

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

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

1376 paths_seen[target.path] = None 

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

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

1379 

1380 def resolve_target(self) -> None: 

1381 """Resolve the target. 

1382 

1383 Raises: 

1384 AliasResolutionError: When the target cannot be resolved. 

1385 It happens when the target does not exist, 

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

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

1388 and added to the collection. 

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

1390 """ 

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

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

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

1394 # through an alias will raise a CyclicAliasError. 

1395 

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

1397 # the whole chain stays unresolved. This prevents 

1398 # bad surprises later, in code that checks if 

1399 # an alias is resolved by checking only 

1400 # the first link of the chain. 

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

1402 raise CyclicAliasError([self.target_path]) 

1403 self._passed_through = True 

1404 try: 

1405 self._resolve_target() 

1406 finally: 

1407 self._passed_through = False 

1408 

1409 def _resolve_target(self) -> None: 

1410 try: 

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

1412 except KeyError as error: 

1413 raise AliasResolutionError(self) from error 

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

1415 raise CyclicAliasError([self.target_path]) 

1416 if resolved.is_alias and not resolved.resolved: 

1417 try: 

1418 resolved.resolve_target() 

1419 except CyclicAliasError as error: 

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

1421 self._target = resolved 

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

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

1424 

1425 def _update_target_aliases(self) -> None: 

1426 with suppress(AttributeError, AliasResolutionError, CyclicAliasError): 

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

1428 

1429 @property 

1430 def resolved(self) -> bool: 

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

1432 return self._target is not None 

1433 

1434 @property 

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

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

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

1438 return self.target_path 

1439 return None 

1440 

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

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

1443 

1444 Parameters: 

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

1446 **kwargs: Additional serialization options. 

1447 

1448 Returns: 

1449 A dictionary. 

1450 """ 

1451 base: dict[str, Any] = { 

1452 "kind": Kind.ALIAS, 

1453 "name": self.name, 

1454 "target_path": self.target_path, 

1455 } 

1456 

1457 if full: 

1458 base["path"] = self.path 

1459 

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

1461 base["lineno"] = self.alias_lineno 

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

1463 base["endlineno"] = self.alias_endlineno 

1464 

1465 return base 

1466 

1467 

1468class Module(Object): 

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

1470 

1471 kind = Kind.MODULE 

1472 

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

1474 """Initialize the module. 

1475 

1476 Parameters: 

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

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

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

1480 """ 

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

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

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

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

1485 

1486 def __repr__(self) -> str: 

1487 try: 

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

1489 except BuiltinModuleError: 

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

1491 

1492 @property 

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

1494 """The file path of this module. 

1495 

1496 Raises: 

1497 BuiltinModuleError: When the instance filepath is None. 

1498 """ 

1499 if self._filepath is None: 

1500 raise BuiltinModuleError(self.name) 

1501 return self._filepath 

1502 

1503 @property 

1504 def imports_future_annotations(self) -> bool: 

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

1506 return ( 

1507 "annotations" in self.members 

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

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

1510 ) 

1511 

1512 @property 

1513 def is_init_module(self) -> bool: 

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

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

1516 return False 

1517 try: 

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

1519 except BuiltinModuleError: 

1520 return False 

1521 

1522 @property 

1523 def is_package(self) -> bool: 

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

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

1526 

1527 @property 

1528 def is_subpackage(self) -> bool: 

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

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

1531 

1532 @property 

1533 def is_namespace_package(self) -> bool: 

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

1535 try: 

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

1537 except BuiltinModuleError: 

1538 return False 

1539 

1540 @property 

1541 def is_namespace_subpackage(self) -> bool: 

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

1543 try: 

1544 return ( 

1545 self.parent is not None 

1546 and isinstance(self.filepath, list) 

1547 and ( 

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

1549 ) 

1550 ) 

1551 except BuiltinModuleError: 

1552 return False 

1553 

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

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

1556 

1557 Parameters: 

1558 **kwargs: Additional serialization options. 

1559 

1560 Returns: 

1561 A dictionary. 

1562 """ 

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

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

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

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

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

1568 else: 

1569 base["filepath"] = None 

1570 return base 

1571 

1572 

1573class Class(Object): 

1574 """The class representing a Python class.""" 

1575 

1576 kind = Kind.CLASS 

1577 

1578 def __init__( 

1579 self, 

1580 *args: Any, 

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

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

1583 **kwargs: Any, 

1584 ) -> None: 

1585 """Initialize the class. 

1586 

1587 Parameters: 

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

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

1590 decorators: The class decorators, if any. 

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

1592 """ 

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

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

1595 """The class bases.""" 

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

1597 """The class decorators.""" 

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

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

1600 

1601 @property 

1602 def parameters(self) -> Parameters: 

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

1604 

1605 This property fetches inherited members, 

1606 and therefore is part of the consumer API: 

1607 do not use when producing Griffe trees! 

1608 """ 

1609 try: 

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

1611 except KeyError: 

1612 return Parameters() 

1613 

1614 @cached_property 

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

1616 """Resolved class bases. 

1617 

1618 This method is part of the consumer API: 

1619 do not use when producing Griffe trees! 

1620 """ 

1621 resolved_bases = [] 

1622 for base in self.bases: 

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

1624 try: 

1625 resolved_base = self.modules_collection[base_path] 

1626 if resolved_base.is_alias: 

1627 resolved_base = resolved_base.final_target 

1628 except (AliasResolutionError, CyclicAliasError, KeyError): 

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

1630 else: 

1631 resolved_bases.append(resolved_base) 

1632 return resolved_bases 

1633 

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

1635 seen = (*seen, self.path) 

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

1637 if not bases: 

1638 return [self] 

1639 for base in bases: 

1640 if base.path in seen: 

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

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

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

1644 

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

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

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

1648 

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

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

1651 

1652 Parameters: 

1653 **kwargs: Additional serialization options. 

1654 

1655 Returns: 

1656 A dictionary. 

1657 """ 

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

1659 base["bases"] = self.bases 

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

1661 return base 

1662 

1663 

1664class Function(Object): 

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

1666 

1667 kind = Kind.FUNCTION 

1668 

1669 def __init__( 

1670 self, 

1671 *args: Any, 

1672 parameters: Parameters | None = None, 

1673 returns: str | Expr | None = None, 

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

1675 **kwargs: Any, 

1676 ) -> None: 

1677 """Initialize the function. 

1678 

1679 Parameters: 

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

1681 parameters: The function parameters. 

1682 returns: The function return annotation. 

1683 decorators: The function decorators, if any. 

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

1685 """ 

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

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

1688 """The function parameters.""" 

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

1690 """The function return type annotation.""" 

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

1692 """The function decorators.""" 

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

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

1695 

1696 for parameter in self.parameters: 

1697 parameter.function = self 

1698 

1699 @property 

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

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

1702 return self.returns 

1703 

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

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

1706 

1707 Parameters: 

1708 **kwargs: Additional serialization options. 

1709 

1710 Returns: 

1711 A dictionary. 

1712 """ 

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

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

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

1716 base["returns"] = self.returns 

1717 return base 

1718 

1719 

1720class Attribute(Object): 

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

1722 

1723 kind = Kind.ATTRIBUTE 

1724 

1725 def __init__( 

1726 self, 

1727 *args: Any, 

1728 value: str | Expr | None = None, 

1729 annotation: str | Expr | None = None, 

1730 **kwargs: Any, 

1731 ) -> None: 

1732 """Initialize the function. 

1733 

1734 Parameters: 

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

1736 value: The attribute value, if any. 

1737 annotation: The attribute annotation, if any. 

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

1739 """ 

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

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

1742 """The attribute value.""" 

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

1744 """The attribute type annotation.""" 

1745 self.setter: Function | None = None 

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

1747 self.deleter: Function | None = None 

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

1749 

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

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

1752 

1753 Parameters: 

1754 **kwargs: Additional serialization options. 

1755 

1756 Returns: 

1757 A dictionary. 

1758 """ 

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

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

1761 base["value"] = self.value 

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

1763 base["annotation"] = self.annotation 

1764 return base