diff --git a/lib/config/definitions.js b/lib/config/definitions.js index 2b5a534aae4c5425bd95a32aba1e8fafca41e168..5e667f1155cfd5b47beea3cf3aaeb167666c0623 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -1180,6 +1180,15 @@ const options = [ mergeable: true, cli: false, }, + { + name: 'compatibility', + description: 'Configuration object for compatibility', + stage: 'package', + type: 'object', + default: {}, + mergeable: true, + cli: false, + }, { name: 'java', description: 'Configuration object for all Java package managers', diff --git a/lib/datasource/pypi.js b/lib/datasource/pypi.js index 9730c986248972f9fbca771bb0668120ca1e0a77..cd8408e0e3510a387c5ed2795564fcfcc31d00d1 100644 --- a/lib/datasource/pypi.js +++ b/lib/datasource/pypi.js @@ -1,7 +1,7 @@ const got = require('got'); const url = require('url'); const is = require('@sindresorhus/is'); -const { isVersion, sortVersions } = require('../versioning')('pep440'); +const { isVersion, sortVersions, matches } = require('../versioning')('pep440'); module.exports = { getPkgReleases, @@ -22,7 +22,25 @@ const changelogUrls = { flake8: 'http://flake8.pycqa.org/en/latest/release-notes/index.html', }; +function compatibleVersions(releases, compatibility) { + const versions = Object.keys(releases) + .filter(isVersion) + .sort(sortVersions); + if (!(compatibility && compatibility.python)) { + return versions; + } + return versions.filter(version => + releases[version].some(release => { + if (!release.requires_python) { + return true; + } + return matches(compatibility.python, release.requires_python); + }) + ); +} + async function getPkgReleases(purl, config = {}) { + const { compatibility } = config; const { fullname: depName } = purl; let hostUrl = 'https://pypi.org/pypi/'; if (is.nonEmptyArray(config.registryUrls)) { @@ -69,9 +87,7 @@ async function getPkgReleases(purl, config = {}) { dependency.repositoryUrl || manualRepositories[depName.toLowerCase()]; dependency.releases = []; if (dep.releases) { - const versions = Object.keys(dep.releases) - .filter(isVersion) - .sort(sortVersions); + const versions = compatibleVersions(dep.releases, compatibility); dependency.releases = versions.map(version => ({ version, releaseTimestamp: (dep.releases[version][0] || {}).upload_time, diff --git a/test/datasource/__snapshots__/pypi.spec.js.snap b/test/datasource/__snapshots__/pypi.spec.js.snap index 807d9074bfc82ca3eebdbc690d1605a561ac0499..40199dafa397309d3405fb3bb1f498ad29337c12 100644 --- a/test/datasource/__snapshots__/pypi.spec.js.snap +++ b/test/datasource/__snapshots__/pypi.spec.js.snap @@ -96,6 +96,26 @@ Object { } `; +exports[`datasource/pypi getPkgReleases respects compatibility 1`] = ` +Object { + "releases": Array [ + Object { + "releaseTimestamp": undefined, + "version": "0.4.0", + }, + Object { + "releaseTimestamp": undefined, + "version": "0.30.3", + }, + Object { + "releaseTimestamp": undefined, + "version": "0.31.0", + }, + ], + "repositoryUrl": undefined, +} +`; + exports[`datasource/pypi getPkgReleases returns non-github home_page 1`] = ` Object { "homepage": "https://microsoft.com", diff --git a/test/datasource/pypi.spec.js b/test/datasource/pypi.spec.js index 8b400ce838472f228852111168fb0d0e2f8d5ba5..f9183d4fd064029d6fd004d2cd859949fb82a84f 100644 --- a/test/datasource/pypi.spec.js +++ b/test/datasource/pypi.spec.js @@ -73,5 +73,29 @@ describe('datasource/pypi', () => { }); expect(await datasource.getPkgReleases('pkg:pypi/something')).toBeNull(); }); + + it('respects compatibility', async () => { + got.mockReturnValueOnce({ + body: { + info: { + name: 'doit', + }, + releases: { + '0.30.3': [{ requires_python: null }], + '0.31.0': [ + { requires_python: '>=3.4' }, + { requires_python: '>=2.7' }, + ], + '0.31.1': [{ requires_python: '>=3.4' }], + '0.4.0': [{ requires_python: '>=3.4' }, { requires_python: null }], + }, + }, + }); + expect( + await datasource.getPkgReleases('pkg:pypi/doit', { + compatibility: { python: '2.7' }, + }) + ).toMatchSnapshot(); + }); }); }); diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index 502506d6ac967232727d6664be5f419b4de3b9db..80ba2cfc5e6681d1b72d0908041512efd021826b 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -130,6 +130,17 @@ This is used to add a suffix to commit messages. Usually left empty except for i This is used to alter `commitMessage` and `prTitle` without needing to copy/paste the whole string. The "topic" is usually refers to the dependency being updated, e.g. "dependency react". +## compatibility + +This is used to restrict which versions are possible to upgrade to based on their language support. +For now this only support `python`, other languages would be added in the future. + +```json +"compatibility": { + "python": "2.7" +} +``` + ## composer Warning: composer support is in alpha stage so you probably only want to run this if you are helping get it feature-ready.