diff --git a/services/pypi/pypi-django-versions.service.js b/services/pypi/pypi-django-versions.service.js
index ffe0fbf1bf..da07cf8f0c 100644
--- a/services/pypi/pypi-django-versions.service.js
+++ b/services/pypi/pypi-django-versions.service.js
@@ -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'),
+})
diff --git a/services/pypi/pypi-django-versions.tester.js b/services/pypi/pypi-django-versions.tester.js
index 83fdca15ec..adba0a5e2b 100644
--- a/services/pypi/pypi-django-versions.tester.js
+++ b/services/pypi/pypi-django-versions.tester.js
@@ -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')
diff --git a/services/pypi/pypi-framework-versions.service.js b/services/pypi/pypi-framework-versions.service.js
new file mode 100644
index 0000000000..1da6edb1e8
--- /dev/null
+++ b/services/pypi/pypi-framework-versions.service.js
@@ -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 = `
+
+ This service currently support the following Frameworks:
+ ${Object.values(frameworkNameMap).map(obj => `${obj.name}`)}
+
+`
+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 })
+ }
+}
diff --git a/services/pypi/pypi-framework-versions.tester.js b/services/pypi/pypi-framework-versions.tester.js
new file mode 100644
index 0000000000..89d2f9821b
--- /dev/null
+++ b/services/pypi/pypi-framework-versions.tester.js
@@ -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',
+ })
diff --git a/services/pypi/pypi-helpers.js b/services/pypi/pypi-helpers.js
index 0e16534d8b..706955b4f9 100644
--- a/services/pypi/pypi-helpers.js
+++ b/services/pypi/pypi-helpers.js
@@ -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,
}
diff --git a/services/pypi/pypi-helpers.spec.js b/services/pypi/pypi-helpers.spec.js
index d5a54dd5f5..17e42b3b4f 100644
--- a/services/pypi/pypi-helpers.spec.js
+++ b/services/pypi/pypi-helpers.spec.js
@@ -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',