Skip to content

Commit 0c9aeac

Browse files
manavgupjkowalleck
andauthored
fix: make pep621 license detections type-aware (#920)
--------- Signed-off-by: Manav Gupta <manavg@gmail.com> Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com> Co-authored-by: Jan Kowalleck <jan.kowalleck@gmail.com>
1 parent aab442d commit 0c9aeac

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

cyclonedx_py/_internal/utils/pep621.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def project2licenses(project: dict[str, Any], lfac: 'LicenseFactory', *,
6060
# https://peps.python.org/pep-0621/#classifiers
6161
# https://packaging.python.org/en/latest/specifications/core-metadata/#classifier-multiple-use
6262
yield from classifiers2licenses(classifiers, lfac, lack)
63-
if plicense := project.get('license'):
63+
if isinstance(plicense := project.get('license'), dict):
6464
# https://packaging.python.org/en/latest/specifications/pyproject-toml/#license
6565
# https://peps.python.org/pep-0621/#license
6666
# https://packaging.python.org/en/latest/specifications/core-metadata/#license
@@ -87,6 +87,7 @@ def project2licenses(project: dict[str, Any], lfac: 'LicenseFactory', *,
8787
text=AttachedText(content=plicense_text))
8888
else:
8989
yield license
90+
# Silently skip any other types (including string/PEP 639)
9091

9192

9293
def project2extrefs(project: dict[str, Any]) -> Generator['ExternalReference', None, None]:
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# This file is part of CycloneDX Python
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
# Copyright (c) OWASP Foundation. All Rights Reserved.
17+
18+
from os.path import join
19+
from tempfile import TemporaryDirectory
20+
from unittest import TestCase
21+
22+
from cyclonedx.factory.license import LicenseFactory
23+
from cyclonedx.model import Encoding
24+
from cyclonedx.model.license import DisjunctiveLicense, LicenseAcknowledgement
25+
from ddt import ddt, named_data
26+
27+
from cyclonedx_py._internal.utils.pep621 import project2licenses
28+
29+
30+
@ddt()
31+
class TestUtilsPEP621(TestCase):
32+
33+
def test_project2licenses_license_dict_text(self) -> None:
34+
project = {
35+
'name': 'testpkg',
36+
'license': {'text': 'This is the license text.'},
37+
}
38+
lfac = LicenseFactory()
39+
with TemporaryDirectory() as tmpdir:
40+
licenses = list(project2licenses(project, lfac, fpath=join(tmpdir, 'pyproject.toml')))
41+
self.assertEqual(len(licenses), 1)
42+
lic = licenses[0]
43+
self.assertIsInstance(lic, DisjunctiveLicense)
44+
self.assertIsNone(lic.id)
45+
self.assertIsNone(lic.text.encoding)
46+
self.assertEqual(lic.text.content, 'This is the license text.')
47+
self.assertEqual(lic.acknowledgement, LicenseAcknowledgement.DECLARED)
48+
49+
def test_project2licenses_license_dict_file(self) -> None:
50+
project = {
51+
'name': 'testpkg',
52+
'license': {'file': 'license.txt'},
53+
}
54+
lfac = LicenseFactory()
55+
with TemporaryDirectory() as tmpdir:
56+
with open(join(tmpdir, project['license']['file']), 'w') as tf:
57+
tf.write('File license text')
58+
licenses = list(project2licenses(project, lfac, fpath=join(tmpdir, 'pyproject.toml')))
59+
self.assertEqual(len(licenses), 1)
60+
lic = licenses[0]
61+
self.assertIsInstance(lic, DisjunctiveLicense)
62+
self.assertIs(lic.text.encoding, Encoding.BASE_64)
63+
self.assertEqual(lic.text.content, 'RmlsZSBsaWNlbnNlIHRleHQ=')
64+
self.assertEqual(lic.acknowledgement, LicenseAcknowledgement.DECLARED)
65+
66+
@named_data(
67+
('none', None),
68+
('string', 'MIT'),
69+
('list', ['MIT', 'Apache-2.0'])
70+
)
71+
def test_project2licenses_license_non_dict(self, license: any) -> None:
72+
project = {
73+
'name': 'testpkg',
74+
'license': license,
75+
}
76+
lfac = LicenseFactory()
77+
with TemporaryDirectory() as tmpdir:
78+
licenses = list(project2licenses(project, lfac, fpath=join(tmpdir, 'pyproject.toml')))
79+
self.assertEqual(len(licenses), 0)

0 commit comments

Comments
 (0)