bring back the [PyPI] downloads badges (#2131)
This commit is contained in:
@@ -1,12 +1,100 @@
|
||||
'use strict'
|
||||
|
||||
const deprecatedService = require('../deprecated-service')
|
||||
const PypiBase = require('./pypi-base')
|
||||
const Joi = require('joi')
|
||||
const BaseJsonService = require('../base-json')
|
||||
const { downloadCount } = require('../../lib/color-formatters')
|
||||
const { metric } = require('../../lib/text-formatters')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
// https://github.com/badges/shields/issues/716
|
||||
module.exports = ['pypi/dm', 'pypi/dw', 'pypi/dd'].map(base =>
|
||||
deprecatedService({
|
||||
category: 'downloads',
|
||||
url: PypiBase.buildUrl(base),
|
||||
})
|
||||
)
|
||||
const pypiStatsSchema = Joi.object({
|
||||
data: Joi.object({
|
||||
last_day: nonNegativeInteger,
|
||||
last_week: nonNegativeInteger,
|
||||
last_month: nonNegativeInteger,
|
||||
}),
|
||||
}).required()
|
||||
|
||||
const periodMap = {
|
||||
dd: {
|
||||
api_field: 'last_day',
|
||||
suffix: '/day',
|
||||
},
|
||||
dw: {
|
||||
api_field: 'last_week',
|
||||
suffix: '/week',
|
||||
},
|
||||
dm: {
|
||||
api_field: 'last_month',
|
||||
suffix: '/month',
|
||||
},
|
||||
}
|
||||
|
||||
// this badge uses PyPI Stats instead of the PyPI API
|
||||
// so it doesn't extend PypiBase
|
||||
module.exports = class PypiDownloads extends BaseJsonService {
|
||||
async fetch({ pkg }) {
|
||||
const url = `https://pypistats.org/api/packages/${pkg.toLowerCase()}/recent`
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema: pypiStatsSchema,
|
||||
errorMessages: { 404: 'package not found' },
|
||||
})
|
||||
}
|
||||
|
||||
static render({ period, downloads }) {
|
||||
return {
|
||||
message: `${metric(downloads)}${periodMap[period].suffix}`,
|
||||
color: downloadCount(downloads),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ period, pkg }) {
|
||||
const json = await this.fetch({ pkg })
|
||||
return this.constructor.render({
|
||||
period,
|
||||
downloads: json.data[periodMap[period].api_field],
|
||||
})
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'downloads' }
|
||||
}
|
||||
|
||||
static get category() {
|
||||
return 'downloads'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return {
|
||||
base: 'pypi',
|
||||
format: '(dd|dw|dm)/(.+)',
|
||||
capture: ['period', 'pkg'],
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - Downloads',
|
||||
exampleUrl: 'dd/Django',
|
||||
urlPattern: 'dd/:package',
|
||||
staticExample: this.render({ period: 'dd', downloads: 14000 }),
|
||||
keywords: ['python'],
|
||||
},
|
||||
{
|
||||
title: 'PyPI - Downloads',
|
||||
exampleUrl: 'dw/Django',
|
||||
urlPattern: 'dw/:package',
|
||||
staticExample: this.render({ period: 'dw', downloads: 250000 }),
|
||||
keywords: ['python'],
|
||||
},
|
||||
{
|
||||
title: 'PyPI - Downloads',
|
||||
exampleUrl: 'dm/Django',
|
||||
urlPattern: 'dm/:package',
|
||||
staticExample: this.render({ period: 'dm', downloads: 1070100 }),
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const Joi = require('joi')
|
||||
const ServiceTester = require('../service-tester')
|
||||
const { isSemver } = require('../test-validators')
|
||||
const { isMetricOverTimePeriod, isSemver } = require('../test-validators')
|
||||
|
||||
const isPsycopg2Version = Joi.string().regex(/^v([0-9][.]?)+$/)
|
||||
|
||||
@@ -15,37 +15,35 @@ const isPipeSeparatedDjangoVersions = isPipeSeparatedPythonVersions
|
||||
const t = new ServiceTester({ id: 'pypi', title: 'PyPi badges' })
|
||||
module.exports = t
|
||||
|
||||
/*
|
||||
tests for downloads endpoints
|
||||
// tests for downloads endpoints
|
||||
|
||||
Note:
|
||||
Download statistics are no longer available from pypi
|
||||
it is exptected that the download badges all show
|
||||
'no longer available'
|
||||
*/
|
||||
t.create('daily downloads (expected failure)')
|
||||
t.create('daily downloads (valid)')
|
||||
.get('/dd/djangorestframework.json')
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
.expectJSONTypes({ name: 'downloads', value: isMetricOverTimePeriod })
|
||||
|
||||
t.create('weekly downloads (expected failure)')
|
||||
t.create('weekly downloads (valid)')
|
||||
.get('/dw/djangorestframework.json')
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
.expectJSONTypes({ name: 'downloads', value: isMetricOverTimePeriod })
|
||||
|
||||
t.create('monthly downloads (expected failure)')
|
||||
t.create('monthly downloads (valid)')
|
||||
.get('/dm/djangorestframework.json')
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
.expectJSONTypes({ name: 'downloads', value: isMetricOverTimePeriod })
|
||||
|
||||
t.create('daily downloads (invalid)')
|
||||
t.create('downloads (mixed-case package name)')
|
||||
.get('/dd/DjangoRestFramework.json')
|
||||
.expectJSONTypes({ name: 'downloads', value: isMetricOverTimePeriod })
|
||||
|
||||
t.create('daily downloads (not found)')
|
||||
.get('/dd/not-a-package.json')
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
.expectJSON({ name: 'downloads', value: 'package not found' })
|
||||
|
||||
t.create('weekly downloads (invalid)')
|
||||
t.create('weekly downloads (not found)')
|
||||
.get('/dw/not-a-package.json')
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
.expectJSON({ name: 'downloads', value: 'package not found' })
|
||||
|
||||
t.create('monthly downloads (invalid)')
|
||||
t.create('monthly downloads (not found)')
|
||||
.get('/dm/not-a-package.json')
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
.expectJSON({ name: 'downloads', value: 'package not found' })
|
||||
|
||||
/*
|
||||
tests for version endpoint
|
||||
|
||||
Reference in New Issue
Block a user