Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support disabling trove classifier verification #1620

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions backend/src/hatchling/metadata/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -977,8 +977,6 @@ def classifiers(self) -> list[str]:
if self._classifiers is None:
import bisect

import trove_classifiers

if 'classifiers' in self.config:
classifiers = self.config['classifiers']
if 'classifiers' in self.dynamic:
Expand All @@ -994,23 +992,36 @@ def classifiers(self) -> list[str]:
message = 'Field `project.classifiers` must be an array'
raise TypeError(message)

known_classifiers = trove_classifiers.classifiers | self._extra_classifiers
verify_classifiers = not os.environ.get("HATCH_NO_VERIFY_TROVE_CLASSIFIERS")
if verify_classifiers:
import trove_classifiers

known_classifiers = trove_classifiers.classifiers | self._extra_classifiers
sorted_classifiers = list(trove_classifiers.sorted_classifiers)

for classifier in sorted(self._extra_classifiers - trove_classifiers.classifiers):
bisect.insort(sorted_classifiers, classifier)

unique_classifiers = set()

for i, classifier in enumerate(classifiers, 1):
if not isinstance(classifier, str):
message = f'Classifier #{i} of field `project.classifiers` must be a string'
raise TypeError(message)

if not self.__classifier_is_private(classifier) and classifier not in known_classifiers:
if not self.__classifier_is_private(classifier) and verify_classifiers and classifier not in known_classifiers:
message = f'Unknown classifier in field `project.classifiers`: {classifier}'
raise ValueError(message)

unique_classifiers.add(classifier)

sorted_classifiers = list(trove_classifiers.sorted_classifiers)
for classifier in sorted(self._extra_classifiers - trove_classifiers.classifiers):
bisect.insort(sorted_classifiers, classifier)
if not verify_classifiers:
import re

# combined text-numeric sort that ensures that Python versions sort correctly
split_re = re.compile(r'(\D*)(\d*)')
sort_key = lambda value: [(a, int(b) if b else None) for a, b in split_re.findall(value)]
sorted_classifiers = sorted(classifiers, key=sort_key)

self._classifiers = sorted(
unique_classifiers, key=lambda c: -1 if self.__classifier_is_private(c) else sorted_classifiers.index(c)
Expand Down
27 changes: 26 additions & 1 deletion tests/backend/metadata/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,12 +950,37 @@ def test_entry_not_string(self, isolation):
with pytest.raises(TypeError, match='Classifier #1 of field `project.classifiers` must be a string'):
_ = metadata.core.classifiers

def test_entry_unknown(self, isolation):
def test_entry_unknown(self, isolation, monkeypatch):
monkeypatch.delenv("HATCH_NO_VERIFY_TROVE_CLASSIFIERS", False)
metadata = ProjectMetadata(str(isolation), None, {'project': {'classifiers': ['foo']}})

with pytest.raises(ValueError, match='Unknown classifier in field `project.classifiers`: foo'):
_ = metadata.core.classifiers

def test_entry_unknown_no_verify(self, isolation, monkeypatch):
monkeypatch.setenv("HATCH_NO_VERIFY_TROVE_CLASSIFIERS", "1")
classifiers = [
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.9',
'Development Status :: 4 - Beta',
'Private :: Do Not Upload',
'Foo',
]
metadata = ProjectMetadata(str(isolation), None, {'project': {'classifiers': classifiers}})

assert (
metadata.core.classifiers
== metadata.core.classifiers
== [
'Private :: Do Not Upload',
'Development Status :: 4 - Beta',
'Foo',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.11',
]
)

def test_correct(self, isolation):
classifiers = [
'Programming Language :: Python :: 3.11',
Expand Down