Commit ddf36aab by Kevin Deldycke Committed by GitHub

Check project against valid and deprecated trove classifiers. (#2881)

Inspects trove classifiers on `check` CLI command calls, and look for
unrecognized and deprecated categories.

Adds dependency https://github.com/pypa/trove-classifiers, a package
published and maintained by the PyPA that is cataloguing all classifiers.
This is the canonical source of all trove definitions.

Resolves: #2579
parent 3b4ac379
...@@ -67,6 +67,7 @@ requests-toolbelt = "^0.9.1" ...@@ -67,6 +67,7 @@ requests-toolbelt = "^0.9.1"
shellingham = "^1.5" shellingham = "^1.5"
# exclude 0.11.2 and 0.11.3 due to https://github.com/sdispater/tomlkit/issues/225 # exclude 0.11.2 and 0.11.3 due to https://github.com/sdispater/tomlkit/issues/225
tomlkit = ">=0.11.1,<1.0.0,!=0.11.2,!=0.11.3" tomlkit = ">=0.11.1,<1.0.0,!=0.11.2,!=0.11.3"
trove-classifiers = "^2022.5.19"
# exclude 20.4.5 - 20.4.6 due to https://github.com/pypa/pip/issues/9953 # exclude 20.4.5 - 20.4.6 due to https://github.com/pypa/pip/issues/9953
virtualenv = "^20.4.3,!=20.4.5,!=20.4.6" virtualenv = "^20.4.3,!=20.4.5,!=20.4.6"
xattr = { version = "^0.9.7", markers = "sys_platform == 'darwin'" } xattr = { version = "^0.9.7", markers = "sys_platform == 'darwin'" }
......
...@@ -9,6 +9,52 @@ class CheckCommand(Command): ...@@ -9,6 +9,52 @@ class CheckCommand(Command):
name = "check" name = "check"
description = "Checks the validity of the <comment>pyproject.toml</comment> file." description = "Checks the validity of the <comment>pyproject.toml</comment> file."
def validate_classifiers(
self, project_classifiers: set[str]
) -> tuple[list[str], list[str]]:
"""Identify unrecognized and deprecated trove classifiers.
A fully-qualified classifier is a string delimited by `` :: `` separators. To
make the error message more readable we need to have visual clues to
materialize the start and end of a classifier string. That way the user can
easily copy and paste it from the messages while reducing mistakes because of
extra spaces.
We use ``!r`` (``repr()``) for classifiers and list of classifiers for
consistency. That way all strings will be rendered with the same kind of quotes
(i.e. simple tick: ``'``).
"""
from trove_classifiers import classifiers
from trove_classifiers import deprecated_classifiers
errors = []
warnings = []
unrecognized = sorted(
project_classifiers - set(classifiers) - set(deprecated_classifiers)
)
if unrecognized:
errors.append(f"Unrecognized classifiers: {unrecognized!r}.")
deprecated = sorted(
project_classifiers.intersection(set(deprecated_classifiers))
)
if deprecated:
for old_classifier in deprecated:
new_classifiers = deprecated_classifiers[old_classifier]
if new_classifiers:
message = (
f"Deprecated classifier {old_classifier!r}. "
f"Must be replaced by {new_classifiers!r}."
)
else:
message = (
f"Deprecated classifier {old_classifier!r}. Must be removed."
)
warnings.append(message)
return errors, warnings
def handle(self) -> int: def handle(self) -> int:
from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.pyproject.toml import PyProjectTOML
...@@ -18,6 +64,13 @@ class CheckCommand(Command): ...@@ -18,6 +64,13 @@ class CheckCommand(Command):
poetry_file = Factory.locate(Path.cwd()) poetry_file = Factory.locate(Path.cwd())
config = PyProjectTOML(poetry_file).poetry_config config = PyProjectTOML(poetry_file).poetry_config
check_result = Factory.validate(config, strict=True) check_result = Factory.validate(config, strict=True)
# Validate trove classifiers
project_classifiers = set(config.get("classifiers", []))
errors, warnings = self.validate_classifiers(project_classifiers)
check_result["errors"].extend(errors)
check_result["warnings"].extend(warnings)
if not check_result["errors"] and not check_result["warnings"]: if not check_result["errors"] and not check_result["warnings"]:
self.info("All set!") self.info("All set!")
......
...@@ -41,10 +41,16 @@ def test_check_invalid(mocker: MockerFixture, tester: CommandTester): ...@@ -41,10 +41,16 @@ def test_check_invalid(mocker: MockerFixture, tester: CommandTester):
expected = """\ expected = """\
Error: 'description' is a required property Error: 'description' is a required property
Error: Unrecognized classifiers: ['Intended Audience :: Clowns'].
Warning: A wildcard Python dependency is ambiguous.\ Warning: A wildcard Python dependency is ambiguous.\
Consider specifying a more explicit one. Consider specifying a more explicit one.
Warning: The "pendulum" dependency specifies the "allows-prereleases" property,\ Warning: The "pendulum" dependency specifies the "allows-prereleases" property,\
which is deprecated. Use "allow-prereleases" instead. which is deprecated. Use "allow-prereleases" instead.
Warning: Deprecated classifier 'Natural Language :: Ukranian'.\
Must be replaced by ['Natural Language :: Ukrainian'].
Warning: Deprecated classifier\
'Topic :: Communications :: Chat :: AOL Instant Messenger'.\
Must be removed.
""" """
assert tester.io.fetch_error() == expected assert tester.io.fetch_error() == expected
...@@ -5,6 +5,12 @@ authors = [ ...@@ -5,6 +5,12 @@ authors = [
"Foo <foo@bar.com>" "Foo <foo@bar.com>"
] ]
license = "INVALID" license = "INVALID"
classifiers = [
"Environment :: Console",
"Intended Audience :: Clowns",
"Natural Language :: Ukranian",
"Topic :: Communications :: Chat :: AOL Instant Messenger",
]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "*" python = "*"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment