Skip to content

Commit edb1774

Browse files
committed
fix: don't error if setup.cfg missing python requires
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
1 parent dac6032 commit edb1774

File tree

2 files changed

+58
-41
lines changed

2 files changed

+58
-41
lines changed

src/sp_repo_review/checks/pyproject.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

3-
from typing import Any
3+
from typing import TYPE_CHECKING, Any
44

5-
from .._compat.importlib.resources.abc import Traversable
65
from . import mk_url
76

7+
if TYPE_CHECKING:
8+
from configparser import ConfigParser
9+
810

911
class PyProject:
1012
family = "pyproject"
@@ -57,7 +59,7 @@ class PP004(PyProject):
5759
url = mk_url("packaging-simple")
5860

5961
@staticmethod
60-
def check(pyproject: dict[str, Any], package: Traversable) -> bool | None:
62+
def check(pyproject: dict[str, Any], setupcfg: ConfigParser | None) -> bool | None:
6163
"""
6264
You should never upper cap your Python requirement. This is rarely correct, and
6365
tools like pip do not handle this properly even if it is correct. This field is used
@@ -81,15 +83,10 @@ def check(pyproject: dict[str, Any], package: Traversable) -> bool | None:
8183
"^" not in requires and "~=" not in requires and "<" not in requires
8284
)
8385

84-
setup_cfg = package / "setup.cfg"
85-
if setup_cfg.is_file():
86-
# pylint: disable-next=import-outside-toplevel
87-
import configparser
88-
89-
config = configparser.ConfigParser()
90-
config.read_string(setup_cfg.read_text(encoding="utf-8"))
91-
if requires := config.get("options", "python_requires"):
92-
return "~=" not in requires and "<" not in requires
86+
if setupcfg and (
87+
requires := setupcfg.get("options", "python_requires", fallback=None)
88+
):
89+
return "~=" not in requires and "<" not in requires
9390

9491
return None
9592

tests/test_pyproject.py

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import configparser
12
import inspect
2-
from pathlib import Path
33

44
from repo_review.testing import compute_check, toml_loads
55

@@ -49,68 +49,88 @@ def test_PP003_has_wheel():
4949
assert not compute_check("PP003", pyproject=toml).result
5050

5151

52-
def test_PP004_no_cap_pyproject(tmp_path: Path):
52+
def test_PP004_no_cap_pyproject():
5353
toml = toml_loads("""
5454
[project]
5555
requires-python = ">=3.10"
5656
""")
5757

58-
assert compute_check("PP004", pyproject=toml, package=tmp_path).result
58+
assert compute_check("PP004", pyproject=toml, setupcfg=None).result
5959

6060

61-
def test_PP004_cap_pyproject(tmp_path: Path):
61+
def test_PP004_cap_pyproject():
6262
toml = toml_loads("""
6363
[project]
6464
requires-python = ">=3.10, <4"
6565
""")
6666

67-
assert compute_check("PP004", pyproject=toml, package=tmp_path).result is False
67+
assert compute_check("PP004", pyproject=toml, setupcfg=None).result is False
6868

6969

70-
def test_PP004_cap_tilde_pyproject(tmp_path: Path):
70+
def test_PP004_cap_tilde_pyproject():
7171
toml = toml_loads("""
7272
[project]
7373
requires-python = "~=3.10"
7474
""")
7575

76-
assert compute_check("PP004", pyproject=toml, package=tmp_path).result is False
76+
assert compute_check("PP004", pyproject=toml, setupcfg=None).result is False
7777

7878

79-
def test_PP004_cap_caret_pyproject(tmp_path: Path):
79+
def test_PP004_cap_caret_pyproject():
8080
toml = toml_loads("""
8181
[tool.poetry.dependencies]
8282
python = "^3.10"
8383
""")
8484

85-
assert compute_check("PP004", pyproject=toml, package=tmp_path).result is False
85+
assert compute_check("PP004", pyproject=toml, setupcfg=None).result is False
8686

8787

88-
def test_PP004_setup_cfg_no_cap(tmp_path: Path):
89-
(tmp_path / "setup.cfg").write_text(
90-
inspect.cleandoc("""
91-
[options]
92-
python_requires = >=3.10
93-
"""),
94-
encoding="utf-8",
95-
)
88+
def test_PP004_setup_cfg_no_cap():
89+
contents = inspect.cleandoc("""
90+
[options]
91+
python_requires = >=3.10
92+
""")
93+
config = configparser.ConfigParser()
94+
config.read_string(contents)
95+
96+
assert compute_check("PP004", pyproject={}, setupcfg=config).result
97+
98+
99+
def test_PP004_setup_cfg_cap():
100+
contents = inspect.cleandoc("""
101+
[options]
102+
python_requires = >=3.10,<4
103+
""")
104+
config = configparser.ConfigParser()
105+
config.read_string(contents)
96106

97-
assert compute_check("PP004", pyproject={}, package=tmp_path).result
107+
assert compute_check("PP004", pyproject={}, setupcfg=config).result is False
98108

99109

100-
def test_PP004_setup_cfg_cap(tmp_path: Path):
101-
(tmp_path / "setup.cfg").write_text(
102-
inspect.cleandoc("""
103-
[options]
104-
python_requires = >=3.10,<4
105-
"""),
106-
encoding="utf-8",
107-
)
110+
def test_PP004_setup_cfg_no_section():
111+
contents = inspect.cleandoc("""
112+
[other]
113+
python_requires = >=3.10
114+
""")
115+
config = configparser.ConfigParser()
116+
config.read_string(contents)
117+
118+
assert not compute_check("PP004", pyproject={}, setupcfg=config).result
119+
120+
121+
def test_PP004_setup_cfg_no_value():
122+
contents = inspect.cleandoc("""
123+
[options]
124+
other = >=3.10
125+
""")
126+
config = configparser.ConfigParser()
127+
config.read_string(contents)
108128

109-
assert compute_check("PP004", pyproject={}, package=tmp_path).result is False
129+
assert not compute_check("PP004", pyproject={}, setupcfg=config).result
110130

111131

112-
def test_PP004_not_present(tmp_path: Path):
113-
assert compute_check("PP004", pyproject={}, package=tmp_path).result is None
132+
def test_PP004_not_present():
133+
assert compute_check("PP004", pyproject={}, setupcfg=None).result is None
114134

115135

116136
def test_PP005_no_license():

0 commit comments

Comments
 (0)