Skip to content

Commit

Permalink
Validate SPDX license expressions (#217)
Browse files Browse the repository at this point in the history
  • Loading branch information
abravalheri authored Nov 11, 2024
2 parents 73977d9 + 7a8c7dc commit f45606b
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 5 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Download = "https://pypi.org/project/validate-pyproject/#files"

[project.optional-dependencies]
all = [
"packaging>=20.4",
"packaging>=24.2",
"tomli>=1.2.1; python_version<'3.11'",
"trove-classifiers>=2021.10.20",
]
Expand Down
26 changes: 22 additions & 4 deletions src/validate_pyproject/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,25 @@ def int(value: builtins.int) -> bool:
return -(2**63) <= value < 2**63


def SPDX(value: str) -> bool:
"""Should validate eventually"""
# TODO: validate conditional to the presence of (the right version) of packaging
return True
try:
from packaging import licenses as _licenses

def SPDX(value: str) -> bool:
"""See :ref:`PyPA's License-Expression specification
<pypa:core-metadata-license-expression>` (added in :pep:`639`).
"""
try:
_licenses.canonicalize_license_expression(value)
return True
except _licenses.InvalidLicenseExpression:
return False

except ImportError: # pragma: no cover
_logger.warning(
"Could not find an up-to-date installation of `packaging`. "
"License expressions might not be validated. "
"To enforce validation, please install `packaging>=24.2`."
)

def SPDX(value: str) -> bool:
return True
File renamed without changes.
1 change: 1 addition & 0 deletions tests/invalid-examples/simple/pep639.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`project.license` must be valid exactly by one definition (0 matches found)
5 changes: 5 additions & 0 deletions tests/invalid-examples/simple/pep639.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[project]
name = "example"
version = "1.2.3"
license = "Apache Software License" # should be "Apache-2.0"
license-files = ["licenses/LICENSE"]
48 changes: 48 additions & 0 deletions tests/test_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,54 @@ def test_invalid_module_name_relaxed(example):
assert formats.python_module_name_relaxed(example) is False


@pytest.mark.parametrize(
"example",
[
"MIT",
"Bsd-3-clause",
"mit and (apache-2.0 or bsd-2-clause)",
"MIT OR GPL-2.0-or-later OR (FSFUL AND BSD-2-Clause)",
"GPL-3.0-only WITH Classpath-exception-2.0 OR BSD-3-Clause",
"LicenseRef-Special-License OR CC0-1.0 OR Unlicense",
"LicenseRef-Public-Domain",
"licenseref-proprietary",
"LicenseRef-Beerware-4.2",
"(LicenseRef-Special-License OR LicenseRef-OtherLicense) OR Unlicense",
],
)
def test_valid_pep639_license_expression(example):
assert formats.SPDX(example) is True


@pytest.mark.parametrize(
"example",
[
"",
"Use-it-after-midnight",
"LicenseRef-License with spaces",
"LicenseRef-License_with_underscores",
"or",
"and",
"with",
"mit or",
"mit and",
"mit with",
"or mit",
"and mit",
"with mit",
"(mit",
"mit)",
"mit or or apache-2.0",
# Missing an operator before `(`.
"mit or apache-2.0 (bsd-3-clause and MPL-2.0)",
# "2-BSD-Clause is not a valid license.
"Apache-2.0 OR 2-BSD-Clause",
],
)
def test_invalid_pep639_license_expression(example):
assert formats.SPDX(example) is False


class TestClassifiers:
"""The ``_TroveClassifier`` class and ``_download_classifiers`` are part of the
private API and therefore need to be tested.
Expand Down

0 comments on commit f45606b

Please sign in to comment.