Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add docstring and code source information to Parameter #288

Merged
merged 8 commits into from
Jun 12, 2024
18 changes: 16 additions & 2 deletions src/griffe/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def __init__(
annotation: str | Expr | None = None,
kind: ParameterKind | None = None,
default: str | Expr | None = None,
docstring: Docstring | None = None,
) -> None:
"""Initialize the parameter.

Expand All @@ -184,6 +185,7 @@ def __init__(
annotation: The parameter annotation, if any.
kind: The parameter kind.
default: The parameter default, if any.
docstring: The parameter docstring.
"""
self.name: str = name
"""The parameter name."""
Expand All @@ -193,6 +195,12 @@ def __init__(
"""The parameter kind."""
self.default: str | Expr | None = default
"""The parameter default value."""
self.docstring: Docstring | None = docstring
"""The parameter docstring."""
# The parent function is set in Function.__init__ when the
# parameter(s) are assigned to a function
self.parent: Function | None = None
pawamoy marked this conversation as resolved.
Show resolved Hide resolved
"""The parent function of the parameter."""

def __str__(self) -> str:
param = f"{self.name}: {self.annotation} = {self.default}"
Expand All @@ -218,7 +226,7 @@ def required(self) -> bool:
"""Whether this parameter is required."""
return self.default is None

def as_dict(self, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002
def as_dict(self, *, full: bool = False, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002
"""Return this parameter's data as a dictionary.

Parameters:
Expand All @@ -227,12 +235,15 @@ def as_dict(self, **kwargs: Any) -> dict[str, Any]: # noqa: ARG002
Returns:
A dictionary.
"""
return {
base: dict[str, Any] = {
"name": self.name,
"annotation": self.annotation,
"kind": self.kind,
"default": self.default,
}
if self.docstring:
base["docstring"] = self.docstring.as_dict(full=full)
return base


class Parameters:
Expand Down Expand Up @@ -1650,6 +1661,9 @@ def __init__(
self.overloads: list[Function] | None = None
"""The overloaded signatures of this function."""

for parameter in self.parameters:
parameter.parent = self
pawamoy marked this conversation as resolved.
Show resolved Hide resolved

@property
def annotation(self) -> str | Expr | None:
"""The type annotation of the returned value."""
Expand Down
1 change: 1 addition & 0 deletions src/griffe/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def _load_parameter(obj_dict: dict[str, Any]) -> Parameter:
annotation=obj_dict["annotation"],
kind=ParameterKind(obj_dict["kind"]),
default=obj_dict["default"],
docstring=_load_docstring(obj_dict),
)


Expand Down
1 change: 1 addition & 0 deletions src/griffe/extensions/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def _dataclass_parameters(class_: Class) -> list[Parameter]:
annotation=member.annotation,
kind=kind,
default=default,
docstring=member.docstring,
),
)

Expand Down
41 changes: 41 additions & 0 deletions tests/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,44 @@ def __init__(self, x: int):
module["A"].endlineno = 3
module["A"].lineno = None
assert not module["A"].source


def test_dataclass_parameter_docstrings() -> None:
"""Class parameters should have a docstring attribute."""
code = """
from dataclasses import dataclass, InitVar

@dataclass
class Base:
a: int
"Parameter a"
b: InitVar[int] = 3
"Parameter b"

@dataclass
class Derived(Base):
c: float
d: InitVar[float]
"Parameter d"
"""

with temporary_visited_package("package", {"__init__.py": code}) as module:
base = module["Base"]
param_self = base.parameters[0]
param_a = base.parameters[1]
param_b = base.parameters[2]
assert param_self.docstring is None
assert param_a.docstring.value == "Parameter a"
assert param_b.docstring.value == "Parameter b"

derived = module["Derived"]
param_self = derived.parameters[0]
param_a = derived.parameters[1]
param_b = derived.parameters[2]
param_c = derived.parameters[3]
param_d = derived.parameters[4]
assert param_self.docstring is None
assert param_a.docstring.value == "Parameter a"
assert param_b.docstring.value == "Parameter b"
assert param_c.docstring is None
assert param_d.docstring.value == "Parameter d"
Loading