Skip to content

Commit dbcf985

Browse files
mshafer-NIJoshua Cannonthejcannonrtzoeller
committed
Recommend Google Style for DocStrings (#88)
* Recommend sphinx as the docstring format * Update docs/Coding-Conventions.md Co-authored-by: mshafer-NI <[email protected]> * Change the convention to be Google style * Update docs/Coding-Conventions.md Co-authored-by: Ryan Zoeller <[email protected]> * Allow any convention style * add org-check to PR notify * touch conventions file to prompt PR checks * touch again * set docstring convention to Google * add some debug output * document D412 * rname vars and re-work to bettter show what the error is * switch to "all" and suprress the codes that we see that don't fit the "suggest Google" * format * revert most of the docstring changes * remove blank lines before closing quotes * fix branch name in notify action * add note about using sphinx.ext.napolean to handle docs * Revert "revert most of the docstring changes" This reverts commit c09c931. * more docstrings to active form * add D406 to excluded rules * s/sell/sells/g * add all the codes pydocstyle would ignore if we were explicitly stating Google style * don't need that twice Co-authored-by: Joshua Cannon <[email protected]> Co-authored-by: Joshua Cannon <[email protected]> Co-authored-by: Ryan Zoeller <[email protected]>
1 parent f900ef2 commit dbcf985

File tree

21 files changed

+165
-93
lines changed

21 files changed

+165
-93
lines changed

docs/Coding-Conventions.md

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,12 @@ def go_shopping():
982982
983983
ℹ️ You can document a package by documenting the module docstring of the package directory's `__init__.py`
984984

985+
### Which docstring format should I follow?
986+
987+
We recommend (and internally use) the [Google docstring format](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods) but you can choose any format so long as you are consistent.
988+
989+
**Note**: Through the use of the [Sphinx napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#getting-started) extension, Sphinx docs generation can interpret [Google style docstrings](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods).
990+
985991
### [D.1.2] ✔️ **DO** List exported modules and subpackages in a package's docstring
986992

987993
> 🐍 This rule stems from [PEP 257](https://www.python.org/dev/peps/pep-0257/#multi-line-docstrings)
@@ -1070,7 +1076,7 @@ The summary line should be on the same line as the opening quotes.
10701076
```python
10711077
# Bad - will produce D205
10721078
def sell(type_):
1073-
"""Sell the specified type of cheese.
1079+
"""Sells the specified type of cheese.
10741080
Will throw an OutOfStockException if the specified type of cheese is out of stock.
10751081
"""
10761082
```
@@ -1079,7 +1085,7 @@ def sell(type_):
10791085
# Bad - will produce D212
10801086
def sell(type_):
10811087
"""
1082-
Sell the specified type of cheese.
1088+
Sells the specified type of cheese.
10831089
10841090
Will throw an OutOfStockException if the specified type of cheese is out of stock.
10851091
"""
@@ -1088,7 +1094,7 @@ def sell(type_):
10881094
```python
10891095
# Good
10901096
def sell(type_):
1091-
"""Sell the specified type of cheese.
1097+
"""Sells the specified type of cheese.
10921098
10931099
Will throw an OutOfStockException if the specified type of cheese is out of stock.
10941100
"""
@@ -1100,7 +1106,7 @@ class CheeseShop(object):
11001106
"""Finest cheese shop in the district, offering a wide variety of cheeses."""
11011107

11021108
def sell(self, type_):
1103-
"""Sell the specified type of cheese."""
1109+
"""Sells the specified type of cheese."""
11041110
```
11051111

11061112
### [D.1.11] ✔️ **DO** Put closing `"""` on its own line for multiline docstrings 💻
@@ -1117,7 +1123,7 @@ class CheeseShop(object):
11171123
Cheeses are sold first-come-first-served, and can run out of stock rather quickly."""
11181124

11191125
def sell(self, type_):
1120-
"""Sell the specified type of cheese.
1126+
"""Sells the specified type of cheese.
11211127
11221128
Will throw an OutOfStockException if the specified type of cheese is out of stock."""
11231129
```
@@ -1131,7 +1137,7 @@ class CheeseShop(object):
11311137
"""
11321138

11331139
def sell(self, type_):
1134-
"""Sell the specified type of cheese.
1140+
"""Sells the specified type of cheese.
11351141
11361142
Will throw an OutOfStockException if the specified type of cheese is out of stock.
11371143
"""
@@ -1157,7 +1163,7 @@ class CheeseShop(object):
11571163
class CheeseShop(object):
11581164
def sell(self, type_):
11591165

1160-
"""Sell the specified type of cheese."""
1166+
"""Sells the specified type of cheese."""
11611167
```
11621168

11631169
```python
@@ -1166,7 +1172,7 @@ class CheeseShop(object):
11661172
"""Finest cheese shop in the district, offering a wide variety of cheeses."""
11671173

11681174
def sell(self, type_):
1169-
"""Sell the specified type of cheese."""
1175+
"""Sells the specified type of cheese."""
11701176
```
11711177

11721178
### [D.1.13]**DO NOT** Put a blank line after a one line function docstring 💻
@@ -1178,18 +1184,55 @@ class CheeseShop(object):
11781184
```python
11791185
# Bad
11801186
def sell(self, type_):
1181-
"""Sell the specified type of cheese."""
1187+
"""Sells the specified type of cheese."""
11821188

11831189
self._do_transaction(type_)
11841190
```
11851191

11861192
```python
11871193
# Good
11881194
def sell(self, type_):
1189-
"""Sell the specified type of cheese."""
1195+
"""Sells the specified type of cheese."""
11901196
self._do_transaction(type_)
11911197
```
11921198

1199+
1200+
### [D.1.14]**DO NOT** Put a blank line after section headers 💻
1201+
1202+
> 💻 This rule is enforced by error code D412
1203+
1204+
```python
1205+
# Bad - will produce D412
1206+
class CheeseShop(object):
1207+
def sell(self, type_):
1208+
"""Sells the specified type of cheese.
1209+
1210+
Args:
1211+
1212+
type_: the desired type
1213+
"""
1214+
self._do_transaction(type_)
1215+
```
1216+
1217+
```python
1218+
# Good
1219+
class CheeseShop(object):
1220+
def sell(self, type_: str):
1221+
"""Sells the specified type of cheese.
1222+
1223+
Args:
1224+
type_: the desired cheese type
1225+
"""
1226+
self._do_transaction(type_)
1227+
```
1228+
1229+
```python
1230+
# Best
1231+
class CheeseShop(object):
1232+
def sell(self, type_: str):
1233+
"""Sells the specified type of cheese."""
1234+
self._do_transaction(type_)
1235+
```
11931236
---
11941237

11951238
# [C] Comments

ni_python_styleguide/_acknowledge_existing_errors/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def _filter_suppresion_from_line(line: str):
3535
def acknowledge_lint_errors(
3636
exclude, app_import_names, extend_ignore, file_or_dir, *_, aggressive=False
3737
):
38-
"""Add a "noqa" comment for each of existing errors (unless excluded).
38+
"""Adds a "noqa" comment for each of existing errors (unless excluded).
3939
4040
Excluded error (reason):
4141
BLK100 - run black
@@ -94,7 +94,7 @@ def acknowledge_lint_errors(
9494

9595

9696
def remove_auto_suppressions_from_file(file: pathlib.Path):
97-
"""Remove auto-suppressions from file."""
97+
"""Removes auto-suppressions from file."""
9898
lines = file.read_text(encoding=_utils.DEFAULT_ENCODING).splitlines()
9999
stripped_lines = [_filter_suppresion_from_line(line) for line in lines]
100100
file.write_text("\n".join(stripped_lines) + "\n", encoding=_utils.DEFAULT_ENCODING)

ni_python_styleguide/_cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def _read_pyproject_toml(ctx, param, value):
3838

3939

4040
def _get_application_import_names(pyproject):
41-
"""Return the application package name the config."""
41+
"""Returns the application package name the config."""
4242
# Otherwise override with what was specified
4343
app_name = (
4444
pyproject.get("tool", {})
@@ -57,7 +57,7 @@ class ConfigGroup(click.Group):
5757
"""click.Group subclass which allows for a config option to load options from."""
5858

5959
def __init__(self, *args, **kwargs):
60-
"""Construct the click.Group with the config option."""
60+
"""Constructs the click.Group with the config option."""
6161
kwargs["params"].append(
6262
click.Option(
6363
["--config"],

ni_python_styleguide/_fix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
def _split_imports_line(lines: str, *_, **__):
18-
r"""Split multi-import lines to multiple lines.
18+
r"""Splits multi-import lines to multiple lines.
1919
2020
>>> _split_imports_line("import os, collections\n")
2121
'import os\nimport collections\n'

ni_python_styleguide/_format.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
def format(file_or_dir, *additional_formatter_args):
11-
"""Format the specified file or directory using the builtin config."""
11+
"""Formats the specified file or directory using the builtin config."""
1212
try:
1313
black.main(
1414
[
@@ -25,7 +25,7 @@ def format(file_or_dir, *additional_formatter_args):
2525

2626

2727
def format_check(file_or_dir):
28-
"""Perform format and return True if changes were made (False otherwise)."""
28+
"""Formats and returns True if changes were made (False otherwise)."""
2929
capture = StringIO()
3030
with contextlib.redirect_stderr(capture):
3131
format(file_or_dir)

ni_python_styleguide/_utils/code_analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
def find_import_region(file: pathlib.Path) -> Tuple[int, int]:
7-
"""Return the index of the first last import line that precedes any other code.
7+
"""Returns the index of the first last import line that precedes any other code.
88
99
Note: will not handle try/except imports
1010

ni_python_styleguide/_utils/lint.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def get_errors_to_process(exclude, app_import_names, extend_ignore, file_or_dir,
2626

2727
def parse(line):
2828
r"""
29-
Parse line into :class:`LintError`.
29+
Parses line into :class:`LintError`.
3030
3131
>>> parse(r'source\arfile.py:55:16: BLK100 Black would make changes.')
3232
LintError(file='source\\arfile.py', line=55, column=16, code='BLK100', explanation='Black would make changes.')
@@ -63,7 +63,7 @@ def _to_lint_error(file: str, line: str, column: str, code: str, explanation: st
6363
)
6464

6565
def parse(self, line):
66-
"""Parse `line` and return a :class:`LintError`.
66+
"""Parses `line` and return a :class:`LintError`.
6767
6868
:param line: the line to parse
6969
:return: lint error as metada object

ni_python_styleguide/_utils/string_helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ def __init__(self, error_file: Optional[str] = None, *_, lines: Optional[List[st
2323

2424
@property
2525
def values(self):
26-
"""Return the values for the file."""
26+
"""The values for the file."""
2727
return self._values
2828

2929
def in_multiline_string(self, lineno):
30-
"""Check if lineno is in a multiline string."""
30+
"""Checks if lineno is in a multiline string."""
3131
return self._values[lineno - 1] # 0 indexed, but we number files 1 indexed
3232

3333
@staticmethod

ni_python_styleguide/config.ini

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ ignore =
6565
F812
6666

6767
# flake8-docstrings
68+
# Ignoring the requirement on imperative mood, as not all conventions agree on this
69+
# and ultimately is up to the developer. If, in the future, the mood was enforced based on
70+
# convention choice we can consider removing this supression as that would make documentation
71+
# consistent within a standard (but maybe not a codebase)
72+
# Developers are welcome to turn back on this error in their specific projects.
73+
# The same goes for the "descriptive"-mood error, if one was to be added to pydocstyle.
74+
D401
75+
# Conflicts with recommending Google style DocStrings
76+
D406 # Section name should end with a newline -> forces a newline at end of DocString
6877
# Ignoring things enforced by black
6978
D200 # One-line docstring should fit on one line with quotes
7079
D206 # Docstring should be indented with spaces, not tabs
@@ -76,7 +85,19 @@ ignore =
7685
D203 # 1 blank line required before class docstring
7786
D213 # Multi-line docstring summary should start at the second line
7887
D400 # First line should end with a period
79-
88+
# Recommending Google, so these don't apply - http://www.pydocstyle.org/en/stable/error_codes.html#:~:text=The%20google%20convention%20added%20in%20v4.0.0
89+
D203
90+
D204
91+
D213
92+
D215
93+
D400
94+
D401
95+
D404
96+
D406
97+
D407
98+
D408
99+
D409
100+
D413
80101
# flake8-import-order
81102
I101 # The names in your from import are in the wrong order. (Enforced by E401)
82103

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010

1111
def pytest_collection_modifyitems(items):
12-
"""Ignore deprecation warnings in all tests."""
12+
"""Ignores deprecation warnings in all tests."""
1313
for item in items:
1414
item.add_marker(pytest.mark.filterwarnings("ignore::DeprecationWarning"))
1515

1616

1717
@pytest.fixture
1818
def styleguide(monkeypatch, cli_runner):
19-
"""Fixture which runs the styleguide when executed.
19+
"""Runs the styleguide when executed.
2020
2121
Args passed to the function are equivalent to CLI args,
2222
and are automatically stringified.
@@ -55,7 +55,7 @@ def runner(*, base_args=[], command="", command_args=[]):
5555

5656
@pytest.fixture
5757
def chdir():
58-
"""Fixture which changes the current working directory when executed."""
58+
"""Changes the current working directory when executed."""
5959
cwd = os.getcwd()
6060
yield os.chdir
6161
os.chdir(cwd)

0 commit comments

Comments
 (0)