Skip to content

Commit a7adc9a

Browse files
authored
Merge branch 'main' into support-===foobar
2 parents 32bda73 + 306d0b8 commit a7adc9a

25 files changed

+242
-204
lines changed

docs/utils.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ Reference
8181
:class:`~packaging.version.Version`. The build number is ``()`` if
8282
there is no build number in the wheel filename, otherwise a
8383
two-item tuple of an integer for the leading digits and
84-
a string for the rest of the build number. The tags portion is an
85-
instance of :class:`~packaging.tags.Tag`.
84+
a string for the rest of the build number. The tags portion is a
85+
frozen set of :class:`~packaging.tags.Tag` instances (as the tag
86+
string format allows multiple tags to be combined into a single
87+
string).
8688

8789
:param str filename: The name of the wheel file.
8890
:raises InvalidWheelFilename: If the filename in question

noxfile.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,19 @@ def tests(session):
4343
coverage = ["python", "-m", "coverage"]
4444

4545
session.install(*nox.project.dependency_groups(PYPROJECT, "test"))
46-
session.install(".")
46+
session.install("-e.")
4747
env = {} if session.python != "3.14" else {"COVERAGE_CORE": "sysmon"}
4848

4949
if "pypy" not in session.python:
5050
session.run(
5151
*coverage,
5252
"run",
53-
"--source",
54-
"packaging",
5553
"-m",
5654
"pytest",
5755
*session.posargs,
5856
env=env,
5957
)
60-
session.run(*coverage, "report", "-m", "--fail-under", "100")
58+
session.run(*coverage, "report")
6159
else:
6260
# Don't do coverage tracking for PyPy, since it's SLOW.
6361
session.run(
@@ -237,6 +235,7 @@ def _check_git_state(session, version_tag):
237235
]
238236
result = subprocess.run(
239237
["git", "remote", "get-url", "--push", "upstream"],
238+
check=False,
240239
capture_output=True,
241240
encoding="utf-8",
242241
)
@@ -245,6 +244,7 @@ def _check_git_state(session, version_tag):
245244
# Ensure we're on main branch for cutting a release.
246245
result = subprocess.run(
247246
["git", "rev-parse", "--abbrev-ref", "HEAD"],
247+
check=False,
248248
capture_output=True,
249249
encoding="utf-8",
250250
)
@@ -253,15 +253,21 @@ def _check_git_state(session, version_tag):
253253

254254
# Ensure there are no uncommitted changes.
255255
result = subprocess.run(
256-
["git", "status", "--porcelain"], capture_output=True, encoding="utf-8"
256+
["git", "status", "--porcelain"],
257+
check=False,
258+
capture_output=True,
259+
encoding="utf-8",
257260
)
258261
if result.stdout:
259262
print(result.stdout, end="", file=sys.stderr)
260263
session.error("The working tree has uncommitted changes")
261264

262265
# Ensure this tag doesn't exist already.
263266
result = subprocess.run(
264-
["git", "rev-parse", version_tag], capture_output=True, encoding="utf-8"
267+
["git", "rev-parse", version_tag],
268+
check=False,
269+
capture_output=True,
270+
encoding="utf-8",
265271
)
266272
if not result.returncode:
267273
session.error(f"Tag already exists! {version_tag} -- {result.stdout!r}")
@@ -280,30 +286,26 @@ def _bump(session, *, version, file, kind):
280286
file.write_text(new_contents)
281287

282288
session.log("git commit")
283-
subprocess.run(["git", "add", str(file)])
284-
subprocess.run(["git", "commit", "-m", f"Bump for {kind}"])
289+
subprocess.run(["git", "add", str(file)], check=False)
290+
subprocess.run(["git", "commit", "-m", f"Bump for {kind}"], check=False)
285291

286292

287293
@contextlib.contextmanager
288294
def _replace_file(original_path):
289295
# Create a temporary file.
290296
fh, replacement_path = tempfile.mkstemp()
291297

292-
try:
293-
with os.fdopen(fh, "w") as replacement:
294-
with open(original_path) as original:
295-
yield original, replacement
296-
except Exception:
297-
raise
298-
else:
299-
shutil.copymode(original_path, replacement_path)
300-
os.remove(original_path)
301-
shutil.move(replacement_path, original_path)
298+
with os.fdopen(fh, "w") as replacement, open(original_path) as original:
299+
yield original, replacement
300+
301+
shutil.copymode(original_path, replacement_path)
302+
os.remove(original_path)
303+
shutil.move(replacement_path, original_path)
302304

303305

304306
def _changelog_update_unreleased_title(version, *, file):
305307
"""Update an "*unreleased*" heading to "{version} - {date}" """
306-
yyyy_mm_dd = datetime.datetime.today().strftime("%Y-%m-%d")
308+
yyyy_mm_dd = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y-%m-%d")
307309
title = f"{version} - {yyyy_mm_dd}"
308310

309311
with _replace_file(file) as (original, replacement):

pyproject.toml

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Source = "https://github.com/pypa/packaging"
3737

3838
[dependency-groups]
3939
test = [
40-
"coverage[toml]>=5.0.0",
40+
"coverage[toml]>=7.2.0",
4141
"pip>=21.1",
4242
"pretend",
4343
"pytest>=6.2.0",
@@ -59,16 +59,19 @@ ignore-words-list = [
5959

6060
[tool.coverage.run]
6161
branch = true
62+
source_pkgs = ["packaging"]
6263

6364
[tool.coverage.report]
64-
exclude_lines = ["pragma: no cover", "@abc.abstractmethod", "@abc.abstractproperty"]
65+
show_missing = true
66+
fail_under = 100
67+
exclude_also = ["@abc.abstractmethod", "@abc.abstractproperty"]
6568

6669
[tool.pytest.ini_options]
6770
minversion = "6.2"
6871
addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
6972
xfail_strict = true
7073
filterwarnings = ["error"]
71-
log_cli_level = "INFO"
74+
log_level = "INFO"
7275
testpaths = ["tests"]
7376

7477

@@ -86,25 +89,57 @@ ignore_missing_imports = true
8689
extend-exclude = [
8790
"src/packaging/licenses/_spdx.py"
8891
]
92+
show-fixes = true
8993

9094
[tool.ruff.lint]
9195
extend-select = [
92-
"B",
96+
"ARG", # flake8-unused-argument
97+
"B", # flake8-bugbear
98+
"BLE", # flake8-blind-except
99+
"C4", # flake8-comprehensions
100+
"DTZ", # flake8-datetimez
93101
"E",
102+
"EXE", # flake8-executable
94103
"F",
95-
"FA",
96-
"I",
97-
"N",
98-
"PYI",
99-
"RUF",
100-
"UP",
101-
"W"
104+
"FA", # flake8-future-annotations
105+
"FLY", # flynt
106+
"FURB", # refurb
107+
"G", # flake8-logging-format
108+
"I", # isort
109+
"ICN", # flake8-import-conventions
110+
"ISC", # flake8-implicit-str-concat
111+
"LOG", # flake8-logging
112+
"N", # pep8-naming
113+
"PERF", # perflint
114+
"PGH", # pygrep-hooks
115+
"PIE", # flake8-pie
116+
"PL", # pylint
117+
"PT", # flake8-pytest-style
118+
"PYI", # flake8-pyi
119+
"Q", # flake8-quotes
120+
"RSE", # flake8-raise
121+
"RUF", # Ruff-specific rules
122+
"SIM", # flake8-simplify
123+
"SLOT", # flake8-slots
124+
"T10", # flake8-debugger
125+
"TRY", # tryceratops
126+
"UP", # pyupgrade
127+
"W",
128+
"YTT", # flake8-2020
102129
]
103130
ignore = [
104-
"N818", # exceptions must end in "*Error"
131+
"N818", # exceptions must end in "*Error"
132+
"PLR09", # too many ...
133+
"PLR2004", # magic value in comparison
134+
"PLW0127", # duplicate of F821
135+
"SIM103", # returning negated value directly not ideal for long chain
136+
"SIM105", # try/except is faster than contextlib.suppress
137+
"TRY003", # long messages outside exception class
105138
]
139+
flake8-comprehensions.allow-dict-calls-with-keyword-arguments = true
140+
flake8-unused-arguments.ignore-variadic-names = true
106141

107142
[tool.ruff.lint.per-file-ignores]
108-
"tests/test_*.py" = ["PYI024"]
143+
"tests/test_*.py" = ["PYI024", "PLR", "SIM201"]
109144
"tasks/check.py" = ["UP032"]
110145
"tests/test_requirements.py" = ["UP032"]

src/packaging/_manylinux.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def _glibc_version_string_ctypes() -> str | None:
106106
Fallback implementation of glibc_version_string using ctypes.
107107
"""
108108
try:
109-
import ctypes
109+
import ctypes # noqa: PLC0415
110110
except ImportError:
111111
return None
112112

@@ -184,23 +184,24 @@ def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
184184
return False
185185
# Check for presence of _manylinux module.
186186
try:
187-
import _manylinux
187+
import _manylinux # noqa: PLC0415
188188
except ImportError:
189189
return True
190190
if hasattr(_manylinux, "manylinux_compatible"):
191191
result = _manylinux.manylinux_compatible(version[0], version[1], arch)
192192
if result is not None:
193193
return bool(result)
194194
return True
195-
if version == _GLibCVersion(2, 5):
196-
if hasattr(_manylinux, "manylinux1_compatible"):
197-
return bool(_manylinux.manylinux1_compatible)
198-
if version == _GLibCVersion(2, 12):
199-
if hasattr(_manylinux, "manylinux2010_compatible"):
200-
return bool(_manylinux.manylinux2010_compatible)
201-
if version == _GLibCVersion(2, 17):
202-
if hasattr(_manylinux, "manylinux2014_compatible"):
203-
return bool(_manylinux.manylinux2014_compatible)
195+
if version == _GLibCVersion(2, 5) and hasattr(_manylinux, "manylinux1_compatible"):
196+
return bool(_manylinux.manylinux1_compatible)
197+
if version == _GLibCVersion(2, 12) and hasattr(
198+
_manylinux, "manylinux2010_compatible"
199+
):
200+
return bool(_manylinux.manylinux2010_compatible)
201+
if version == _GLibCVersion(2, 17) and hasattr(
202+
_manylinux, "manylinux2014_compatible"
203+
):
204+
return bool(_manylinux.manylinux2014_compatible)
204205
return True
205206

206207

src/packaging/_musllinux.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _get_musl_version(executable: str) -> _MuslVersion | None:
4949
return None
5050
if ld is None or "musl" not in ld:
5151
return None
52-
proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
52+
proc = subprocess.run([ld], check=False, stderr=subprocess.PIPE, text=True)
5353
return _parse_musl_version(proc.stderr)
5454

5555

src/packaging/_tokenizer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __init__(
3333

3434
def __str__(self) -> str:
3535
marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
36-
return "\n ".join([self.message, self.source, marker])
36+
return f"{self.message}\n {self.source}\n {marker}"
3737

3838

3939
DEFAULT_RULES: dict[str, str | re.Pattern[str]] = {

src/packaging/licenses/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,7 @@ def canonicalize_license_expression(
9494
token == "("
9595
and python_tokens
9696
and python_tokens[-1] not in {"or", "and", "("}
97-
):
98-
message = f"Invalid license expression: {raw_license_expression!r}"
99-
raise InvalidLicenseExpression(message)
100-
elif token == ")" and python_tokens and python_tokens[-1] == "(":
97+
) or (token == ")" and python_tokens and python_tokens[-1] == "("):
10198
message = f"Invalid license expression: {raw_license_expression!r}"
10299
raise InvalidLicenseExpression(message)
103100
else:

src/packaging/markers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def _format_marker(
180180
def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str]) -> bool:
181181
if isinstance(rhs, str):
182182
try:
183-
spec = Specifier("".join([op.serialize(), rhs]))
183+
spec = Specifier(f"{op.serialize()}{rhs}")
184184
except InvalidSpecifier:
185185
pass
186186
else:

src/packaging/metadata.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,10 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
360360
# We have to wrap parsed.keys() in a set, because in the case of multiple
361361
# values for a key (a list), the key will appear multiple times in the
362362
# list of keys, but we're avoiding that by using get_all().
363-
for name in frozenset(parsed.keys()):
363+
for name_with_case in frozenset(parsed.keys()):
364364
# Header names in RFC are case insensitive, so we'll normalize to all
365365
# lower case to make comparisons easier.
366-
name = name.lower()
366+
name = name_with_case.lower()
367367

368368
# We use get_all() here, even for fields that aren't multiple use,
369369
# because otherwise someone could have e.g. two Name fields, and we
@@ -399,7 +399,7 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
399399
# can be independently encoded, so we'll need to check each
400400
# of them.
401401
chunks: list[tuple[bytes, str | None]] = []
402-
for bin, encoding in email.header.decode_header(h):
402+
for bin, _encoding in email.header.decode_header(h):
403403
try:
404404
bin.decode("utf8", "strict")
405405
except UnicodeDecodeError:

0 commit comments

Comments
 (0)