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
« 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.
4from __future__ import annotations
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
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
21if TYPE_CHECKING:
22 from collections.abc import Sequence
24 from _griffe.collections import LinesCollection, ModulesCollection
25 from _griffe.docstrings.models import DocstringSection
26 from _griffe.expressions import Expr
28from functools import cached_property
31class Decorator:
32 """This class represents decorators."""
34 def __init__(self, value: str | Expr, *, lineno: int | None, endlineno: int | None) -> None:
35 """Initialize the decorator.
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."""
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
55 def as_dict(self, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002
56 """Return this decorator's data as a dictionary.
58 Parameters:
59 **kwargs: Additional serialization options.
61 Returns:
62 A dictionary.
63 """
64 return {
65 "value": self.value,
66 "lineno": self.lineno,
67 "endlineno": self.endlineno,
68 }
71class Docstring:
72 """This class represents docstrings."""
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.
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."""
107 @property
108 def lines(self) -> list[str]:
109 """The lines of the docstring."""
110 return self.value.split("\n")
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])
123 @cached_property
124 def parsed(self) -> list[DocstringSection]:
125 """The docstring sections, parsed into structured data."""
126 return self.parse()
128 def parse(
129 self,
130 parser: DocstringStyle | Parser | None = None,
131 **options: Any,
132 ) -> list[DocstringSection]:
133 """Parse the docstring into structured data.
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.
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))
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.
153 Parameters:
154 full: Whether to return full info, or just base info.
155 **kwargs: Additional serialization options.
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
170class Parameter:
171 """This class represent a function parameter."""
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.
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."""
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
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})"
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 )
226 @property
227 def required(self) -> bool:
228 """Whether this parameter is required."""
229 return self.default is None
231 def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002
232 """Return this parameter's data as a dictionary.
234 Parameters:
235 **kwargs: Additional serialization options.
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
251class Parameters:
252 """This class is a container for parameters.
254 It allows to get parameters using their position (index) or their name:
256 ```pycon
257 >>> parameters = Parameters(Parameter("hello"))
258 >>> parameters[0] is parameters["hello"]
259 True
260 ```
261 """
263 def __init__(self, *parameters: Parameter) -> None:
264 """Initialize the parameters container.
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)
274 def __repr__(self) -> str:
275 return f"Parameters({', '.join(repr(param) for param in self._parameters_list)})"
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("*")]
283 def __len__(self):
284 """The number of parameters."""
285 return len(self._parameters_list)
287 def __iter__(self):
288 """Iterate over the parameters, in order."""
289 return iter(self._parameters_list)
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
295 def add(self, parameter: Parameter) -> None:
296 """Add a parameter to the container.
298 Parameters:
299 parameter: The function parameter to add.
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")
311class Object(ObjectAliasMixin):
312 """An abstract class representing a Python object."""
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.
323 Only aliases can be marked as inherited.
324 """
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.
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."""
353 self.lineno: int | None = lineno
354 """The starting line number of the object."""
356 self.endlineno: int | None = endlineno
357 """The ending line number of the object."""
359 self.docstring: Docstring | None = docstring
360 """The object docstring."""
362 self.parent: Module | Class | None = parent
363 """The parent of the object (none if top module)."""
365 self.members: dict[str, Object | Alias] = {}
366 """The object members (modules, classes, functions, attributes)."""
368 self.labels: set[str] = set()
369 """The object labels (`property`, `dataclass`, etc.)."""
371 self.imports: dict[str, str] = {}
372 """The other objects imported by this object.
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 """
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.
381 Exports can contain string (object names) or resolvable names,
382 like other lists of exports coming from submodules:
384 ```python
385 from .submodule import __all__ as submodule_all
387 __all__ = ["hello", *submodule_all]
388 ```
390 Exports get expanded by the loader before it expands wildcards and resolves aliases.
391 """
393 self.aliases: dict[str, Alias] = {}
394 """The aliases pointing to this object."""
396 self.runtime: bool = runtime
397 """Whether this object is available at runtime.
399 Typically, type-guarded objects (under an `if TYPE_CHECKING` condition)
400 are not available at runtime.
401 """
403 self.extra: dict[str, dict[str, Any]] = defaultdict(dict)
404 """Namespaced dictionaries storing extra metadata for this object, used by extensions."""
406 self.public: bool | None = None
407 """Whether this object is public."""
409 self.deprecated: bool | str | None = None
410 """Whether this object is deprecated (boolean or deprecation message)."""
412 self._lines_collection: LinesCollection | None = lines_collection
413 self._modules_collection: ModulesCollection | None = modules_collection
415 # attach the docstring to this object
416 if docstring:
417 docstring.parent = self
419 def __repr__(self) -> str:
420 return f"{self.__class__.__name__}({self.name!r}, {self.lineno!r}, {self.endlineno!r})"
422 # Prevent using `__len__`.
423 def __bool__(self) -> bool:
424 """An object is always true-ish."""
425 return True
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())
431 @property
432 def has_docstring(self) -> bool:
433 """Whether this object has a docstring (empty or not)."""
434 return bool(self.docstring)
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).
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
461 def is_kind(self, kind: str | Kind | set[str | Kind]) -> bool:
462 """Tell if this object is of the given kind.
464 Parameters:
465 kind: An instance or set of kinds (strings or enumerations).
467 Raises:
468 ValueError: When an empty set is given as argument.
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
481 @cached_property
482 def inherited_members(self) -> dict[str, Alias]:
483 """Members that are inherited from base classes.
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
502 @property
503 def is_module(self) -> bool:
504 """Whether this object is a module."""
505 return self.kind is Kind.MODULE
507 @property
508 def is_class(self) -> bool:
509 """Whether this object is a class."""
510 return self.kind is Kind.CLASS
512 @property
513 def is_function(self) -> bool:
514 """Whether this object is a function."""
515 return self.kind is Kind.FUNCTION
517 @property
518 def is_attribute(self) -> bool:
519 """Whether this object is an attribute."""
520 return self.kind is Kind.ATTRIBUTE
522 @property
523 def is_init_module(self) -> bool:
524 """Whether this object is an `__init__.py` module."""
525 return False
527 @property
528 def is_package(self) -> bool:
529 """Whether this object is a package (top module)."""
530 return False
532 @property
533 def is_subpackage(self) -> bool:
534 """Whether this object is a subpackage."""
535 return False
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
542 @property
543 def is_namespace_subpackage(self) -> bool:
544 """Whether this object is a namespace subpackage."""
545 return False
547 def has_labels(self, *labels: str) -> bool:
548 """Tell if this object has all the given labels.
550 Parameters:
551 *labels: Labels that must be present.
553 Returns:
554 True or False.
555 """
556 return set(labels).issubset(self.labels)
558 def filter_members(self, *predicates: Callable[[Object | Alias], bool]) -> dict[str, Object | Alias]:
559 """Filter and return members based on predicates.
561 Parameters:
562 *predicates: A list of predicates, i.e. callables accepting a member as argument and returning a boolean.
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
574 @property
575 def module(self) -> Module:
576 """The parent module of this object.
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'))
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")
596 @property
597 def package(self) -> Module:
598 """The absolute top module (the package) of this object.
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
611 @property
612 def filepath(self) -> Path | list[Path]:
613 """The file path (or directory list for namespace packages) where this object was defined.
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
623 @property
624 def relative_package_filepath(self) -> Path:
625 """The file path where this object was defined, relative to the top module path.
627 Raises:
628 ValueError: When the relative path could not be computed.
629 """
630 package_path = self.package.filepath
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)
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
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
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)
664 @property
665 def relative_filepath(self) -> Path:
666 """The file path where this object was defined, relative to the current working directory.
668 If this object's file path is not relative to the current working directory, return its absolute path.
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
684 @property
685 def path(self) -> str:
686 """The dotted path of this object.
688 On regular objects (not aliases), the path is the canonical path.
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
698 @property
699 def canonical_path(self) -> str:
700 """The full dotted path of this object.
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}"
708 @property
709 def modules_collection(self) -> ModulesCollection:
710 """The modules collection attached to this object or its parents.
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
721 @property
722 def lines_collection(self) -> LinesCollection:
723 """The lines collection attached to this object or its parents.
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
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]
753 @property
754 def source(self) -> str:
755 """The source code of this object."""
756 return dedent("\n".join(self.lines))
758 def resolve(self, name: str) -> str:
759 """Resolve a name within this object's and parents' scope.
761 Parameters:
762 name: The name to resolve.
764 Raises:
765 NameResolutionError: When the name could not be resolved.
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?
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
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}")
784 # Name is parent, non-module object.
785 if name == self.parent.name and not self.parent.is_module:
786 return self.parent.path
788 # Recurse in parent.
789 return self.parent.resolve(name)
791 def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]:
792 """Return this object's data as a dictionary.
794 Parameters:
795 full: Whether to return full info, or just base info.
796 **kwargs: Additional serialization options.
798 Returns:
799 A dictionary.
800 """
801 base: dict[str, Any] = {
802 "kind": self.kind,
803 "name": self.name,
804 }
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 )
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
823 base["labels"] = self.labels
824 base["members"] = {name: member.as_dict(full=full, **kwargs) for name, member in self.members.items()}
826 return base
829class Alias(ObjectAliasMixin):
830 """This class represents an alias, or indirection, to an object declared in another module.
832 Aliases represent objects that are in the scope of a module or class,
833 but were imported from another module.
835 They behave almost exactly like regular objects, to a few exceptions:
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 """
844 is_alias: bool = True
845 """Always true for aliases."""
846 is_collection: bool = False
847 """Always false for aliases."""
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.
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."""
875 self.alias_lineno: int | None = lineno
876 """The starting line number of the alias."""
878 self.alias_endlineno: int | None = endlineno
879 """The ending line number of the alias."""
881 self.runtime: bool = runtime
882 """Whether this alias is available at runtime."""
884 self.inherited: bool = inherited
885 """Whether this alias represents an inherited member."""
887 self.public: bool | None = None
888 """Whether this alias is public."""
890 self.deprecated: str | bool | None = None
891 """Whether this alias is deprecated (boolean or deprecation message)."""
893 self._parent: Module | Class | Alias | None = parent
894 self._passed_through: bool = False
896 self.target_path: str
897 """The path of this alias' target."""
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()
907 def __repr__(self) -> str:
908 return f"Alias({self.name!r}, {self.target_path!r})"
910 # Prevent using `__len__`.
911 def __bool__(self) -> bool:
912 """An alias is always true-ish."""
913 return True
915 def __len__(self) -> int:
916 """The length of an alias is always 1."""
917 return 1
919 # SPECIAL PROXIES -------------------------------
920 # The following methods and properties exist on the target(s),
921 # but we must handle them in a special way.
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
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
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
948 @property
949 def parent(self) -> Module | Class | Alias | None:
950 """The parent of this alias."""
951 return self._parent
953 @parent.setter
954 def parent(self, value: Module | Class | Alias) -> None:
955 self._parent = value
956 self._update_target_aliases()
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
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
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
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 }
983 @cached_property
984 def inherited_members(self) -> dict[str, Alias]:
985 """Members that are inherited from base classes.
987 Each inherited member of the target will be wrapped in an alias,
988 to preserve correct object access paths.
990 This method is part of the consumer API:
991 do not use when producing Griffe trees!
992 """
993 final_target = self.final_target
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 }
1004 def as_json(self, *, full: bool = False, **kwargs: Any) -> str:
1005 """Return this target's data as a JSON string.
1007 Parameters:
1008 full: Whether to return full info, or just base info.
1009 **kwargs: Additional serialization options passed to encoder.
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)
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.
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
1029 @property
1030 def lineno(self) -> int | None:
1031 """The starting line number of the target object."""
1032 return self.final_target.lineno
1034 @property
1035 def endlineno(self) -> int | None:
1036 """The ending line number of the target object."""
1037 return self.final_target.endlineno
1039 @property
1040 def docstring(self) -> Docstring | None:
1041 """The target docstring."""
1042 return self.final_target.docstring
1044 @docstring.setter
1045 def docstring(self, docstring: Docstring | None) -> None:
1046 self.final_target.docstring = docstring
1048 @property
1049 def labels(self) -> set[str]:
1050 """The target labels (`property`, `dataclass`, etc.)."""
1051 return self.final_target.labels
1053 @property
1054 def imports(self) -> dict[str, str]:
1055 """The other objects imported by this alias' target.
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
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.
1066 Exports can contain string (object names) or resolvable names,
1067 like other lists of exports coming from submodules:
1069 ```python
1070 from .submodule import __all__ as submodule_all
1072 __all__ = ["hello", *submodule_all]
1073 ```
1075 Exports get expanded by the loader before it expands wildcards and resolves aliases.
1076 """
1077 return self.final_target.exports
1079 @property
1080 def aliases(self) -> dict[str, Alias]:
1081 """The aliases pointing to this object."""
1082 return self.final_target.aliases
1084 def is_kind(self, kind: str | Kind | set[str | Kind]) -> bool:
1085 """Tell if this object is of the given kind.
1087 Parameters:
1088 kind: An instance or set of kinds (strings or enumerations).
1090 Raises:
1091 ValueError: When an empty set is given as argument.
1093 Returns:
1094 True or False.
1095 """
1096 return self.final_target.is_kind(kind)
1098 @property
1099 def is_module(self) -> bool:
1100 """Whether this object is a module."""
1101 return self.final_target.is_module
1103 @property
1104 def is_class(self) -> bool:
1105 """Whether this object is a class."""
1106 return self.final_target.is_class
1108 @property
1109 def is_function(self) -> bool:
1110 """Whether this object is a function."""
1111 return self.final_target.is_function
1113 @property
1114 def is_attribute(self) -> bool:
1115 """Whether this object is an attribute."""
1116 return self.final_target.is_attribute
1118 def has_labels(self, *labels: str) -> bool:
1119 """Tell if this object has all the given labels.
1121 Parameters:
1122 *labels: Labels that must be present.
1124 Returns:
1125 True or False.
1126 """
1127 return self.final_target.has_labels(*labels)
1129 def filter_members(self, *predicates: Callable[[Object | Alias], bool]) -> dict[str, Object | Alias]:
1130 """Filter and return members based on predicates.
1132 Parameters:
1133 *predicates: A list of predicates, i.e. callables accepting a member as argument and returning a boolean.
1135 Returns:
1136 A dictionary of members.
1137 """
1138 return self.final_target.filter_members(*predicates)
1140 @property
1141 def module(self) -> Module:
1142 """The parent module of this object.
1144 Raises:
1145 ValueError: When the object is not a module and does not have a parent.
1146 """
1147 return self.final_target.module
1149 @property
1150 def package(self) -> Module:
1151 """The absolute top module (the package) of this object."""
1152 return self.final_target.package
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
1159 @property
1160 def relative_filepath(self) -> Path:
1161 """The file path where this object was defined, relative to the current working directory.
1163 If this object's file path is not relative to the current working directory, return its absolute path.
1165 Raises:
1166 ValueError: When the relative path could not be computed.
1167 """
1168 return self.final_target.relative_filepath
1170 @property
1171 def relative_package_filepath(self) -> Path:
1172 """The file path where this object was defined, relative to the top module path.
1174 Raises:
1175 ValueError: When the relative path could not be computed.
1176 """
1177 return self.final_target.relative_package_filepath
1179 @property
1180 def canonical_path(self) -> str:
1181 """The full dotted path of this object.
1183 The canonical path is the path where the object was defined (not imported).
1184 """
1185 return self.final_target.canonical_path
1187 @property
1188 def lines_collection(self) -> LinesCollection:
1189 """The lines collection attached to this object or its parents.
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
1196 @property
1197 def lines(self) -> list[str]:
1198 """The lines containing the source of this object."""
1199 return self.final_target.lines
1201 @property
1202 def source(self) -> str:
1203 """The source code of this object."""
1204 return self.final_target.source
1206 def resolve(self, name: str) -> str:
1207 """Resolve a name within this object's and parents' scope.
1209 Parameters:
1210 name: The name to resolve.
1212 Raises:
1213 NameResolutionError: When the name could not be resolved.
1215 Returns:
1216 The resolved name.
1217 """
1218 return self.final_target.resolve(name)
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.
1225 @property
1226 def _filepath(self) -> Path | list[Path] | None:
1227 return cast(Module, self.final_target)._filepath
1229 @property
1230 def bases(self) -> list[Expr | str]:
1231 """The class bases."""
1232 return cast(Class, self.final_target).bases
1234 @property
1235 def decorators(self) -> list[Decorator]:
1236 """The class/function decorators."""
1237 return cast(Union[Class, Function], self.target).decorators
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
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
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
1254 @property
1255 def is_subpackage(self) -> bool:
1256 """Whether this module is a subpackage."""
1257 return cast(Module, self.final_target).is_subpackage
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
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
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
1274 @overloads.setter
1275 def overloads(self, overloads: list[Function] | None) -> None:
1276 cast(Union[Module, Class, Function], self.final_target).overloads = overloads
1278 @property
1279 def parameters(self) -> Parameters:
1280 """The parameters of the current function or `__init__` method for classes.
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
1288 @property
1289 def returns(self) -> str | Expr | None:
1290 """The function return type annotation."""
1291 return cast(Function, self.final_target).returns
1293 @returns.setter
1294 def returns(self, returns: str | Expr | None) -> None:
1295 cast(Function, self.final_target).returns = returns
1297 @property
1298 def setter(self) -> Function | None:
1299 """The setter linked to this function (property)."""
1300 return cast(Attribute, self.final_target).setter
1302 @property
1303 def deleter(self) -> Function | None:
1304 """The deleter linked to this function (property)."""
1305 return cast(Attribute, self.final_target).deleter
1307 @property
1308 def value(self) -> str | Expr | None:
1309 """The attribute value."""
1310 return cast(Attribute, self.final_target).value
1312 @property
1313 def annotation(self) -> str | Expr | None:
1314 """The attribute type annotation."""
1315 return cast(Attribute, self.final_target).annotation
1317 @annotation.setter
1318 def annotation(self, annotation: str | Expr | None) -> None:
1319 cast(Attribute, self.final_target).annotation = annotation
1321 @property
1322 def resolved_bases(self) -> list[Object]:
1323 """Resolved class bases.
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
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()
1334 # SPECIFIC ALIAS METHOD AND PROPERTIES -----------------
1335 # These methods and properties do not exist on targets,
1336 # they are specific to aliases.
1338 @property
1339 def target(self) -> Object | Alias:
1340 """The resolved target (actual object), if possible.
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
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
1358 @property
1359 def final_target(self) -> Object:
1360 """The final, resolved target, if possible.
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.
1367 # The cycle detection is needed because alias chains can be created
1368 # as already resolved, and can contain cycles.
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]
1380 def resolve_target(self) -> None:
1381 """Resolve the target.
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.
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
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
1425 def _update_target_aliases(self) -> None:
1426 with suppress(AttributeError, AliasResolutionError, CyclicAliasError):
1427 self._target.aliases[self.path] = self # type: ignore[union-attr]
1429 @property
1430 def resolved(self) -> bool:
1431 """Whether this alias' target is resolved."""
1432 return self._target is not None
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
1441 def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002
1442 """Return this alias' data as a dictionary.
1444 Parameters:
1445 full: Whether to return full info, or just base info.
1446 **kwargs: Additional serialization options.
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 }
1457 if full:
1458 base["path"] = self.path
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
1465 return base
1468class Module(Object):
1469 """The class representing a Python module."""
1471 kind = Kind.MODULE
1473 def __init__(self, *args: Any, filepath: Path | list[Path] | None = None, **kwargs: Any) -> None:
1474 """Initialize the module.
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."""
1486 def __repr__(self) -> str:
1487 try:
1488 return f"Module({self.filepath!r})"
1489 except BuiltinModuleError:
1490 return f"Module({self.name!r})"
1492 @property
1493 def filepath(self) -> Path | list[Path]:
1494 """The file path of this module.
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
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 )
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
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
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
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
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
1554 def as_dict(self, **kwargs: Any) -> dict[str, Any]:
1555 """Return this module's data as a dictionary.
1557 Parameters:
1558 **kwargs: Additional serialization options.
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
1573class Class(Object):
1574 """The class representing a Python class."""
1576 kind = Kind.CLASS
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.
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."""
1601 @property
1602 def parameters(self) -> Parameters:
1603 """The parameters of this class' `__init__` method, if any.
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()
1614 @cached_property
1615 def resolved_bases(self) -> list[Object]:
1616 """Resolved class bases.
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
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)]
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
1649 def as_dict(self, **kwargs: Any) -> dict[str, Any]:
1650 """Return this class' data as a dictionary.
1652 Parameters:
1653 **kwargs: Additional serialization options.
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
1664class Function(Object):
1665 """The class representing a Python function."""
1667 kind = Kind.FUNCTION
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.
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."""
1696 for parameter in self.parameters:
1697 parameter.function = self
1699 @property
1700 def annotation(self) -> str | Expr | None:
1701 """The type annotation of the returned value."""
1702 return self.returns
1704 def as_dict(self, **kwargs: Any) -> dict[str, Any]:
1705 """Return this function's data as a dictionary.
1707 Parameters:
1708 **kwargs: Additional serialization options.
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
1720class Attribute(Object):
1721 """The class representing a Python module/class/instance attribute."""
1723 kind = Kind.ATTRIBUTE
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.
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."""
1750 def as_dict(self, **kwargs: Any) -> dict[str, Any]:
1751 """Return this function's data as a dictionary.
1753 Parameters:
1754 **kwargs: Additional serialization options.
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