Skip to content

Commit 015ea00

Browse files
jlainechris48s
andauthored
[PyPI] Fix license for packages following PEP 639 (#11001)
* [PyPI] Fix license for packages following PEP 639 PEP 639 states that the preferred way of documenting a Python project's license is an SPDX expression in a `License-Expression` metadata field. PyPI exposes this information in `info.license_expression` in its JSON data. Fixes: #11000 * add license_expression to pypi response schema * move comments inline into the relevant blocks * assign both license and license_expression to intermediate variables * always pass a license_expression in test input objects --------- Co-authored-by: chris48s <git@chris-shaw.dev>
1 parent 9f7a14b commit 015ea00

File tree

3 files changed

+45
-16
lines changed

3 files changed

+45
-16
lines changed

Diff for: services/pypi/pypi-base.js

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const schema = Joi.object({
99
// https://github.com/badges/shields/issues/2022
1010
// https://github.com/badges/shields/issues/7728
1111
license: Joi.string().allow('').allow(null),
12+
license_expression: Joi.string().allow('').allow(null),
1213
classifiers: Joi.array().items(Joi.string()).required(),
1314
}).required(),
1415
urls: Joi.array()

Diff for: services/pypi/pypi-helpers.js

+20-13
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,29 @@ function parseClassifiers(parsedData, pattern, preserveCase = false) {
4747
}
4848

4949
function getLicenses(packageData) {
50-
const {
51-
info: { license },
52-
} = packageData
50+
const license = packageData.info.license
51+
const licenseExpression = packageData.info.license_expression
5352

54-
/*
55-
The .license field may either contain
56-
- a short license description (e.g: 'MIT' or 'GPL-3.0') or
57-
- the full text of a license
58-
but there is nothing in the response that tells us explicitly.
59-
We have to make an assumption based on the length.
60-
See https://github.com/badges/shields/issues/8689 and
61-
https://github.com/badges/shields/pull/8690 for more info.
62-
*/
63-
if (license && license.length < 40) {
53+
if (licenseExpression) {
54+
/*
55+
The .license_expression field contains an SPDX expression, and it
56+
is the preferred way of documenting a Python project's license.
57+
See https://peps.python.org/pep-0639/
58+
*/
59+
return [licenseExpression]
60+
} else if (license && license.length < 40) {
61+
/*
62+
The .license field may either contain
63+
- a short license description (e.g: 'MIT' or 'GPL-3.0') or
64+
- the full text of a license
65+
but there is nothing in the response that tells us explicitly.
66+
We have to make an assumption based on the length.
67+
See https://github.com/badges/shields/issues/8689 and
68+
https://github.com/badges/shields/pull/8690 for more info.
69+
*/
6470
return [license]
6571
} else {
72+
// else fall back to trove classifiers
6673
const parenthesizedAcronymRegex = /\(([^)]+)\)/
6774
const bareAcronymRegex = /^[a-z0-9]+$/
6875
const spdxAliases = {

Diff for: services/pypi/pypi-helpers.spec.js

+24-3
Original file line numberDiff line numberDiff line change
@@ -100,32 +100,47 @@ describe('PyPI helpers', function () {
100100
})
101101

102102
test(getLicenses, () => {
103-
forCases([given({ info: { license: 'MIT', classifiers: [] } })]).expect([
104-
'MIT',
105-
])
106103
forCases([
107104
given({
108105
info: {
109106
license: null,
107+
license_expression: 'MIT',
108+
classifiers: [],
109+
},
110+
}),
111+
given({
112+
info: {
113+
license: 'MIT',
114+
license_expression: null,
115+
classifiers: [],
116+
},
117+
}),
118+
given({
119+
info: {
120+
license: null,
121+
license_expression: null,
110122
classifiers: ['License :: OSI Approved :: MIT License'],
111123
},
112124
}),
113125
given({
114126
info: {
115127
license: '',
128+
license_expression: null,
116129
classifiers: ['License :: OSI Approved :: MIT License'],
117130
},
118131
}),
119132
given({
120133
info: {
121134
license:
122135
'this text is really really really really really really long',
136+
license_expression: null,
123137
classifiers: ['License :: OSI Approved :: MIT License'],
124138
},
125139
}),
126140
given({
127141
info: {
128142
license: '',
143+
license_expression: null,
129144
classifiers: [
130145
'License :: OSI Approved :: MIT License',
131146
'License :: DFSG approved',
@@ -136,24 +151,28 @@ describe('PyPI helpers', function () {
136151
given({
137152
info: {
138153
license: '',
154+
license_expression: null,
139155
classifiers: ['License :: Public Domain'],
140156
},
141157
}).expect(['Public Domain'])
142158
given({
143159
info: {
144160
license: '',
161+
license_expression: null,
145162
classifiers: ['License :: Netscape Public License (NPL)'],
146163
},
147164
}).expect(['NPL'])
148165
given({
149166
info: {
150167
license: '',
168+
license_expression: null,
151169
classifiers: ['License :: OSI Approved :: Apache Software License'],
152170
},
153171
}).expect(['Apache-2.0'])
154172
given({
155173
info: {
156174
license: '',
175+
license_expression: null,
157176
classifiers: [
158177
'License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication',
159178
],
@@ -162,6 +181,7 @@ describe('PyPI helpers', function () {
162181
given({
163182
info: {
164183
license: '',
184+
license_expression: null,
165185
classifiers: [
166186
'License :: OSI Approved :: GNU Affero General Public License v3',
167187
],
@@ -170,6 +190,7 @@ describe('PyPI helpers', function () {
170190
given({
171191
info: {
172192
license: '',
193+
license_expression: null,
173194
classifiers: ['License :: OSI Approved :: Zero-Clause BSD (0BSD)'],
174195
},
175196
}).expect(['0BSD'])

0 commit comments

Comments
 (0)