Skip to content

Commit

Permalink
Support disabling trove classifier verification
Browse files Browse the repository at this point in the history
Add a HATCH_NO_VERIFY_TROVE_CLASSIFIERS hook to disable verification
of trove classifiers.  This makes it possible to build newer packages
against old versions of trove-classifiers (or even without the package
installed).  This is a convenience feature for Linux distributions
that do not want to track minimum required trove-classifiers versions
as required by various packages.

Fixes #1368
  • Loading branch information
mgorny committed Jul 16, 2024
1 parent 3adae6c commit 55c79a3
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 8 deletions.
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

0 comments on commit 55c79a3

Please sign in to comment.