Skip to content

Commit 3c37126

Browse files
authored
Add support for frequenz-api-common (frequenz-floss#42)
- Improve message when no proto files are found - Allow configuring via pyproject.toml - Add `frequenz-api-common` to the default submodules - Remove `sumodules/` from the submodule name - Widen the dependencies in the docs - Remove unnecessary force option to git submodules add - Remove `--recursive` from submodules update - Make integration tests more verbose - Remove unnecessary `type: ignore` Fixes frequenz-floss#40.
2 parents b39655d + 55fe39b commit 3c37126

File tree

6 files changed

+127
-39
lines changed

6 files changed

+127
-39
lines changed

cookiecutter/hooks/post_gen_project.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ def mkdir(self) -> None:
182182
"git",
183183
"submodule",
184184
"add",
185-
"-f",
186185
"--name",
187186
submodule.name,
188187
submodule.url,
@@ -194,7 +193,7 @@ def mkdir(self) -> None:
194193
note_on_failure=f"Please add submodule `{submodule.name}` manually.",
195194
)
196195
try_run(
197-
["git", "submodule", "update", "--init", "--recursive"],
196+
["git", "submodule", "update", "--init"],
198197
verbose=True,
199198
warn_on_error=True,
200199
warn_on_bad_status="Failed to initialize git submodules!",
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{% if cookiecutter.type == "api" -%}
2-
[submodule "submodules/api-common-protos"]
2+
[submodule "api-common-protos"]
33
path = submodules/api-common-protos
44
url = https://github.com/googleapis/api-common-protos.git
5+
[submodule "frequenz-api-common"]
6+
path = submodules/frequenz-api-common
7+
url = https://github.com/frequenz-floss/frequenz-api-common.git
58
{% endif -%}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies = [ # TODO(cookiecutter): Remove and add more if appropriate
3838
]
3939
{%- elif cookiecutter.type == "api" %}
4040
dependencies = [ # TODO(cookiecutter): Remove and add more if appropriate
41+
"frequenz-api-common >= 0.2.0, < 0.3.0",
4142
"googleapis-common-protos >= 1.56.2, < 2",
4243
"grpcio >= 1.51.1, < 2",
4344
]

src/frequenz/repo/config/__init__.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# License: MIT
22
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
33

4-
"""Frequenz project setup tools and common configuration.
4+
r"""Frequenz project setup tools and common configuration.
55
66
The tools are provided to configure the main types of repositories most commonly used at
77
Frequenz, defined in
@@ -166,15 +166,21 @@
166166
- `pytests/`: Directory containing the tests for the Python code.
167167
- `submodules/api-common-protos`: Directory containing the submodule with the
168168
`google/api-common-protos` repository.
169+
- `submodules/frequenz-api-common`: Directory containing the submodule with the
170+
`frequenz-floss/frequenz-api-common` repository.
169171
170172
Normally Frequenz APIs use basic types from
171-
[`google/api-common-protos`](https://github.com/googleapis/api-common-protos),
172-
so you need to make sure the proper submodule is added to your project:
173+
[`google/api-common-protos`](https://github.com/googleapis/api-common-protos) and
174+
[`frequenz-floss/frequenz-api-common`](https://github.com/frequenz-floss/frequenz-api-common),
175+
so you need to make sure the proper submodules are added to your project:
173176
174177
```sh
175178
mkdir submodules
176-
git submodule add https://github.com:googleapis/api-common-protos.git submodules/api-common-protos
177-
git commit -m "Add Google api-common-protos submodule" submodules/api-common-protos
179+
git submodule add https://github.com/googleapis/api-common-protos.git \
180+
submodules/api-common-protos
181+
git submodule add https://github.com/frequenz-floss/frequenz-api-common.git \
182+
submodules/frequenz-api-common
183+
git commit -m "Add api-common-protos and frequenz-api-common submodules" submodules
178184
```
179185
180186
Then you need to add this package as a build dependency and a few extra
@@ -190,8 +196,9 @@
190196
191197
[project]
192198
dependencies = [
193-
"googleapis-common-protos == 1.56.2",
194-
"grpcio == 1.51.0",
199+
"frequenz-api-common >= 0.2.0, < 0.3.0",
200+
"googleapis-common-protos >= 1.56.2, < 2",
201+
"grpcio >= 1.51.1, < 2",
195202
]
196203
```
197204
@@ -222,11 +229,29 @@
222229
223230
```
224231
recursive-include submodules/api-common-protos/google *.proto
232+
recursive-include submodules/frequenz-api-common/proto *.proto
225233
```
226234
227-
For now there is no way to customize where the protocol files are located,
228-
where the generated files should be placed, or which extra directories must be
229-
included when compiling the protocol files.
235+
If the defaults are not suitable for you (for example you need to use more or less
236+
submodules or your proto files are located somewhere else, you can customize how
237+
the protocol files are generated by adding the following section to your
238+
`pyproject.toml` file:
239+
240+
```toml
241+
[tool.frequenz_repo_config.setuptools.grpc_tools]
242+
# Location of the proto files relative to the root of the repository (default: "proto")
243+
proto_path = "proto_files"
244+
# Glob pattern to use to find the proto files in the proto_path (default: "*.proto")
245+
proto_glob = "*.prt" # Default: "*.proto"
246+
# List of paths to pass to the protoc compiler as include paths (default:
247+
# ["submodules/api-common-protos", "submodules/frequenz-api-common/proto"])
248+
include_paths = ["submodules/api-common-protos"]
249+
# Path where to generate the Python files (default: "py")
250+
py_path = "generated"
251+
```
252+
253+
Please adapt the instructions above to your project structure if you need to change the
254+
defaults.
230255
"""
231256

232257
from . import nox, setuptools

src/frequenz/repo/config/setuptools/grpc_tools.py

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
import pathlib as _pathlib
1414
import subprocess as _subprocess
1515
import sys as _sys
16+
import tomllib as _tomllib
1617

1718
import setuptools as _setuptools
18-
19-
# The typing stub for this module is missing
20-
import setuptools.command.build as _build_command # type: ignore[import]
19+
import setuptools.command.build as _build_command
2120

2221

2322
class CompileProto(_setuptools.Command):
@@ -58,16 +57,69 @@ class CompileProto(_setuptools.Command):
5857
]
5958
"""Options of the command."""
6059

60+
DEFAULT_OPTIONS: dict[str, str] = {
61+
"proto_path": "proto",
62+
"proto_glob": "*.proto",
63+
"include_paths": "submodules/api-common-protos,submodules/frequenz-api-common/proto",
64+
"py_path": "py",
65+
}
66+
6167
def initialize_options(self) -> None:
6268
"""Initialize options."""
63-
self.proto_path = "proto"
64-
self.proto_glob = "*.proto"
65-
self.include_paths = "submodules/api-common-protos"
66-
self.py_path = "py"
69+
options = self._get_options_from_pyproject_toml(self.DEFAULT_OPTIONS)
70+
71+
self.proto_path = options["proto_path"]
72+
self.proto_glob = options["proto_glob"]
73+
self.include_paths = options["include_paths"]
74+
self.py_path = options["py_path"]
6775

6876
def finalize_options(self) -> None:
6977
"""Finalize options."""
7078

79+
def _get_options_from_pyproject_toml(
80+
self, defaults: dict[str, str]
81+
) -> dict[str, str]:
82+
"""Get the options from the pyproject.toml file.
83+
84+
The options are read from the `[tool.frequenz-repo-config.setuptools.grpc_tools]`
85+
section of the pyproject.toml file.
86+
87+
Args:
88+
defaults: The default values for the options.
89+
90+
Returns:
91+
The options read from the pyproject.toml file.
92+
"""
93+
try:
94+
with _pathlib.Path("pyproject.toml").open("rb") as toml_file:
95+
pyproject_toml = _tomllib.load(toml_file)
96+
except FileNotFoundError:
97+
return defaults
98+
except (IOError, OSError) as err:
99+
print(f"WARNING: Failed to load pyproject.toml: {err}")
100+
return defaults
101+
102+
try:
103+
config = pyproject_toml["tool"]["frequenz-repo-config"]["setuptools"][
104+
"grpc_tools"
105+
]
106+
except KeyError:
107+
return defaults
108+
109+
known_keys = frozenset(defaults.keys())
110+
config_keys = frozenset(config.keys())
111+
if unknown_keys := config_keys - known_keys:
112+
print(
113+
"WARNING: There are some configuration keys in pyproject.toml we don't "
114+
"know about and will be ignored: "
115+
+ ", ".join(f"'{k}'" for k in unknown_keys)
116+
)
117+
118+
if "include_paths" in config:
119+
config["include_paths"] = ",".join(config["include_paths"])
120+
121+
return dict(defaults, **{k: config[k] for k in (known_keys & config_keys)})
122+
71123
def run(self) -> None:
72124
"""Compile the Python protobuf files."""
73125
include_paths = self.include_paths.split(",")
@@ -76,7 +128,10 @@ def run(self) -> None:
76128
]
77129

78130
if not proto_files:
79-
print(f"No proto files found in {self.proto_path}/**/{self.proto_glob}/")
131+
print(
132+
f"No proto files found in {self.proto_path}/**/{self.proto_glob}/, "
133+
"skipping compilation of proto files."
134+
)
80135
return
81136

82137
protoc_cmd = (

tests/integration/test_cookiecutter_generation.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,30 @@
1616
def test_generation(tmp_path: pathlib.Path, repo_type: str) -> None:
1717
"""Test generation of a new repo."""
1818
cwd = pathlib.Path().cwd()
19-
subprocess.run(
20-
[
21-
"cookiecutter",
22-
"--no-input",
23-
cwd / "cookiecutter",
24-
f"type={repo_type}",
25-
"name=test",
26-
"description=Test description",
27-
],
28-
cwd=tmp_path,
29-
check=True,
19+
_run(
20+
tmp_path,
21+
"cookiecutter",
22+
"--no-input",
23+
str(cwd / "cookiecutter"),
24+
f"type={repo_type}",
25+
"name=test",
26+
"description=Test description",
3027
)
28+
3129
subdirs = list(tmp_path.iterdir())
3230
assert len(subdirs) == 1
3331
repo_path = subdirs[0]
34-
subprocess.run("python3 -m venv .venv".split(), cwd=repo_path, check=True)
35-
subprocess.run(
36-
". .venv/bin/activate; pip install .[dev-noxfile]; nox",
37-
shell=True,
38-
cwd=repo_path,
39-
check=True,
40-
)
32+
_run(repo_path, "python3", "-m", "venv", ".venv")
33+
34+
cmd = ". .venv/bin/activate; pip install .[dev-noxfile]; nox"
35+
print()
36+
print(f"Running in shell [{cwd}]: {cmd}")
37+
subprocess.run(cmd, shell=True, cwd=repo_path, check=True)
38+
39+
40+
def _run(cwd: pathlib.Path, *cmd: str) -> subprocess.CompletedProcess[bytes]:
41+
print()
42+
print("-" * 80)
43+
print(f"Running [{cwd}]: {' '.join(cmd)}")
44+
print()
45+
return subprocess.run(cmd, cwd=cwd, check=True)

0 commit comments

Comments
 (0)