Skip to content

Commit

Permalink
Add support for Sphinx docstring style (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsh9 authored Jul 16, 2023
1 parent 872d24f commit 274e457
Show file tree
Hide file tree
Showing 26 changed files with 2,607 additions and 20 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change Log

## [0.1.0] - 2023-07-15

- Added
- Added support for the
[Sphinx docstring style](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html)
- Full diff
- https://github.com/jsh9/pydoclint/compare/0.0.16...0.1.0

## [0.0.16] - 2023-07-14

- Fixed
Expand Down
4 changes: 3 additions & 1 deletion pydoclint/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ def validateStyleValue(
) -> Optional[str]:
"""Validate the value of the 'style' option"""
if value not in {'numpy', 'google'}:
raise click.BadParameter('"--style" must be "numpy" or "google"')
raise click.BadParameter(
'"--style" must be "numpy", "google", or "sphinx"'
)

return value

Expand Down
13 changes: 8 additions & 5 deletions pydoclint/utils/doc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Any, List

import docstring_parser.parser as sphinx_parser
from docstring_parser.common import Docstring, DocstringReturns
from docstring_parser.google import GoogleParser
from numpydoc.docscrape import NumpyDocString
Expand Down Expand Up @@ -27,6 +28,8 @@ def __init__(self, docstring: str, style: str = 'numpy') -> None:
elif style == 'google':
parser = GoogleParser()
self.parsed = parser.parse(docstring)
elif style == 'sphinx':
self.parsed = sphinx_parser.parse(docstring)
else:
self._raiseException()

Expand Down Expand Up @@ -56,7 +59,7 @@ def isShortDocstring(self) -> bool:
and not bool(self.parsed.get('index'))
)

if self.style == 'google':
if self.style in {'google', 'sphinx'}:
# API documentation:
# https://rr-.github.io/docstring_parser/docstring_parser.Docstring.html
return (
Expand All @@ -80,7 +83,7 @@ def argList(self) -> ArgList:
if self.style == 'numpy':
return ArgList.fromNumpydocParam(self.parsed.get('Parameters', []))

if self.style == 'google':
if self.style in {'google', 'sphinx'}:
return ArgList.fromGoogleParsedParam(self.parsed.params)

self._raiseException() # noqa: R503
Expand All @@ -91,7 +94,7 @@ def hasReturnsSection(self) -> bool:
if self.style == 'numpy':
return bool(self.parsed.get('Returns'))

if self.style == 'google':
if self.style in {'google', 'sphinx'}:
retSection: DocstringReturns = self.parsed.returns
return retSection is not None and not retSection.is_generator

Expand All @@ -103,7 +106,7 @@ def hasYieldsSection(self) -> bool:
if self.style == 'numpy':
return bool(self.parsed.get('Yields'))

if self.style == 'google':
if self.style in {'google', 'sphinx'}:
retSection: DocstringReturns = self.parsed.returns
return retSection is not None and retSection.is_generator

Expand All @@ -115,7 +118,7 @@ def hasRaisesSection(self) -> bool:
if self.style == 'numpy':
return bool(self.parsed.get('Raises'))

if self.style == 'google':
if self.style in {'google', 'sphinx'}:
return len(self.parsed.raises) > 0

self._raiseException() # noqa: R503
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pydoclint
version = 0.0.16
version = 0.1.0
description = A Python docstring linter that checks arguments, returns, yields, and raises sections
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
51 changes: 51 additions & 0 deletions tests/data/sphinx/abstract_method/cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from abc import ABC, abstractmethod
from collections.abc import Generator, Iterator


class AbstractClass(ABC):
"""Example abstract class."""

@abstractmethod
def abstract_method(self, var1: str) -> Generator[str, None, None]:
"""Abstract method.
No violations in this method.
:param var1: Variable.
:type var1: str
:raises: ValueError: Example exception
:yield: Paths to the files and directories listed.
:rtype: str
"""

@abstractmethod
def another_abstract_method(self, var1: str) -> Iterator[str]:
"""Another abstract method.
The linter will complain about not having a return section, because
if the return type annotation is `Iterator`, it is supposed to be
returning something, rather than yielding something. (To yield
something, use `Generator` as the return type annotation.)
:param var1: Variable.
:type var1: str
:raises: ValueError: Example exception
:yield: Paths to the files and directories listed.
:rtype: str
"""

@abstractmethod
def third_abstract_method(self, var1: str) -> str:
"""The 3rd abstract method.
The linter will complain about not having a return section.
:param var1: Variable.
:type var1: str
:raises: ValueError: Example exception
"""
116 changes: 116 additions & 0 deletions tests/data/sphinx/allow_init_docstring/cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
class A:
"""
A class that does something
:param arg1: Arg 1
:type arg1: int
:param arg2: Arg 2
:type arg2: float
"""

def __init__(self, arg1: int, arg2: float) -> None:
"""Initialize the class"""
self.arg1 = arg1
self.arg2 = arg2


class B:
"""
A class that does something
:param arg1: Arg 1
:type arg1: int
:param arg2: Arg 2
:type arg2: float
:return: None
:rtype: None
"""

def __init__(self, arg1: int, arg2: float) -> None:
"""
Do something.
:param arg1: Arg 1
:type arg1: int
:param arg2: Arg 2
:type arg2: float
:return: None
:rtype: None
"""
self.arg1 = arg1
self.arg2 = arg2


class C:
"""
A class that does something
:raises TypeError: Type error
"""

def __init__(self, arg1: int, arg2: float) -> None:
"""
Do something.
:param arg1: Arg 1
:type arg1: int
:param arg2: Arg 2
:type arg2: float
:raises TypeError: Type error
"""
self.arg1 = arg1
self.arg2 = arg2

if arg1 + arg2 == 0:
raise ValueError('Something wrong')


class D:
"""
A class that does something
:yield: Thing to yield
:rtype: int
"""

def __init__(self, arg1: int, arg2: float) -> None:
"""
Do something.
:param arg1: Arg 1
:type arg1: int
:param arg2: Arg 2
:type arg2: float
:yield: Thing to yield
:rtype: int
"""
self.arg1 = arg1
self.arg2 = arg2


class E:
"""
A class that does something
:attr attr1:
:attr attr1: Arg 2
"""

def __init__(self, arg1: int, arg2: float) -> None:
"""
Do something.
:param arg1: Arg 1
:type arg1: int
:param arg2: Arg 2
:type arg2: float
:raises: ValueError: When some values are invalid
"""
self.arg1 = arg1
self.arg2 = arg2

if arg1 + arg2 == 0:
raise ValueError('Something wrong')
Loading

0 comments on commit 274e457

Please sign in to comment.