Skip to content

Commit 07780ea

Browse files
authored
Move mkdocstrings-macros integration to a library (#349)
This PR moves the documentation macros functionality from individual template repositories into a central location, making it available as a `macros` *pluglet*. Key improvements include: - Removes duplicated boilerplate code - Adds a modular `macros` *pluglet* with common functionality - Simplifies template setup by using git-aware version info - Includes migration script for existing repositories - Updates release notes and documentation The PR also bumps template dependencies and repository configuration versions in preparation for the 0.12 release. Fixes #179.
2 parents 660cd95 + d01b08e commit 07780ea

File tree

30 files changed

+583
-722
lines changed

30 files changed

+583
-722
lines changed

RELEASE_NOTES.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Summary
44

5-
<!-- Here goes a general summary of what this release is about -->
5+
This release introduces a new MkDocs macros *pluglet* system that simplifies documentation setup and provides enhanced functionality for version information and code annotations. It also includes changes to how pytest warnings are handled in templates.
66

77
## Upgrading
88

@@ -15,22 +15,28 @@
1515

1616
### Cookiecutter template
1717

18-
All upgrading should be done via the migration script or regenerating the templates. But you might still need to adapt your code:
18+
All upgrading should be done via the migration script or regenerating the templates.
19+
20+
```bash
21+
curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python/v0.12/cookiecutter/migrate.py | python3
22+
```
23+
24+
But you might still need to adapt your code:
1925

2026
- `pytest` now uses `-Werror` by default (but still treat deprecations as normal warnings), so if your tests run with warnings, they will now be turned to errors, and you'll need to fix them.
2127

28+
- Projects using `docs/_scripts/macros.py` with customized scripts can use the new provided utility functions. See the [`mkdocstrings_macros` documentation](https://frequenz-floss.github.io/frequenz-repo-config-python/v0.12/reference/frequenz/repo/config/mkdocs/mkdocstrings_macros/) for the new features and setup.
29+
2230
## New Features
2331

24-
<!-- Here goes the main new features and examples or instructions on how to use them -->
32+
- Two new modules were introduced to facilitate the configuration of `macros` for use within docstrings via `mkdocstrings`: [`mkdocstrings_macros`](https://frequenz-floss.github.io/frequenz-repo-config-python/v0.12/reference/frequenz/repo/config/mkdocs/mkdocstrings_macros/) and [`annotations`](https://frequenz-floss.github.io/frequenz-repo-config-python/v0.12/reference/frequenz/repo/config/mkdocs/annotations/).
2533

2634
### Cookiecutter template
2735

2836
- `pytest` now uses `-Werror -Wdefault::DeprecationWarning -Wdefault::PendingDeprecationWarning` by default. Deprecations are still treated as warnings, as when testing with the `pytest_min` session is normal to get deprecation warnings as we are using old versions of dependencies.
2937

3038
## Bug Fixes
3139

32-
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
33-
3440
### Cookiecutter template
3541

3642
- Fixed a compatibility issue in the macros doc script with `mkdocsstrings` 0.28.

cookiecutter/migrate.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
And remember to follow any manual instructions for each run.
2121
""" # noqa: E501
2222

23+
import hashlib
2324
import os
2425
import subprocess
2526
import tempfile
@@ -30,8 +31,8 @@
3031
def main() -> None:
3132
"""Run the migration steps."""
3233
add_default_pytest_options()
33-
34-
# Add a separation line like this one after each migration step.
34+
print("=" * 72)
35+
migrate_mkdocs_macros()
3536
print("=" * 72)
3637

3738

@@ -64,6 +65,92 @@ def add_default_pytest_options() -> None:
6465
)
6566

6667

68+
def migrate_mkdocs_macros() -> None:
69+
"""Migrate from custom macros.py to standard module."""
70+
macros_file = Path("docs/_scripts/macros.py")
71+
mkdocs_yaml = Path("mkdocs.yaml")
72+
if not mkdocs_yaml.exists():
73+
mkdocs_yaml = Path("mkdocs.yml")
74+
75+
known_hashes = {
76+
"47a991286132471b6cb666577beb89e78c0f5d4975c53f0dcb319c4338a2c3cb",
77+
"6bb960c72b370ac77918f49d7a35f39c0ddb58fe52cf2d12caa2577098fd8469",
78+
"7351276ac314955a343bab09d1602e50300887291f841643e9fb79c94acc923c",
79+
"8fa5f9f3fd928e17f590e3ab056434474633259d615971404db0d2f3034adb62",
80+
"ba3ff5f1612b3dd22372a8ca95394b8ea468f18dcefc494c73811c8433fcb880",
81+
"dd32e8759abc43232bb3db5b33c0a7cf8d8442db6135c594968c499d8bae0ce5",
82+
}
83+
84+
print("Checking if docs/_scripts/macros.py can be migrated...")
85+
86+
file_hash = calculate_file_sha256_skip_lines(macros_file, 2)
87+
if not file_hash:
88+
return
89+
90+
if file_hash not in known_hashes:
91+
manual_step("The macros.py file seems to be customized. You have two options:")
92+
manual_step("")
93+
manual_step(
94+
"1. Switch to the standard module (if you don't have custom macros):"
95+
)
96+
manual_step(" a. Update mkdocs.yaml to use the standard module:")
97+
manual_step(
98+
' module_name: docs/_scripts/macros -> modules: ["frequenz.repo.config.mkdocs.mkdocstrings_macros"]' # noqa: E501
99+
)
100+
manual_step(" b. Remove docs/_scripts/macros.py")
101+
manual_step("")
102+
manual_step("2. Keep your custom macros but use the standard functionality:")
103+
manual_step(" a. Update mkdocs.yaml:")
104+
manual_step(" - Keep using module_name: docs/_scripts/macros")
105+
manual_step(" b. Update your macros.py to be minimal:")
106+
manual_step(" ```python")
107+
manual_step(
108+
" from frequenz.repo.config.mkdocs.mkdocstrings_macros import hook_env_with_everything" # noqa: E501
109+
)
110+
manual_step("")
111+
manual_step(" def define_env(env):")
112+
manual_step(" # Add your custom variables, filters, and macros here")
113+
manual_step(" env.variables.my_var = 'Example'")
114+
manual_step(" env.filters.my_filter = lambda x: x.upper()")
115+
manual_step("")
116+
manual_step(
117+
" # This must be at the end to enable all standard features"
118+
)
119+
manual_step(" hook_env_with_everything(env)")
120+
manual_step(" ```")
121+
manual_step("")
122+
manual_step("See the docs for more details:")
123+
manual_step(
124+
"https://frequenz-floss.github.io/frequenz-repo-config-python/v0.12/reference/frequenz/repo/config/mkdocs/mkdocstrings_macros/" # noqa: E501
125+
)
126+
return
127+
128+
if not mkdocs_yaml.exists():
129+
print("mkdocs.yaml/yml not found, skipping macros migration")
130+
return
131+
132+
content = mkdocs_yaml.read_text(encoding="utf-8")
133+
if "module_name: docs/_scripts/macros" not in content:
134+
print("Custom macros configuration not found in mkdocs.yaml")
135+
return
136+
137+
print("Updating mkdocs.yaml to use standard module...")
138+
new_content = content.replace(
139+
"module_name: docs/_scripts/macros",
140+
'modules: ["frequenz.repo.config.mkdocs.mkdocstrings_macros"]',
141+
)
142+
new_content = new_content.replace(
143+
"# inside docstrings. See the comment in `docs/_scripts/macros.py` for more\n"
144+
" # details\n",
145+
"# inside docstrings.\n",
146+
)
147+
148+
replace_file_contents_atomically(mkdocs_yaml, content, new_content)
149+
150+
print("Removing docs/_scripts/macros.py...")
151+
macros_file.unlink()
152+
153+
67154
def apply_patch(patch_content: str) -> None:
68155
"""Apply a patch using the patch utility."""
69156
subprocess.run(["patch", "-p1"], input=patch_content.encode(), check=True)
@@ -134,5 +221,25 @@ def manual_step(message: str) -> None:
134221
print(f"\033[0;33m>>> {message}\033[0m")
135222

136223

224+
def calculate_file_sha256_skip_lines(filepath: Path, skip_lines: int) -> str | None:
225+
"""Calculate SHA256 of file contents excluding the first N lines.
226+
227+
Args:
228+
filepath: Path to the file to hash
229+
skip_lines: Number of lines to skip at the beginning
230+
231+
Returns:
232+
The SHA256 hex digest, or None if the file doesn't exist
233+
"""
234+
if not filepath.exists():
235+
return None
236+
237+
# Read file and normalize line endings to LF
238+
content = filepath.read_text(encoding="utf-8").replace("\r\n", "\n")
239+
# Skip first N lines and ensure there's a trailing newline
240+
remaining_content = "\n".join(content.splitlines()[skip_lines:]) + "\n"
241+
return hashlib.sha256(remaining_content.encode()).hexdigest()
242+
243+
137244
if __name__ == "__main__":
138245
main()

cookiecutter/{{cookiecutter.github_repo_name}}/docs/_scripts/macros.py

Lines changed: 0 additions & 80 deletions
This file was deleted.

cookiecutter/{{cookiecutter.github_repo_name}}/mkdocs.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,9 @@ plugins:
129129
{%- endif %}
130130
- https://typing-extensions.readthedocs.io/en/stable/objects.inv
131131
# Note this plugin must be loaded after mkdocstrings to be able to use macros
132-
# inside docstrings. See the comment in `docs/_scripts/macros.py` for more
133-
# details
132+
# inside docstrings.
134133
- macros:
135-
module_name: docs/_scripts/macros
134+
modules: ["frequenz.repo.config.mkdocs.mkdocstrings_macros"]
136135
on_undefined: strict
137136
on_error_fail: true
138137
- search

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

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33

44
[build-system]
55
requires = [
6-
"setuptools == 75.5.0",
6+
"setuptools == 75.8.0",
77
"setuptools_scm[toml] == 8.1.0",
8-
"frequenz-repo-config[{{cookiecutter.type}}] == 0.11.0",
8+
"frequenz-repo-config[{{cookiecutter.type}}] == 0.12.0",
99
{%- if cookiecutter.type == "api" %}
1010
# We need to pin the protobuf, grpcio and grpcio-tools dependencies to make
1111
# sure the code is generated using the minimum supported versions, as older
1212
# versions can't work with code that was generated with newer versions.
1313
# https://protobuf.dev/support/cross-version-runtime-guarantee/#backwards
14-
"protobuf == 5.28.0",
15-
"grpcio-tools == 1.66.1",
16-
"grpcio == 1.66.1",
14+
"protobuf == 5.29.3",
15+
"grpcio-tools == 1.70.0",
16+
"grpcio == 1.70.0",
1717
{%- endif %}
1818
]
1919
build-backend = "setuptools.build_meta"
@@ -48,27 +48,27 @@ dependencies = [
4848
# Make sure to update the version for cross-referencing also in the
4949
# mkdocs.yml file when changing the version here (look for the config key
5050
# plugins.mkdocstrings.handlers.python.import)
51-
"frequenz-sdk >= 1.0.0rc1300, < 1.0.0rc1400",
51+
"frequenz-sdk >= 1.0.0rc1500, < 1.0.0rc1600",
5252
]
5353
{%- elif cookiecutter.type == "app" %}
5454
dependencies = [
5555
"typing-extensions == 4.12.2",
5656
# Make sure to update the version for cross-referencing also in the
5757
# mkdocs.yml file when changing the version here (look for the config key
5858
# plugins.mkdocstrings.handlers.python.import)
59-
"frequenz-sdk == 1.0.0rc1300",
59+
"frequenz-sdk == 1.0.0rc1500",
6060
]
6161
{%- elif cookiecutter.type == "api" %}
6262
dependencies = [
63-
"frequenz-api-common >= 0.6.2, < 0.7.0",
63+
"frequenz-api-common >= 0.6.3, < 0.7.0",
6464
# We can't widen beyond the current value unless we bump the minimum
6565
# requirements too because of protobuf cross-version runtime guarantees:
6666
# https://protobuf.dev/support/cross-version-runtime-guarantee/#major
67-
"protobuf >= 5.28.0, < 7", # Do not widen beyond 7!
67+
"protobuf >= 5.29.3, < 7", # Do not widen beyond 7!
6868
# We couldn't find any document with a spec about the cross-version runtime
6969
# guarantee for grpcio, so unless we find one in the future, we'll assume
7070
# major version jumps are not compatible
71-
"grpcio >= 1.66.1, < 2", # Do not widen beyond 2!
71+
"grpcio >= 1.70.0, < 2", # Do not widen beyond 2!
7272
]
7373
{%- else %}
7474
dependencies = [
@@ -87,48 +87,48 @@ dev-flake8 = [
8787
"flake8 == 7.1.1",
8888
"flake8-docstrings == 1.7.0",
8989
"flake8-pyproject == 1.2.3", # For reading the flake8 config from pyproject.toml
90-
"pydoclint == 0.5.9",
90+
"pydoclint == 0.6.0",
9191
"pydocstyle == 6.3.0",
9292
]
93-
dev-formatting = ["black == 24.10.0", "isort == 5.13.2"]
93+
dev-formatting = ["black == 25.1.0", "isort == 6.0.0"]
9494
dev-mkdocs = [
9595
"Markdown == 3.7",
96-
"black == 24.10.0",
96+
"black == 25.1.0",
9797
"mike == 2.1.3",
9898
"mkdocs-gen-files == 0.5.0",
9999
"mkdocs-literate-nav == 0.6.1",
100100
"mkdocs-macros-plugin == 1.3.7",
101-
"mkdocs-material == 9.5.45",
101+
"mkdocs-material == 9.6.2",
102102
"mkdocstrings[python] == 0.28.0",
103103
"mkdocstrings-python == 1.14.0",
104-
"frequenz-repo-config[{{cookiecutter.type}}] == 0.11.0",
104+
"frequenz-repo-config[{{cookiecutter.type}}] == 0.12.0",
105105
]
106106
dev-mypy = [
107107
"mypy == 1.9.0",
108108
{%- if cookiecutter.type == "api" %}
109-
"grpc-stubs == 1.53.0.2",
109+
"grpc-stubs == 1.53.0.5",
110110
{%- endif %}
111-
"types-Markdown == 3.7.0.20240822",
111+
"types-Markdown == 3.7.0.20241204",
112112
# For checking the noxfile, docs/ script, and tests
113113
"{{cookiecutter.pypi_package_name}}[dev-mkdocs,dev-noxfile,dev-pytest]",
114114
]
115115
dev-noxfile = [
116116
"nox == 2024.10.9",
117-
"frequenz-repo-config[{{cookiecutter.type}}] == 0.11.0",
117+
"frequenz-repo-config[{{cookiecutter.type}}] == 0.12.0",
118118
]
119119
dev-pylint = [
120120
# dev-pytest already defines a dependency to pylint because of the examples
121121
# For checking the noxfile, docs/ script, and tests
122122
"{{cookiecutter.pypi_package_name}}[dev-mkdocs,dev-noxfile,dev-pytest]",
123123
]
124124
dev-pytest = [
125-
"pytest == 8.3.3",
126-
"pylint == 3.3.1", # We need this to check for the examples
127-
"frequenz-repo-config[extra-lint-examples] == 0.11.0",
125+
"pytest == 8.3.4",
126+
"pylint == 3.3.4", # We need this to check for the examples
127+
"frequenz-repo-config[extra-lint-examples] == 0.12.0",
128128
{%- if cookiecutter.type != "api" %}
129129
"pytest-mock == 3.14.0",
130-
"pytest-asyncio == 0.24.0",
131-
"async-solipsism == 0.6",
130+
"pytest-asyncio == 0.25.3",
131+
"async-solipsism == 0.7",
132132
{%- endif %}
133133
]
134134
dev = [

0 commit comments

Comments
 (0)