[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:
@@ -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'),
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
103
services/pypi/pypi-framework-versions.service.js
Normal file
103
services/pypi/pypi-framework-versions.service.js
Normal 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 })
|
||||
}
|
||||
}
|
||||
164
services/pypi/pypi-framework-versions.tester.js
Normal file
164
services/pypi/pypi-framework-versions.tester.js
Normal 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',
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user