Skip to content

Commit 4dde2b6

Browse files
committed
cookiecutter: Add a pytest hook to lint docstring examples
Examples found in code *docstrings* in the `src/` directory will now be collected and checked using `pylint`. This is done via the file `src/conftest.py`, which hooks into `pytest`. We also need to update the regex to use the current development version of the package in the tests because now we also use the `extra-lint-examples` optional dependency. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 9b76f73 commit 4dde2b6

File tree

20 files changed

+115
-21
lines changed

20 files changed

+115
-21
lines changed

RELEASE_NOTES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@
5858

5959
- Add an `.editorconfig` file to ensure a common basic editor configuration for different file types.
6060

61+
- Add a `pytest` hook to collect and lint code examples found in *docstrings* using `pylint`.
62+
63+
Examples found in code *docstrings* in the `src/` directory will now be collected and checked using `pylint`. This is done via the file `src/conftest.py`, which hooks into `pytest`, so to only check the examples you can run `pylint src`.
64+
65+
!!! info
66+
67+
There is a bug in the library used to extract the examples that prevents from collecting examples from `__init__.py` files. See https://github.com/frequenz-floss/frequenz-repo-config-python/issues/113 for more details.
68+
6169
## Bug Fixes
6270

6371
- The distribution package doesn't include tests and other useless files anymore.

cookiecutter/{{cookiecutter.github_repo_name}}/MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ exclude CODEOWNERS
99
exclude CONTRIBUTING.md
1010
exclude mkdocs.yml
1111
exclude noxfile.py
12+
exclude src/conftest.py
1213
recursive-exclude .github *
1314
recursive-exclude docs *
1415
{%- if cookiecutter.type == "api" %}

cookiecutter/{{cookiecutter.github_repo_name}}/pyproject.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,15 @@ dev-pylint = [
8686
# For checking the noxfile, docs/ script, and tests
8787
"{{cookiecutter.pypi_package_name}}[dev-mkdocs,dev-noxfile,dev-pytest]",
8888
]
89-
{%- if cookiecutter.type == "api" %}
90-
dev-pytest = ["pytest == 7.3.1"]
91-
{%- else %}
9289
dev-pytest = [
9390
"pytest == 7.3.1",
91+
"frequenz-repo-config[extra-lint-examples] == 0.5.0",
92+
{%- if cookiecutter.type != "api" %}
9493
"pytest-mock == 3.10.0",
9594
"pytest-asyncio == 0.21.0",
9695
"async-solipsism == 0.5",
97-
]
9896
{%- endif %}
97+
]
9998
dev = [
10099
"{{cookiecutter.pypi_package_name}}[dev-mkdocs,dev-docstrings,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
101100
]
@@ -138,7 +137,7 @@ disable = [
138137

139138
[tool.pytest.ini_options]
140139
{%- if cookiecutter.type != "api" %}
141-
testpaths = ["tests"]
140+
testpaths = ["tests", "src"]
142141
asyncio_mode = "auto"
143142
required_plugins = ["pytest-asyncio", "pytest-mock"]
144143
{%- else %}
@@ -147,7 +146,7 @@ testpaths = ["pytests"]
147146
{%- if cookiecutter.type != "api" %}
148147

149148
[[tool.mypy.overrides]]
150-
module = ["async_solipsism", "async_solipsism.*"]
149+
module = ["async_solipsism", "async_solipsism.*", "sybil", "sybil.*"]
151150
ignore_missing_imports = true
152151
{%- endif %}
153152
{%- if cookiecutter.type == "api" %}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# License: {{cookiecutter.license}}
2+
# Copyright © {% now 'utc', '%Y' %} {{cookiecutter.author_name}}
3+
4+
"""Validate docstring code examples.
5+
6+
Code examples are often wrapped in triple backticks (```) within docstrings.
7+
This plugin extracts these code examples and validates them using pylint.
8+
"""
9+
10+
from frequenz.repo.config.pytest import examples
11+
from sybil import Sybil
12+
13+
pytest_collect_file = Sybil(**examples.get_sybil_arguments()).pytest()

tests/integration/test_cookiecutter_generation.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,8 @@ def _update_pyproject_repo_config_dep(
283283
repo_type: Type of the repo to generate.
284284
repo_path: Path to the generated repo.
285285
"""
286-
repo_config_dep = (
287-
f"frequenz-repo-config[{repo_type.value}] @ file://{repo_config_path}"
288-
)
289-
repo_config_dep_re = re.compile(
290-
rf"""frequenz-repo-config\[{repo_type.value}\][^"]+"""
291-
)
286+
repo_config_dep = rf"frequenz-repo-config[\1] @ file://{repo_config_path}"
287+
repo_config_dep_re = re.compile(r"""frequenz-repo-config\[([^]]+)\][^"]+""")
292288

293289
with open(repo_path / "pyproject.toml", encoding="utf8") as pyproject_file:
294290
pyproject_content = pyproject_file.read()

tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ exclude CODEOWNERS
66
exclude CONTRIBUTING.md
77
exclude mkdocs.yml
88
exclude noxfile.py
9+
exclude src/conftest.py
910
recursive-exclude .github *
1011
recursive-exclude docs *
1112
recursive-exclude tests *

tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ dev-pylint = [
7070
]
7171
dev-pytest = [
7272
"pytest == 7.3.1",
73+
"frequenz-repo-config[extra-lint-examples] == 0.5.0",
7374
"pytest-mock == 3.10.0",
7475
"pytest-asyncio == 0.21.0",
7576
"async-solipsism == 0.5",
@@ -112,12 +113,12 @@ disable = [
112113
]
113114

114115
[tool.pytest.ini_options]
115-
testpaths = ["tests"]
116+
testpaths = ["tests", "src"]
116117
asyncio_mode = "auto"
117118
required_plugins = ["pytest-asyncio", "pytest-mock"]
118119

119120
[[tool.mypy.overrides]]
120-
module = ["async_solipsism", "async_solipsism.*"]
121+
module = ["async_solipsism", "async_solipsism.*", "sybil", "sybil.*"]
121122
ignore_missing_imports = true
122123

123124
[tool.setuptools_scm]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Validate docstring code examples.
5+
6+
Code examples are often wrapped in triple backticks (```) within docstrings.
7+
This plugin extracts these code examples and validates them using pylint.
8+
"""
9+
10+
from frequenz.repo.config.pytest import examples
11+
from sybil import Sybil
12+
13+
pytest_collect_file = Sybil(**examples.get_sybil_arguments()).pytest()

tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ exclude CODEOWNERS
77
exclude CONTRIBUTING.md
88
exclude mkdocs.yml
99
exclude noxfile.py
10+
exclude src/conftest.py
1011
recursive-exclude .github *
1112
recursive-exclude docs *
1213
recursive-exclude pytests *
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Validate docstring code examples.
5+
6+
Code examples are often wrapped in triple backticks (```) within docstrings.
7+
This plugin extracts these code examples and validates them using pylint.
8+
"""
9+
10+
from frequenz.repo.config.pytest import examples
11+
from sybil import Sybil
12+
13+
pytest_collect_file = Sybil(**examples.get_sybil_arguments()).pytest()

0 commit comments

Comments
 (0)