diff --git a/CHANGELOG.md b/CHANGELOG.md index a3d388f1..8edcfc60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## CHANGELOG +### 6.0.0 + +* Added support for `License-Expression` metadata field, see [PEP 639](https://peps.python.org/pep-0639/) +* Added `--from=expression` option +* Breaking change: The `--from=all` output now includes the `License-Expression` value + ### 5.0.0 * Dropped support Python 3.8 diff --git a/piplicenses.py b/piplicenses.py index b2e09ef8..c902797a 100755 --- a/piplicenses.py +++ b/piplicenses.py @@ -162,6 +162,9 @@ def normalize_pkg_name(pkg_name: str) -> str: lambda metadata: metadata.get("maintainer-email"), ], "license": [lambda metadata: metadata.get("license")], + "license_expression": [ + lambda metadata: metadata.get("license-expression") + ], "summary": [lambda metadata: metadata.get("summary")], } @@ -171,6 +174,7 @@ def normalize_pkg_name(pkg_name: str) -> str: "Description": "summary", "License-Metadata": "license", "License-Classifier": "license_classifier", + "License-Expression": "license_expression", } @@ -312,6 +316,7 @@ def get_python_sys_path(executable: str) -> list[str]: args.from_, cast(List[str], pkg_info["license_classifier"]), cast(str, pkg_info["license"]), + cast(str, pkg_info["license_expression"]), ) if fail_on_licenses: @@ -374,6 +379,7 @@ def create_licenses_table( args.from_, cast(List[str], pkg["license_classifier"]), cast(str, pkg["license"]), + cast(str, pkg["license_expression"]), ) license_str = "; ".join(sorted(license_set)) row.append(license_str) @@ -399,6 +405,7 @@ def create_summary_table(args: CustomNamespace) -> PrettyTable: args.from_, cast(List[str], pkg["license_classifier"]), cast(str, pkg["license"]), + cast(str, pkg["license_expression"]), ) ) ) @@ -613,8 +620,14 @@ def find_license_from_classifier(classifiers: list[str]) -> list[str]: def select_license_by_source( - from_source: FromArg, license_classifier: list[str], license_meta: str + from_source: FromArg, + license_classifier: list[str], + license_meta: str, + license_expression: str, ) -> set[str]: + if not license_expression or license_expression != LICENSE_UNKNOWN: + return {license_expression} + license_classifier_set = set(license_classifier) or {LICENSE_UNKNOWN} if ( from_source == FromArg.CLASSIFIER @@ -635,6 +648,7 @@ def get_output_fields(args: CustomNamespace) -> list[str]: if args.from_ == FromArg.ALL: output_fields.append("License-Metadata") output_fields.append("License-Classifier") + output_fields.append("License-Expression") else: output_fields.append("License") @@ -839,6 +853,7 @@ class FromArg(NoValueEnum): META = M = auto() CLASSIFIER = C = auto() MIXED = MIX = auto() + EXPRESSION = EXPR = auto() ALL = auto() diff --git a/test_piplicenses.py b/test_piplicenses.py index ca5623b3..6c8c8434 100644 --- a/test_piplicenses.py +++ b/test_piplicenses.py @@ -212,6 +212,20 @@ def test_from_mixed(self) -> None: license_notation_as_classifier = "MIT License" self.assertIn(license_notation_as_classifier, license_columns) + def test_from_expression(self) -> None: + from_args = ["--from=expression"] + args = self.parser.parse_args(from_args) + output_fields = get_output_fields(args) + table = create_licenses_table(args, output_fields) + + self.assertIn("License", output_fields) + + license_columns = self._create_license_columns(table, output_fields) + license_notation_as_expression = "MIT" + # TODO enable assert once a dependency uses 'License-Expression' + # TODO (maybe black) + # self.assertIn(license_notation_as_expression, license_columns) + def test_from_all(self) -> None: from_args = ["--from=all"] args = self.parser.parse_args(from_args) @@ -220,6 +234,7 @@ def test_from_all(self) -> None: self.assertIn("License-Metadata", output_fields) self.assertIn("License-Classifier", output_fields) + self.assertIn("License-Expression", output_fields) index_license_meta = output_fields.index("License-Metadata") license_meta = [] @@ -231,6 +246,11 @@ def test_from_all(self) -> None: for row in table.rows: license_classifier.append(row[index_license_classifier]) + index_license_expression = output_fields.index("License-Expression") + license_expression = [ + row[index_license_expression] for row in table.rows + ] + for license_name in ("BSD", "MIT", "Apache 2.0"): self.assertIn(license_name, license_meta) for license_name in ( @@ -239,6 +259,10 @@ def test_from_all(self) -> None: "Apache Software License", ): self.assertIn(license_name, license_classifier) + # TODO enable assert once a dependency uses 'License-Expression' + # TODO (maybe black) + # for license_name in ("MIT",): + # self.assertIn(license_name, license_expression) def test_find_license_from_classifier(self) -> None: classifiers = ["License :: OSI Approved :: MIT License"] @@ -270,27 +294,62 @@ def test_select_license_by_source(self) -> None: self.assertEqual( {"MIT License"}, select_license_by_source( - FromArg.CLASSIFIER, ["MIT License"], "MIT" + FromArg.CLASSIFIER, ["MIT License"], "MIT", LICENSE_UNKNOWN ), ) self.assertEqual( {LICENSE_UNKNOWN}, - select_license_by_source(FromArg.CLASSIFIER, [], "MIT"), + select_license_by_source( + FromArg.CLASSIFIER, [], "MIT", LICENSE_UNKNOWN + ), ) self.assertEqual( {"MIT License"}, - select_license_by_source(FromArg.MIXED, ["MIT License"], "MIT"), + select_license_by_source( + FromArg.MIXED, ["MIT License"], "MIT", LICENSE_UNKNOWN + ), ) self.assertEqual( - {"MIT"}, select_license_by_source(FromArg.MIXED, [], "MIT") + {"MIT"}, + select_license_by_source( + FromArg.MIXED, [], "MIT", LICENSE_UNKNOWN + ), ) self.assertEqual( {"Apache License 2.0"}, select_license_by_source( - FromArg.MIXED, ["Apache License 2.0"], "Apache-2.0" + FromArg.MIXED, + ["Apache License 2.0"], + "Apache-2.0", + LICENSE_UNKNOWN, + ), + ) + + self.assertEqual( + {"MIT"}, + select_license_by_source( + FromArg.MIXED, [], LICENSE_UNKNOWN, "MIT" + ), + ) + self.assertEqual( + {"Apache-2.0"}, + select_license_by_source( + FromArg.MIXED, + ["Apache License 2.0"], + "Apache", + "Apache-2.0", + ), + ) + self.assertEqual( + {"Apache-2.0"}, + select_license_by_source( + FromArg.EXPRESSION, + ["Apache License 2.0"], + "Apache", + "Apache-2.0", ), )