[pypi] Add Framework Version Badges support (#8261)

* [pypi] Add Framework Version Badges support
* Fix redirect from Django versions
* Fix staticPreview
* Refactor service to remove duplication
* Rename to Versions from Framework Classifiers
* Refactor render and handle to thrown an exception

Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
This commit is contained in:
Érico Andrei
2022-08-01 16:17:12 -03:00
committed by GitHub
parent 9e29d3d3f2
commit fbb2ff3619
6 changed files with 302 additions and 82 deletions

View File

@@ -1,45 +1,12 @@
import PypiBase from './pypi-base.js'
import { sortDjangoVersions, parseClassifiers } from './pypi-helpers.js'
import { redirector } from '../index.js'
export default class PypiDjangoVersions extends PypiBase {
static category = 'platform-support'
static route = this.buildRoute('pypi/djversions')
static examples = [
{
title: 'PyPI - Django Version',
pattern: ':packageName',
namedParams: { packageName: 'djangorestframework' },
staticPreview: this.render({ versions: ['1.11', '2.0', '2.1'] }),
keywords: ['python'],
},
]
static defaultBadgeData = { label: 'django versions' }
static render({ versions }) {
if (versions.length > 0) {
return {
message: sortDjangoVersions(versions).join(' | '),
color: 'blue',
}
} else {
return {
message: 'missing',
color: 'red',
}
}
}
async handle({ egg }) {
const packageData = await this.fetch({ egg })
const versions = parseClassifiers(
packageData,
/^Framework :: Django :: ([\d.]+)$/
)
return this.constructor.render({ versions })
}
}
export default redirector({
category: 'platform-support',
route: {
base: 'pypi/djversions',
pattern: ':packageName*',
},
transformPath: ({ packageName }) =>
`/pypi/frameworkversions/django/${packageName}`,
dateAdded: new Date('2022-07-28'),
})

View File

@@ -1,32 +1,24 @@
import Joi from 'joi'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
const isPipeSeparatedDjangoVersions = Joi.string().regex(
/^([1-9]\.[0-9]+(?: \| )?)+$/
t.create(
'redirect supported django versions (valid, package version in request)'
)
t.create('supported django versions (valid, package version in request)')
.get('/djangorestframework/3.7.3.json')
.expectBadge({
label: 'django versions',
message: isPipeSeparatedDjangoVersions,
})
.expectRedirect(
'/pypi/frameworkversions/django/djangorestframework/3.7.3.json'
)
t.create('supported django versions (valid, no package version specified)')
t.create(
'redirect supported django versions (valid, no package version specified)'
)
.get('/djangorestframework.json')
.expectBadge({
label: 'django versions',
message: isPipeSeparatedDjangoVersions,
})
.expectRedirect('/pypi/frameworkversions/django/djangorestframework.json')
t.create('supported django versions (no versions specified)')
t.create('redirect supported django versions (no versions specified)')
.get('/django/1.11.json')
.expectBadge({ label: 'django versions', message: 'missing' })
.expectRedirect('/pypi/frameworkversions/django/django/1.11.json')
t.create('supported django versions (invalid)')
t.create('redirect supported django versions (invalid)')
.get('/not-a-package.json')
.expectBadge({
label: 'django versions',
message: 'package or version not found',
})
.expectRedirect('/pypi/frameworkversions/django/not-a-package.json')

View File

@@ -0,0 +1,103 @@
import { InvalidResponse } from '../index.js'
import PypiBase from './pypi-base.js'
import { sortPypiVersions, parseClassifiers } from './pypi-helpers.js'
const frameworkNameMap = {
'aws-cdk': {
name: 'AWS CDK',
classifier: 'AWS CDK',
},
django: {
name: 'Django',
classifier: 'Django',
},
'django-cms': {
name: 'Django CMS',
classifier: 'Django CMS',
},
jupyterlab: {
name: 'JupyterLab',
classifier: 'Jupyter :: JupyterLab',
},
odoo: {
name: 'Odoo',
classifier: 'Odoo',
},
plone: {
name: 'Plone',
classifier: 'Plone',
},
wagtail: {
name: 'Wagtail',
classifier: 'Wagtail',
},
zope: {
name: 'Zope',
classifier: 'Zope',
},
}
const documentation = `
<p>
This service currently support the following Frameworks: <br/>
${Object.values(frameworkNameMap).map(obj => `<strong>${obj.name}</strong>`)}
</p>
`
export default class PypiFrameworkVersion extends PypiBase {
static category = 'platform-support'
static route = {
base: 'pypi/frameworkversions',
pattern: `:frameworkName(${Object.keys(frameworkNameMap).join(
'|'
)})/:packageName*`,
}
static examples = [
{
title: 'PyPI - Versions from Framework Classifiers',
namedParams: {
frameworkName: 'Plone',
packageName: 'plone.volto',
},
staticPreview: this.render({
name: 'Plone',
versions: ['5.2', '6.0'],
}),
keywords: ['python'],
documentation,
},
]
static defaultBadgeData = { label: 'versions' }
static render({ name, versions }) {
name = name ? name.toLowerCase() : ''
const label = `${name} versions`
return {
label,
message: sortPypiVersions(versions).join(' | '),
color: 'blue',
}
}
async handle({ frameworkName, packageName }) {
const classifier = frameworkNameMap[frameworkName]
? frameworkNameMap[frameworkName].classifier
: frameworkName
const name = frameworkNameMap[frameworkName]
? frameworkNameMap[frameworkName].name
: frameworkName
const regex = new RegExp(`^Framework :: ${classifier} :: ([\\d.]+)$`)
const packageData = await this.fetch({ egg: packageName })
const versions = parseClassifiers(packageData, regex)
if (versions.length === 0) {
throw new InvalidResponse({
prettyMessage: `${name} versions are missing for ${packageName}`,
})
}
return this.constructor.render({ name, versions })
}
}

View File

@@ -0,0 +1,164 @@
import Joi from 'joi'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
const isPipeSeparatedFrameworkVersions = Joi.string().regex(
/^([1-9]+(\.[0-9]+)?(?: \| )?)+$/
)
t.create('supported django versions (valid, package version in request)')
.get('/django/djangorestframework/3.7.3.json')
.expectBadge({
label: 'django versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported django versions (valid, no package version specified)')
.get('/django/djangorestframework.json')
.expectBadge({
label: 'django versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported django versions (no versions specified)')
.get('/django/django/1.11.json')
.expectBadge({
label: 'versions',
message: 'Django versions are missing for django/1.11',
})
t.create('supported django versions (invalid)')
.get('/django/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})
t.create('supported plone versions (valid, package version in request)')
.get('/plone/plone.rest/1.6.2.json')
.expectBadge({ label: 'plone versions', message: '4.3 | 5.0 | 5.1 | 5.2' })
t.create('supported plone versions (valid, no package version specified)')
.get('/plone/plone.rest.json')
.expectBadge({
label: 'plone versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported plone versions (invalid)')
.get('/plone/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})
t.create('supported zope versions (valid, package version in request)')
.get('/zope/plone/5.2.9.json')
.expectBadge({ label: 'zope versions', message: '4' })
t.create('supported zope versions (valid, no package version specified)')
.get('/zope/Plone.json')
.expectBadge({
label: 'zope versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported zope versions (invalid)')
.get('/zope/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})
t.create('supported wagtail versions (valid, package version in request)')
.get('/wagtail/wagtail-headless-preview/0.3.0.json')
.expectBadge({ label: 'wagtail versions', message: '2 | 3' })
t.create('supported wagtail versions (valid, no package version specified)')
.get('/wagtail/wagtail-headless-preview.json')
.expectBadge({
label: 'wagtail versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported wagtail versions (invalid)')
.get('/wagtail/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})
t.create('supported django cms versions (valid, package version in request)')
.get('/django-cms/djangocms-ads/1.1.0.json')
.expectBadge({
label: 'django cms versions',
message: '3.7 | 3.8 | 3.9 | 3.10',
})
t.create('supported django cms versions (valid, no package version specified)')
.get('/django-cms/djangocms-ads.json')
.expectBadge({
label: 'django cms versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported django cms versions (invalid)')
.get('/django-cms/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})
t.create('supported odoo versions (valid, package version in request)')
.get('/odoo/odoo-addon-sale-tier-validation/15.0.1.0.0.6.json')
.expectBadge({ label: 'odoo versions', message: '15.0' })
t.create('supported odoo versions (valid, no package version specified)')
.get('/odoo/odoo-addon-sale-tier-validation.json')
.expectBadge({
label: 'odoo versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported odoo versions (invalid)')
.get('/odoo/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})
t.create('supported aws cdk versions (valid, package version in request)')
.get('/aws-cdk/aws-cdk.aws-glue-alpha/2.34.0a0.json')
.expectBadge({ label: 'aws cdk versions', message: '2' })
t.create('supported aws cdk versions (valid, no package version specified)')
.get('/aws-cdk/aws-cdk.aws-glue-alpha.json')
.expectBadge({
label: 'aws cdk versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported aws cdk versions (invalid)')
.get('/aws-cdk/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})
t.create('supported jupyterlab versions (valid, package version in request)')
.get('/jupyterlab/structured-text/0.0.2.json')
.expectBadge({ label: 'jupyterlab versions', message: '3' })
t.create('supported jupyterlab versions (valid, no package version specified)')
.get('/jupyterlab/structured-text.json')
.expectBadge({
label: 'jupyterlab versions',
message: isPipeSeparatedFrameworkVersions,
})
t.create('supported jupyterlab versions (invalid)')
.get('/jupyterlab/not-a-package.json')
.expectBadge({
label: 'versions',
message: 'package or version not found',
})

View File

@@ -6,7 +6,7 @@
our own functions to parse and sort django versions
*/
function parseDjangoVersionString(str) {
function parsePypiVersionString(str) {
if (typeof str !== 'string') {
return false
}
@@ -20,18 +20,12 @@ function parseDjangoVersionString(str) {
}
// Sort an array of django versions low to high.
function sortDjangoVersions(versions) {
function sortPypiVersions(versions) {
return versions.sort((a, b) => {
if (
parseDjangoVersionString(a).major === parseDjangoVersionString(b).major
) {
return (
parseDjangoVersionString(a).minor - parseDjangoVersionString(b).minor
)
if (parsePypiVersionString(a).major === parsePypiVersionString(b).major) {
return parsePypiVersionString(a).minor - parsePypiVersionString(b).minor
} else {
return (
parseDjangoVersionString(a).major - parseDjangoVersionString(b).major
)
return parsePypiVersionString(a).major - parsePypiVersionString(b).major
}
})
}
@@ -101,8 +95,8 @@ function getPackageFormats(packageData) {
export {
parseClassifiers,
parseDjangoVersionString,
sortDjangoVersions,
parsePypiVersionString,
sortPypiVersions,
getLicenses,
getPackageFormats,
}

View File

@@ -1,8 +1,8 @@
import { test, given, forCases } from 'sazerac'
import {
parseClassifiers,
parseDjangoVersionString,
sortDjangoVersions,
parsePypiVersionString,
sortPypiVersions,
getLicenses,
getPackageFormats,
} from './pypi-helpers.js'
@@ -60,7 +60,7 @@ describe('PyPI helpers', function () {
given(classifiersFixture, /^(?!.*)*$/).expect([])
})
test(parseDjangoVersionString, function () {
test(parsePypiVersionString, function () {
given('1').expect({ major: 1, minor: 0 })
given('1.0').expect({ major: 1, minor: 0 })
given('7.2').expect({ major: 7, minor: 2 })
@@ -69,7 +69,7 @@ describe('PyPI helpers', function () {
given('foo').expect({ major: 0, minor: 0 })
})
test(sortDjangoVersions, function () {
test(sortPypiVersions, function () {
// Each of these includes a different variant: 2.0, 2, and 2.0rc1.
given(['2.0', '1.9', '10', '1.11', '2.1', '2.11']).expect([
'1.9',