Skip to content

Commit

Permalink
Validate Trove classifiers in twine check
Browse files Browse the repository at this point in the history
  • Loading branch information
browniebroke committed Oct 12, 2024
1 parent ded15b9 commit b7a353e
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 12 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencies = [
"keyring >= 15.1; platform_machine != 'ppc64le' and platform_machine != 's390x'",
"rfc3986 >= 1.4.0",
"rich >= 12.0.0",
"trove-classifiers >= 2024.10.12 ",

# workaround for #1116
"pkginfo < 1.11",
Expand Down
74 changes: 74 additions & 0 deletions tests/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,80 @@ def test_main(monkeypatch):
assert check_stub.calls == [pretend.call(["dist/*"], strict=False)]


def test_fails_invalid_classifiers(tmp_path, capsys, caplog):
sdist = build_sdist(
tmp_path,
{
"setup.cfg": (
"""
[metadata]
name = test-package
version = 0.0.1
long_description = file:README.md
long_description_content_type = text/markdown
classifiers =
Framework :: Django :: 5
"""
),
"README.md": (
"""
# test-package
A test package.
"""
),
},
)

assert check.check([sdist])

assert capsys.readouterr().out == f"Checking {sdist}: FAILED\n"

assert len(caplog.record_tuples) > 0
assert caplog.record_tuples == [
(
"twine.commands.check",
logging.ERROR,
"`Framework :: Django :: 5` is not a valid classifier"
" and would prevent upload to PyPI.\n",
)
]


def test_passes_valid_classifiers(tmp_path, capsys, caplog):
sdist = build_sdist(
tmp_path,
{
"setup.cfg": (
"""
[metadata]
name = test-package
version = 0.0.1
long_description = file:README.md
long_description_content_type = text/markdown
classifiers =
Programming Language :: Python :: 3
Framework :: Django
Framework :: Django :: 5.1
"""
),
"README.md": (
"""
# test-package
A test package.
"""
),
},
)

assert not check.check([sdist])

assert capsys.readouterr().out == f"Checking {sdist}: PASSED\n"

assert caplog.record_tuples == []


# TODO: Test print() color output

# TODO: Test log formatting
35 changes: 23 additions & 12 deletions twine/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import readme_renderer.rst
from rich import print
from trove_classifiers import classifiers as all_classifiers

from twine import commands
from twine import package as package_file
Expand Down Expand Up @@ -76,14 +77,15 @@ def _parse_content_type(value: str) -> Tuple[str, Dict[str, str]]:

def _check_file(
filename: str, render_warning_stream: _WarningStream
) -> Tuple[List[str], bool]:
) -> Tuple[List[str], List[str]]:
"""Check given distribution."""
warnings = []
is_ok = True
errors = []

package = package_file.PackageFile.from_filename(filename, comment=None)

metadata = package.metadata_dictionary()

# Check description
description = cast(Optional[str], metadata["description"])
description_content_type = cast(Optional[str], metadata["description_content_type"])

Expand All @@ -103,9 +105,21 @@ def _check_file(
description, stream=render_warning_stream, **params
)
if rendering_result is None:
is_ok = False
errors.append(
"`long_description` has syntax errors in markup"
" and would not be rendered on PyPI."
)

return warnings, is_ok
# Check classifiers
dist_classifiers = cast(Optional[List[str]], metadata["classifiers"])
for classifier in dist_classifiers:
if classifier not in all_classifiers:
errors.append(
f"`{classifier}` is not a valid classifier"
f" and would prevent upload to PyPI."
)

return warnings, errors


def check(
Expand Down Expand Up @@ -137,17 +151,14 @@ def check(
for filename in uploads:
print(f"Checking {filename}: ", end="")
render_warning_stream = _WarningStream()
warnings, is_ok = _check_file(filename, render_warning_stream)
warnings, errors = _check_file(filename, render_warning_stream)

# Print the status and/or error
if not is_ok:
if errors:
failure = True
print("[red]FAILED[/red]")
logger.error(
"`long_description` has syntax errors in markup"
" and would not be rendered on PyPI."
f"\n{render_warning_stream}"
)
error_mgs = "\n".join(errors)
logger.error(f"{error_mgs}\n" f"{render_warning_stream}")
elif warnings:
if strict:
failure = True
Expand Down

0 comments on commit b7a353e

Please sign in to comment.