Skip to content

Commit dac6032

Browse files
henryiiipre-commit-ci[bot]Copilot
authored
feat: check for deprecated setup.cfg settings (#572)
* feat: check for deprecated setup.cfg settings Signed-off-by: Henry Schreiner <[email protected]> * style: pre-commit fixes * Update tests/test_setupcfg.py Co-authored-by: Copilot <[email protected]> * Update test_setupcfg.py --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <[email protected]>
1 parent 533c606 commit dac6032

File tree

8 files changed

+134
-28
lines changed

8 files changed

+134
-28
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,9 @@ _readthedocs
155155

156156
# Default cookiecutter output
157157
/package
158+
159+
# VSCode
160+
/.vscode
161+
162+
# UV output
163+
/uv.lock

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,9 @@ for family, grp in itertools.groupby(collected.checks.items(), key=lambda x: x[1
365365
- `RF201`: Avoid using deprecated config settings
366366
- `RF202`: Use (new) lint config section
367367

368+
### Setuptools Config
369+
- [`SCFG001`](https://learn.scientific-python.org/development/guides/packaging-classic#SCFG001): Avoid deprecated setup.cfg names
370+
368371
<!-- [[[end]]] -->
369372

370373
[repo-review]: https://repo-review.readthedocs.io

docs/pages/guides/packaging_classic.md

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,27 @@ packaging styles, but this document is intended to outline a recommended style
1515
that existing packages should slowly adopt. The reasoning for each decision is
1616
outlined as well.
1717

18-
There are several popular packaging systems. This guide covers [Setuptools][],
19-
which is the oldest system and supports compiled extensions. If you are not
20-
working on legacy code or are willing to make a larger change, other systems
21-
like [Hatch][] are drastically simpler - most of this page is unneeded for those
22-
systems. Even setuptools supports modern config now, though setup.py is still
23-
also required for compiled packages to be supported.
24-
25-
Also see the [Python packaging guide][], especially the [Python packaging
26-
tutorial][].
18+
There are several popular packaging systems. This guide covers the old
19+
configuration style for [Setuptools][]. Unless you really need it, you should be
20+
using the modern style described in
21+
[Simple Packaging](/guides/packaging-simple/). The modern style is guided by
22+
Python Enhancement Proposals (PEPs), and is more stable than the
23+
setuptools-specific mechanisms that evolve over the years. This page is kept to
24+
help users with legacy code (and hopefully upgrade it).
2725

2826
{: .note }
2927

30-
> Raw source lives in git and has a `setup.py`. You _can_ install directly from
31-
> git via pip, but normally users install from distributions hosted on PyPI.
32-
> There are three options: **A)** A source package, called an SDist and has a
33-
> name that ends in `.tar.gz`. This is a copy of the GitHub repository, stripped
34-
> of a few specifics like CI files, and possibly with submodules included (if
35-
> there are any). **B)** A pure python wheel, which ends in `.whl`; this is only
36-
> possible if there are no compiled extensions in the library. This does _not_
37-
> contain a setup.py, but rather a `PKG_INFO` file that is rendered from
38-
> setup.py (or from another build system). **C)** If not pure Python, a
39-
> collection of wheels for every binary platform, generally one per supported
40-
> Python version and OS as well.
28+
> Raw source lives in git and has a `pyproject.toml` and/or a `setup.py`. You
29+
> _can_ install directly from git via pip, but normally users install from
30+
> distributions hosted on PyPI. There are three options: **A)** A source
31+
> package, called an SDist and has a name that ends in `.tar.gz`. This is a copy
32+
> of the GitHub repository, stripped of a few specifics like CI files, and
33+
> possibly with submodules included (if there are any). **B)** A pure python
34+
> wheel, which ends in `.whl`; this is only possible if there are no compiled
35+
> extensions in the library. This does _not_ contain a setup.py, but rather a
36+
> `PKG_INFO` file that is rendered from setup.py (or from another build system).
37+
> **C)** If not pure Python, a collection of wheels for every binary platform,
38+
> generally one per supported Python version and OS as well.
4139
>
4240
> Developer requirements (users of A or git) are generally higher than the
4341
> requirements to use B or C. Poetry and optionally flit create SDists that
@@ -87,8 +85,8 @@ these "[hypermodern][]" packaging tools is growing in scientific Python
8785
packages. All tools build the same wheels (and they often build setuptools
8886
compliant SDists, as well).
8987

90-
{% rr PP003 %} Note that `"wheel"` is never required; it is injected
91-
automatically by setuptools only when needed.
88+
{% rr PP003 %} Note that `"wheel"` is never required; it was injected
89+
automatically by setuptools in older versions, and is no longer used at all.
9290

9391
### Special additions: NumPy
9492

@@ -326,9 +324,12 @@ where = src
326324
# extern
327325
```
328326

327+
{% rr SCFG001 %} Note that all keys use underscores; using a dash will cause
328+
warnings and eventually failures.
329+
329330
And, a possible `setup.py`; though in recent versions of pip, there no longer is
330-
a need to include a legacy `setup.py` file, even for editable installs, unless
331-
you are building extensions.
331+
a need to include a legacy `setup.py` file, even for editable installs or
332+
building extensions.
332333

333334
```python
334335
#!/usr/bin/env python
@@ -479,7 +480,5 @@ the app.
479480
[manifest.in]: https://packaging.python.org/guides/using-manifest-in/
480481
[setuptools]: https://setuptools.readthedocs.io/en/latest/userguide/index.html
481482
[setuptools cfg]: https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html
482-
[python packaging guide]: https://packaging.python.org
483-
[python packaging tutorial]: https://packaging.python.org/tutorials/packaging-projects/
484483

485484
<!-- prettier-ignore-end -->

docs/pages/guides/packaging_simple.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ much; they all use a [standard configuration language][metadata] introduced in
1616
[PEP 621][]. The PyPA's Flit is a great option. [scikit-build-core][] and
1717
[meson-python][] are being developed to support this sort of configuration,
1818
enabling binary extension packages to benefit too. These [PEP 621][] tools
19-
currently include [Hatch][], [PDM][], [Flit][], and [Setuptools][]. [Poetry][]
20-
will eventually gain support in 2.0.
19+
currently include [Hatch][], [PDM][], [Flit][], [Setuptools][], [Poetry][] 2.0,
20+
and compiled backends (see the next page).
21+
22+
Also see the [Python packaging guide][], especially the [Python packaging
23+
tutorial][].
2124

2225
{: .note-title }
2326

@@ -204,6 +207,8 @@ This is tool specific.
204207
[pep 621]: https://www.python.org/dev/peps/pep-0621
205208
[scikit-build-core]: https://scikit-build-core.readthedocs.io
206209
[meson-python]: https://meson-python.readthedocs.io
210+
[python packaging guide]: https://packaging.python.org
211+
[python packaging tutorial]: https://packaging.python.org/tutorials/packaging-projects/
207212
208213
<!-- prettier-ignore-end -->
209214

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,15 @@ ruff = "sp_repo_review.checks.ruff:repo_review_checks"
6969
mypy = "sp_repo_review.checks.mypy:repo_review_checks"
7070
github = "sp_repo_review.checks.github:repo_review_checks"
7171
readthedocs = "sp_repo_review.checks.readthedocs:repo_review_checks"
72+
setupcfg = "sp_repo_review.checks.setupcfg:repo_review_checks"
7273

7374
[project.entry-points."repo_review.fixtures"]
7475
workflows = "sp_repo_review.checks.github:workflows"
7576
dependabot = "sp_repo_review.checks.github:dependabot"
7677
precommit = "sp_repo_review.checks.precommit:precommit"
7778
readthedocs = "sp_repo_review.checks.readthedocs:readthedocs"
7879
ruff = "sp_repo_review.checks.ruff:ruff"
80+
setupcfg = "sp_repo_review.checks.setupcfg:setupcfg"
7981

8082
[project.entry-points."repo_review.families"]
8183
scikit-hep = "sp_repo_review.families:get_families"

src/sp_repo_review/checks/setupcfg.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# SCFG: setup.cfg
2+
## SCFG0xx: setup.cfg checks
3+
4+
from __future__ import annotations
5+
6+
import configparser
7+
8+
from .._compat.importlib.resources.abc import Traversable
9+
from . import mk_url
10+
11+
12+
def setupcfg(root: Traversable) -> configparser.ConfigParser | None:
13+
setupcfg_path = root.joinpath("setup.cfg")
14+
if setupcfg_path.is_file():
15+
config = configparser.ConfigParser()
16+
with setupcfg_path.open("r") as f:
17+
config.read_file(f)
18+
return config
19+
return None
20+
21+
22+
class SCFG:
23+
family = "setupcfg"
24+
25+
26+
class SCFG001(SCFG):
27+
"Avoid deprecated setup.cfg names"
28+
29+
url = mk_url("packaging-classic")
30+
31+
@staticmethod
32+
def check(setupcfg: configparser.ConfigParser | None) -> str | None:
33+
if setupcfg is None:
34+
return None
35+
invalid = []
36+
if setupcfg.has_section("metadata"):
37+
invalid += [
38+
f"metadata.{x}" for x, _ in setupcfg.items("metadata") if "-" in x
39+
]
40+
if setupcfg.has_section("options"):
41+
invalid += [
42+
f"options.{x}" for x, _ in setupcfg.items("options") if "-" in x
43+
]
44+
if invalid:
45+
return (
46+
"Invalid setup.cfg options found, only underscores allowed: "
47+
+ ", ".join(invalid)
48+
)
49+
return ""
50+
51+
52+
def repo_review_checks() -> dict[str, SCFG]:
53+
return {p.__name__: p() for p in SCFG.__subclasses__()}

src/sp_repo_review/families.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,7 @@ def get_families(pyproject: dict[str, Any]) -> dict[str, Family]:
5151
"docs": Family(
5252
name="Documentation",
5353
),
54+
"setupcfg": Family(
55+
name="Setuptools Config",
56+
),
5457
}

tests/test_setupcfg.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import configparser
2+
3+
from repo_review.testing import compute_check
4+
5+
6+
def test_scfg001():
7+
setupcfg = configparser.ConfigParser()
8+
setupcfg.read_string("""
9+
[metadata]
10+
name = foo
11+
version = 1.0
12+
description = A test package
13+
author = Me
14+
author_email = [email protected]
15+
""")
16+
assert compute_check("SCFG001", setupcfg=setupcfg).result
17+
18+
19+
def test_scfg001_invalid():
20+
setupcfg = configparser.ConfigParser()
21+
setupcfg.read_string("""
22+
[metadata]
23+
name = foo
24+
version = 1.0
25+
description = A test package
26+
author = Me
27+
author-email = [email protected]
28+
""")
29+
answer = compute_check("SCFG001", setupcfg=setupcfg)
30+
assert not answer.result
31+
assert "metadata.author-email" in answer.err_msg
32+
33+
34+
def test_no_setupcfg():
35+
assert compute_check("SCFG001", setupcfg=None).result is None

0 commit comments

Comments
 (0)