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

Make "Yields" an official parsed section #3

Merged
merged 1 commit into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# (Fork) 0.0.3 (2023-08-28)

- Google, Numpy, Sphinx: Make "Yields" an official parsed section (`DocstringYields`)


# (Fork) 0.0.2 (2023-08-26)

- Google: Added capability to parse the yields section
Expand Down
22 changes: 20 additions & 2 deletions docstring_parser/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def __init__(


class DocstringReturns(DocstringMeta):
"""DocstringMeta symbolizing :returns or :yields metadata."""
"""DocstringMeta symbolizing :returns metadata."""

def __init__(
self,
Expand All @@ -101,6 +101,24 @@ def __init__(
self.return_name = return_name


class DocstringYields(DocstringMeta):
"""DocstringMeta symbolizing :yields metadata."""

def __init__(
self,
args: T.List[str],
description: T.Optional[str],
type_name: T.Optional[str],
is_generator: bool,
yield_name: T.Optional[str] = None,
) -> None:
"""Initialize self."""
super().__init__(args, description)
self.type_name = type_name
self.is_generator = is_generator
self.yield_name = yield_name


class DocstringRaises(DocstringMeta):
"""DocstringMeta symbolizing :raises metadata."""

Expand Down Expand Up @@ -199,7 +217,7 @@ def yields(self):
Takes the first generator information.
"""
for item in self.meta:
if isinstance(item, DocstringReturns) and item.is_generator:
if isinstance(item, DocstringYields) and item.is_generator:
return item
return None

Expand Down
23 changes: 19 additions & 4 deletions docstring_parser/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
DocstringRaises,
DocstringReturns,
DocstringStyle,
DocstringYields,
ParseError,
RenderingStyle,
)
Expand Down Expand Up @@ -124,12 +125,19 @@ def _build_meta(self, text: str, title: str) -> DocstringMeta:

@staticmethod
def _build_single_meta(section: Section, desc: str) -> DocstringMeta:
if section.key in RETURNS_KEYWORDS | YIELDS_KEYWORDS:
if section.key in RETURNS_KEYWORDS:
return DocstringReturns(
args=[section.key],
description=desc,
type_name=None,
is_generator=section.key in YIELDS_KEYWORDS,
is_generator=False,
)
if section.key in YIELDS_KEYWORDS:
return DocstringYields(
args=[section.key],
description=desc,
type_name=None,
is_generator=True,
)
if section.key in RAISES_KEYWORDS:
return DocstringRaises(
Expand Down Expand Up @@ -174,12 +182,19 @@ def _build_multi_meta(
is_optional=is_optional,
default=default,
)
if section.key in RETURNS_KEYWORDS | YIELDS_KEYWORDS:
if section.key in RETURNS_KEYWORDS:
return DocstringReturns(
args=[section.key, before],
description=desc,
type_name=before,
is_generator=section.key in YIELDS_KEYWORDS,
is_generator=False,
)
if section.key in YIELDS_KEYWORDS:
return DocstringYields(
args=[section.key, before],
description=desc,
type_name=before,
is_generator=True,
)
if section.key in RAISES_KEYWORDS:
return DocstringRaises(
Expand Down
18 changes: 18 additions & 0 deletions docstring_parser/numpydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
DocstringRaises,
DocstringReturns,
DocstringStyle,
DocstringYields,
RenderingStyle,
)

Expand Down Expand Up @@ -210,6 +211,23 @@ class YieldsSection(ReturnsSection):

is_generator = True

def _parse_item(self, key: str, value: str) -> DocstringYields:
match = RETURN_KEY_REGEX.match(key)
if match is not None:
yield_name = match.group("name")
type_name = match.group("type")
else:
yield_name = None
type_name = None

return DocstringYields(
args=[self.key],
description=_clean_str(value),
type_name=type_name,
is_generator=self.is_generator,
yield_name=yield_name,
)


class DeprecationSection(_SphinxSection):
"""Parser for numpydoc "deprecation warning" sections."""
Expand Down
27 changes: 25 additions & 2 deletions docstring_parser/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
DocstringRaises,
DocstringReturns,
DocstringStyle,
DocstringYields,
ParseError,
RenderingStyle,
)
Expand Down Expand Up @@ -54,7 +55,7 @@ def _build_meta(args: T.List[str], desc: str) -> DocstringMeta:
default=default,
)

if key in RETURNS_KEYWORDS | YIELDS_KEYWORDS:
if key in RETURNS_KEYWORDS:
if len(args) == 2:
type_name = args[1]
elif len(args) == 1:
Expand All @@ -68,7 +69,24 @@ def _build_meta(args: T.List[str], desc: str) -> DocstringMeta:
args=args,
description=desc,
type_name=type_name,
is_generator=key in YIELDS_KEYWORDS,
is_generator=False,
)

if key in YIELDS_KEYWORDS:
if len(args) == 2:
type_name = args[1]
elif len(args) == 1:
type_name = None
else:
raise ParseError(
f"Expected one or no arguments for a {key} keyword."
)

return DocstringYields(
args=args,
description=desc,
type_name=type_name,
is_generator=True,
)

if key in DEPRECATION_KEYWORDS:
Expand Down Expand Up @@ -127,6 +145,7 @@ def parse(text: str) -> Docstring:

types = {}
rtypes = {}
ytypes = {}
for match in re.finditer(
r"(^:.*?)(?=^:|\Z)", meta_chunk, flags=re.S | re.M
):
Expand All @@ -151,6 +170,8 @@ def parse(text: str) -> Docstring:
types[args[1]] = desc
elif len(args) in [1, 2] and args[0] == "rtype":
rtypes[None if len(args) == 1 else args[1]] = desc
elif len(args) in [1, 2] and args[0] == "ytype":
ytypes[None if len(args) == 1 else args[1]] = desc
else:
ret.meta.append(_build_meta(args, desc))

Expand All @@ -159,6 +180,8 @@ def parse(text: str) -> Docstring:
meta.type_name = meta.type_name or types.get(meta.arg_name)
elif isinstance(meta, DocstringReturns):
meta.type_name = meta.type_name or rtypes.get(meta.return_name)
elif isinstance(meta, DocstringYields):
meta.type_name = meta.type_name or ytypes.get(meta.yield_name)

if not any(isinstance(m, DocstringReturns) for m in ret.meta) and rtypes:
for (return_name, type_name) in rtypes.items():
Expand Down
12 changes: 4 additions & 8 deletions docstring_parser/tests/test_google.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,12 +585,7 @@ def test_returns() -> None:
int: description
"""
)
assert docstring.returns is not None
assert docstring.returns.type_name == "int"
assert docstring.returns.description == "description"
assert docstring.many_returns is not None
assert len(docstring.many_returns) == 1
assert docstring.many_returns[0] == docstring.returns
assert docstring.returns is None

docstring = parse(
"""
Expand Down Expand Up @@ -691,7 +686,7 @@ def test_yields() -> None:
assert docstring.yields.description == (
"description\nwith much text\n\neven some spacing"
)
assert docstring.returns.is_generator is True
assert docstring.yields.is_generator is True

docstring = parse(
"""
Expand All @@ -716,7 +711,8 @@ def test_yields() -> None:
)
assert docstring.returns is not None
assert docstring.yields is not None
assert docstring.yields.type_name == docstring.returns.type_name
assert docstring.yields.type_name == 'Optional[Mapping[str, List[int]]]'
assert docstring.returns.type_name == 'int'


def test_raises() -> None:
Expand Down
2 changes: 1 addition & 1 deletion docstring_parser/tests/test_numpydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ def test_yields() -> None:
assert docstring.meta[0].args == ["yields"]
assert docstring.meta[0].type_name == "int"
assert docstring.meta[0].description == "description"
assert docstring.meta[0].return_name is None
assert docstring.meta[0].yield_name is None
assert docstring.meta[0].is_generator


Expand Down
24 changes: 12 additions & 12 deletions docstring_parser/tests/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,27 +381,27 @@ def test_yields() -> None:
:yields: description
"""
)
assert docstring.returns is not None
assert docstring.returns.type_name is None
assert docstring.returns.description == "description"
assert docstring.returns.is_generator
assert docstring.returns is None
assert docstring.yields is not None
assert docstring.yields.type_name is None
assert docstring.yields.description == "description"
assert docstring.yields.is_generator
assert docstring.many_returns is not None
assert len(docstring.many_returns) == 1
assert docstring.many_returns[0] == docstring.returns
assert len(docstring.many_returns) == 0

docstring = parse(
"""
Short description
:yields int: description
"""
)
assert docstring.returns is not None
assert docstring.returns.type_name == "int"
assert docstring.returns.description == "description"
assert docstring.returns.is_generator
assert docstring.returns is None
assert docstring.yields is not None
assert docstring.yields.type_name == "int"
assert docstring.yields.description == "description"
assert docstring.yields.is_generator
assert docstring.many_returns is not None
assert len(docstring.many_returns) == 1
assert docstring.many_returns[0] == docstring.returns
assert len(docstring.many_returns) == 0


def test_raises() -> None:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "docstring_parser_fork"
version = "0.0.2"
version = "0.0.3"
description = "Parse Python docstrings in reST, Google and Numpydoc format"
authors = ["Marcin Kurczewski <[email protected]>"]
license = "MIT"
Expand Down