Skip to content

Commit

Permalink
fix: Support documenting multiple items for optional tuples
Browse files Browse the repository at this point in the history
Issue #117: #117
  • Loading branch information
pawamoy committed Nov 30, 2022
1 parent 37ae0a2 commit 727456d
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 5 deletions.
41 changes: 36 additions & 5 deletions src/griffe/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

from __future__ import annotations

import sys
from typing import Any, Callable

from griffe.exceptions import NameResolutionError

# TODO: remove once Python 3.7 support is dropped
if sys.version_info < (3, 8):
from cached_property import cached_property
else:
from functools import cached_property # noqa: WPS440


class Name:
"""This class represents a Python object identified by a name in a given scope.
Expand Down Expand Up @@ -129,7 +136,7 @@ def kind(self) -> str:
Returns:
The main type of this expression.
"""
return str(self).split("[", 1)[0].rsplit(".", 1)[-1].lower()
return str(self.non_optional).split("[", 1)[0].rsplit(".", 1)[-1].lower()

@property
def is_tuple(self) -> bool:
Expand Down Expand Up @@ -158,6 +165,30 @@ def is_generator(self) -> bool:
"""
return self.kind == "generator"

@cached_property
def non_optional(self) -> Expression:
"""Return the same expression as non-optional.
This will return a new expression without
the `Optional[]` or `| None` parts.
Returns:
A non-optional expression.
"""
if self[-1] == "None" and self[-2] == " | ":
if isinstance(self[0], Expression):
return self[0]
return Expression(self[0])
if self[0] == "None" and self[1] == " | ":
if isinstance(self[2], Expression):
return self[2]
return Expression(self[2])
if isinstance(self[0], Name) and self[0].full == "typing.Optional":
if isinstance(self[2], Expression):
return self[2]
return Expression(self[2])
return self

def tuple_item(self, nth: int) -> str | Name:
"""Return the n-th item of this tuple expression.
Expand All @@ -171,15 +202,15 @@ def tuple_item(self, nth: int) -> str | Name:
# N|E [ E ]
# N , N , N
# 0 1 2 3 4
return self[2][2 * nth]
return self.non_optional[2][2 * nth]

def tuple_items(self) -> list[Name | Expression]:
"""Return a tuple items as a list.
Returns:
The tuple items.
"""
return self[2][::2]
return self.non_optional[2][::2]

def iterator_item(self) -> Name | Expression:
"""Return the item of an iterator.
Expand All @@ -188,7 +219,7 @@ def iterator_item(self) -> Name | Expression:
The iterator item.
"""
# Iterator[ItemType]
return self[2]
return self.non_optional[2]

def generator_items(self) -> tuple[Name | Expression, Name | Expression, Name | Expression]:
"""Return the items of a generator.
Expand All @@ -199,4 +230,4 @@ def generator_items(self) -> tuple[Name | Expression, Name | Expression, Name |
The return type.
"""
# Generator[Yield, Send/Receive, Return]
return self[2][0], self[2][2], self[2][4]
return self.non_optional[2][0], self[2][2], self[2][4]
42 changes: 42 additions & 0 deletions tests/test_expressions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Test names and expressions methods."""

from __future__ import annotations

import pytest

from griffe.docstrings.parsers import Parser
from tests.helpers import temporary_visited_module


@pytest.mark.parametrize(
("annotation", "items"),
[
("tuple[int, float] | None", 2),
("None | tuple[int, float]", 2),
("Optional[tuple[int, float]]", 2),
("typing.Optional[tuple[int, float]]", 2),
],
)
def test_explode_return_annotations(annotation, items):
"""Check that we correctly split items from return annotations.
Parameters:
annotation: The return annotation.
items: The number of items to write in the docstring returns section.
"""
newline = "\n "
returns = newline.join(f"x{_}: Some value." for _ in range(items))
code = f"""
import typing
from typing import Optional
def function() -> {annotation}:
'''This function returns either two ints or None
Returns:
{returns}
'''
"""
with temporary_visited_module(code) as module:
sections = module["function"].docstring.parse(Parser.google)
assert sections[1].value

0 comments on commit 727456d

Please sign in to comment.