Compare commits

...

2 Commits

Author SHA1 Message Date
Paul Melnikow
332a496e84 Match unit test to value used by API 2020-09-22 15:57:24 -04:00
Paul Melnikow
5f28ac34cc [PyPI] When Python version classifiers are absent, fall back to requires_python 2020-09-22 15:54:16 -04:00
7 changed files with 110 additions and 29 deletions

15
package-lock.json generated
View File

@@ -6460,6 +6460,21 @@
}
}
},
"@renovate/pep440": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@renovate/pep440/-/pep440-0.4.1.tgz",
"integrity": "sha512-UJR4qqM5l1b84iXd9SS8nPOFfxCWDgL3uOhhYwb15DgC1j8wf5ZLoyTcVUhnCLXG770c999PNLJEUl4wRscvhQ==",
"requires": {
"xregexp": "4.2.0"
},
"dependencies": {
"xregexp": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.2.0.tgz",
"integrity": "sha512-IyMa7SVe9FyT4WbQVW3b95mTLVceHhLEezQ02+QMvmIqDnKTxk0MLWIQPSW2MXAr1zQb+9yvwYhcyQULneh3wA=="
}
}
},
"@samverschueren/stream-to-observable": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",

View File

@@ -23,10 +23,12 @@
},
"dependencies": {
"@hapi/joi": "^17.1.1",
"@renovate/pep440": "^0.4.1",
"@sentry/node": "^5.24.2",
"@shields_io/camp": "^18.0.0",
"badge-maker": "file:badge-maker",
"bytes": "^3.1.0",
"camelcase": "^5.3.1",
"@shields_io/camp": "^18.0.0",
"chai-as-promised": "^7.1.1",
"chalk": "^4.1.0",
"check-node-version": "^4.0.3",
@@ -39,7 +41,6 @@
"escape-string-regexp": "^4.0.0",
"fast-xml-parser": "^3.17.4",
"fsos": "^1.1.6",
"badge-maker": "file:badge-maker",
"glob": "^7.1.6",
"graphql": "^14.7.0",
"graphql-tag": "^2.11.0",

View File

@@ -9,6 +9,7 @@ const schema = Joi.object({
// https://github.com/badges/shields/issues/2022
license: Joi.string().allow(''),
classifiers: Joi.array().items(Joi.string()).required(),
requires_python: Joi.alternatives().try(Joi.string(), Joi.allow(null)),
}).required(),
releases: Joi.object()
.pattern(

View File

@@ -1,5 +1,13 @@
'use strict'
const { satisfies } = require('@renovate/pep440')
// This list tracks "Active Python Releases" at
// https://www.python.org/downloads/ which means it needs to be manually updated
// every two years or so. It would be good to find a machine-readable version of
// this listing (like we do with PHP) so it does not need to be updated manually.
const ACTIVE_PYTHON_VERSIONS = ['2.7', '3.5', '3.6', '3.7', '3.8']
/*
Django versions will be specified in the form major.minor
trying to sort with `semver.compare` will throw e.g:
@@ -93,10 +101,52 @@ function getPackageFormats(packageData) {
}
}
function getPythonVersionsFromClassifiers(packageData) {
let versions = parseClassifiers(
packageData,
/^Programming Language :: Python :: ([\d.]+)$/
)
// If no versions are found yet, check "X :: Only" as a fallback.
if (versions.length === 0) {
versions.push(
...parseClassifiers(
packageData,
/^Programming Language :: Python :: (\d+) :: Only$/
)
)
}
// We only show v2 if eg. v2.4 does not appear.
// See https://github.com/badges/shields/pull/489 for more.
;['2', '3'].forEach(majorVersion => {
if (versions.some(v => v.startsWith(`${majorVersion}.`))) {
versions = versions.filter(v => v !== majorVersion)
}
})
return versions.sort()
}
function getPythonVersionsFromPythonRequires(packageData) {
const {
info: { requires_python: pythonRequires },
} = packageData
if (pythonRequires) {
return ACTIVE_PYTHON_VERSIONS.filter(activeVersion =>
satisfies(activeVersion, pythonRequires)
)
} else {
return undefined
}
}
module.exports = {
parseClassifiers,
parseDjangoVersionString,
sortDjangoVersions,
getLicenses,
getPackageFormats,
getPythonVersionsFromClassifiers,
getPythonVersionsFromPythonRequires,
}

View File

@@ -7,6 +7,7 @@ const {
sortDjangoVersions,
getLicenses,
getPackageFormats,
getPythonVersionsFromPythonRequires,
} = require('./pypi-helpers')
const classifiersFixture = {
@@ -168,4 +169,13 @@ describe('PyPI helpers', function () {
},
}).expect({ hasWheel: false, hasEgg: true })
})
test(getPythonVersionsFromPythonRequires, () => {
given({ info: { requires_python: null } }).expect(undefined)
given({
info: {
requires_python: '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
},
}).expect(['2.7', '3.5', '3.6', '3.7', '3.8'])
})
})

View File

@@ -1,7 +1,10 @@
'use strict'
const PypiBase = require('./pypi-base')
const { parseClassifiers } = require('./pypi-helpers')
const {
getPythonVersionsFromClassifiers,
getPythonVersionsFromPythonRequires,
} = require('./pypi-helpers')
module.exports = class PypiPythonVersions extends PypiBase {
static get category() {
@@ -18,7 +21,7 @@ module.exports = class PypiPythonVersions extends PypiBase {
title: 'PyPI - Python Version',
pattern: ':packageName',
namedParams: { packageName: 'Django' },
staticPreview: this.render({ versions: ['3.5', '3.6', '3.7'] }),
staticPreview: this.render({ versions: ['3.5', '3.6', '3.7', '3.8'] }),
},
]
}
@@ -28,17 +31,9 @@ module.exports = class PypiPythonVersions extends PypiBase {
}
static render({ versions }) {
const versionSet = new Set(versions)
// We only show v2 if eg. v2.4 does not appear.
// See https://github.com/badges/shields/pull/489 for more.
;['2', '3'].forEach(majorVersion => {
if (Array.from(versions).some(v => v.startsWith(`${majorVersion}.`))) {
versionSet.delete(majorVersion)
}
})
if (versionSet.size) {
if (versions.length) {
return {
message: Array.from(versionSet).sort().join(' | '),
message: versions.join(' | '),
color: 'blue',
}
} else {
@@ -52,19 +47,10 @@ module.exports = class PypiPythonVersions extends PypiBase {
async handle({ egg }) {
const packageData = await this.fetch({ egg })
const versions = parseClassifiers(
packageData,
/^Programming Language :: Python :: ([\d.]+)$/
)
// If no versions are found yet, check "X :: Only" as a fallback.
if (versions.length === 0) {
versions.push(
...parseClassifiers(
packageData,
/^Programming Language :: Python :: (\d+) :: Only$/
)
)
}
const versions =
getPythonVersionsFromClassifiers(packageData) ||
getPythonVersionsFromPythonRequires(packageData) ||
[]
return this.constructor.render({ versions })
}

View File

@@ -21,11 +21,29 @@ t.create('python versions (valid, no package version specified)')
message: isPipeSeparatedPythonVersions,
})
t.create('python versions ("Only" and others)')
t.create(
'python versions (valid, package version in request, experimental flag)'
)
.get('/requests/2.18.4.json?experimental')
.expectBadge({
label: 'python',
message: isPipeSeparatedPythonVersions,
})
t.create(
'python versions (valid, no package version specified, experimental flag)'
)
.get('/requests.json?experimental')
.expectBadge({
label: 'python',
message: isPipeSeparatedPythonVersions,
})
t.create('python versions ("Only" classifier and others)')
.get('/uvloop/0.12.1.json')
.expectBadge({ label: 'python', message: '3.5 | 3.6 | 3.7' })
t.create('python versions ("Only" only)')
t.create('python versions ("Only" classifier only)')
.get('/hashpipe/0.9.1.json')
.expectBadge({ label: 'python', message: '3' })