Skip to content

Commit da5550b

Browse files
committed
Split mypy check for the source package and dev files
`mypy` tends to have issues with checking source files inside a `src/` directory. There are tricks[1] but it seems like the only way to do it is to run `mypy` on `.` using `MYPYPATH=src`, which will still check the whole `.` directory (include whole virtualenvs if there are any), which is not what we want. For now we've been using `-p` to check for packages instead of files, which seemed to have done the trick, but we need to pass directories that don't really have a package structure (like benchmarks or examples or tests) as packages, which is also not great. This commit tries a different approach, we check the source code as a package separately, and then check the development files (tests, examples, etc.), which are passed as simple paths. This also opens up the door to using more relaxed rules for these development files if needed in the future. To achieve this we also move the `packages` specification to the `pyproject.toml` file, so now `mypy` can be called without any arguments to check the source code package, making it easier to call it without relaying in `nox`. [1] https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 7b9625d commit da5550b

File tree

10 files changed

+44
-6
lines changed

10 files changed

+44
-6
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ testpaths = ["pytests"]
173173
[tool.mypy]
174174
explicit_package_bases = true
175175
namespace_packages = true
176+
packages = ["{{cookiecutter.python_package}}"]
176177
strict = true
177178
{%- if cookiecutter.type != "api" %}
178179

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ disable = [
156156
[tool.mypy]
157157
explicit_package_bases = true
158158
namespace_packages = true
159+
packages = ["frequenz.repo.config"]
159160
strict = true
160161

161162
[[tool.mypy.overrides]]

src/frequenz/repo/config/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,13 @@
182182
[tool.mypy]
183183
explicit_package_bases = true
184184
namespace_packages = true
185+
packages = ["your_package_name"] # Use the actual package name here
185186
strict = true
186187
```
187188
189+
You can just call `mypy` to check the package of your sources or you can use `mypy
190+
tests` to check the tests, for example.
191+
188192
## `mkdocs` (generating documentation)
189193
190194
### API reference generation

src/frequenz/repo/config/nox/config.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,14 @@ def copy(self, /) -> Self:
114114
extra_paths=self.extra_paths.copy(),
115115
)
116116

117-
def path_args(self, session: _nox.Session, /) -> list[str]:
117+
def path_args(
118+
self,
119+
session: _nox.Session,
120+
/,
121+
*,
122+
include_sources: bool = True,
123+
include_extra: bool = True,
124+
) -> list[str]:
118125
"""Return the file paths to run the checks on.
119126
120127
If positional arguments are present in the nox session, those are used
@@ -123,16 +130,22 @@ def path_args(self, session: _nox.Session, /) -> list[str]:
123130
124131
Args:
125132
session: The nox session to use to look for command-line arguments.
133+
include_sources: Whether to include the source paths or not.
134+
include_extra: Whether to include the extra paths or not.
126135
127136
Returns:
128137
The file paths to run the checks on.
129138
"""
130139
if session.posargs:
131140
return session.posargs
132141

133-
return list(
134-
str(p) for p in _util.existing_paths(self.source_paths + self.extra_paths)
135-
)
142+
paths: list[str] = []
143+
if include_sources:
144+
paths.extend(self.source_paths)
145+
if include_extra:
146+
paths.extend(self.extra_paths)
147+
148+
return list(str(p) for p in _util.existing_paths(paths))
136149

137150
def package_args(self, session: _nox.Session, /) -> list[str]:
138151
"""Return the package names to run the checks on.

src/frequenz/repo/config/nox/session.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,22 @@ def mypy(session: nox.Session, install_deps: bool = True) -> None:
6262
session.install("-e", ".[dev-mypy]")
6363

6464
conf = _config.get()
65-
pkg_args = _util.flatten(("-p", p) for p in conf.package_args(session))
66-
session.run("mypy", *conf.opts.mypy, *pkg_args)
65+
66+
# If we get CLI options, we run mypy on those, but still passing the
67+
# configured options (they can be overridden by the CLI options).
68+
if session.posargs:
69+
session.run("mypy", *conf.opts.mypy, *session.posargs)
70+
return
71+
72+
# We separate running the mypy checks into two runs, one is the default, as
73+
# configured in `pyproject.toml`, which should run against the sources.
74+
session.run("mypy", *conf.opts.mypy)
75+
76+
# The second run checks development files, like tests, benchmarks, etc.
77+
# This is an attempt to minimize mypy internal errors.
78+
session.run(
79+
"mypy", *conf.opts.mypy, *conf.path_args(session, include_sources=False)
80+
)
6781

6882

6983
@nox.session

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ required_plugins = ["pytest-asyncio", "pytest-mock"]
146146
[tool.mypy]
147147
explicit_package_bases = true
148148
namespace_packages = true
149+
packages = ["frequenz.actor.test"]
149150
strict = true
150151

151152
[[tool.mypy.overrides]]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ testpaths = ["pytests"]
138138
[tool.mypy]
139139
explicit_package_bases = true
140140
namespace_packages = true
141+
packages = ["frequenz.api.test"]
141142
strict = true
142143

143144
[tool.setuptools.package-dir]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ required_plugins = ["pytest-asyncio", "pytest-mock"]
145145
[tool.mypy]
146146
explicit_package_bases = true
147147
namespace_packages = true
148+
packages = ["frequenz.app.test"]
148149
strict = true
149150

150151
[[tool.mypy.overrides]]

tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ required_plugins = ["pytest-asyncio", "pytest-mock"]
142142
[tool.mypy]
143143
explicit_package_bases = true
144144
namespace_packages = true
145+
packages = ["frequenz.test"]
145146
strict = true
146147

147148
[[tool.mypy.overrides]]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ required_plugins = ["pytest-asyncio", "pytest-mock"]
146146
[tool.mypy]
147147
explicit_package_bases = true
148148
namespace_packages = true
149+
packages = ["frequenz.model.test"]
149150
strict = true
150151

151152
[[tool.mypy.overrides]]

0 commit comments

Comments
 (0)