diff --git a/lib/manager/pip_requirements/extract.js b/lib/manager/pip_requirements/extract.js index 69af4aee3592c9eb90f3c794127b1c9c6714c688..ef5cc7489126c720347d2de5e221ae0bd23e54f0 100644 --- a/lib/manager/pip_requirements/extract.js +++ b/lib/manager/pip_requirements/extract.js @@ -1,5 +1,7 @@ +const XRegExp = require('xregexp'); // based on https://www.python.org/dev/peps/pep-0508/#names -const packagePattern = '([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])'; +const packagePattern = '[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]'; +const versionPattern = require('@renovate/pep440/lib/version').VERSION_PATTERN; module.exports = { packagePattern, @@ -8,13 +10,9 @@ module.exports = { function extractDependencies(content) { logger.debug('pip_requirements.extractDependencies()'); - // TODO: for now we only support semver, but we need better support for python versions - // see https://github.com/pypa/packaging/blob/master/packaging/version.py - // see https://www.python.org/dev/peps/pep-0440/#version-epochs - const regex = new RegExp( - `^${packagePattern}==([0-9]+\\.[0-9]+\\.[0-9]+)$`, - 'g' - ); + // TODO: for now we only support package==version + // future support for more complex ranges could be added later + const regex = new XRegExp(`^(${packagePattern})(==${versionPattern})$`, 'g'); const deps = content .split('\n') .map((line, lineNumber) => { @@ -28,6 +26,7 @@ function extractDependencies(content) { depName, currentValue, lineNumber, + purl: 'pkg:pypi/' + depName, versionScheme: 'pep440', }; }) diff --git a/lib/manager/pip_requirements/index.js b/lib/manager/pip_requirements/index.js index 9d4ee6fddc77256123b28542f4eff5c8f0068830..f46810c1bd768db7118623684f0535c656d01afc 100644 --- a/lib/manager/pip_requirements/index.js +++ b/lib/manager/pip_requirements/index.js @@ -1,5 +1,4 @@ const { packagePattern, extractDependencies } = require('./extract'); -const { getPackageUpdates } = require('./package'); const { updateDependency } = require('./update'); const contentPattern = new RegExp(`^${packagePattern}==`); @@ -8,7 +7,6 @@ const language = 'python'; module.exports = { contentPattern, extractDependencies, - getPackageUpdates, language, updateDependency, }; diff --git a/lib/manager/pip_requirements/package.js b/lib/manager/pip_requirements/package.js deleted file mode 100644 index 5f2d7350000274b56aa136c1e0b4ab193989a633..0000000000000000000000000000000000000000 --- a/lib/manager/pip_requirements/package.js +++ /dev/null @@ -1,50 +0,0 @@ -const got = require('got'); -const { - isGreaterThan, - sortVersions, - isVersion, - getMajor, -} = require('../../versioning/semver'); - -module.exports = { - getPackageUpdates, -}; - -async function getPackageUpdates(config) { - try { - logger.debug('pip_requirements.getPackageUpdates()'); - const { currentValue, depName } = config; - if (!isVersion(currentValue)) { - return []; - } - const { releases } = (await got(`https://pypi.org/pypi/${depName}/json`, { - json: true, - })).body; - const newVersions = Object.keys(releases) - .filter( - release => isVersion(release) && isGreaterThan(release, currentValue) - ) - .sort(sortVersions); - - if (newVersions.length) { - logger.info({ newVersions, depName }, 'Found newer Python releases'); - } else { - return []; - } - - const newValue = newVersions.pop(); - - return [ - { - depName, - newValue, - newMajor: getMajor(newValue), - fromVersion: currentValue, - toVersion: newValue, - }, - ]; - } catch (err) { - logger.info({ err }, 'Error fetching new package versions'); - return []; - } -} diff --git a/lib/manager/pip_requirements/update.js b/lib/manager/pip_requirements/update.js index 546a9d333adb14a06d18cabaf040f1488916c198..99360c10029f14421d825c6a85ff5bc5b775fa85 100644 --- a/lib/manager/pip_requirements/update.js +++ b/lib/manager/pip_requirements/update.js @@ -6,7 +6,7 @@ function updateDependency(fileContent, upgrade) { try { logger.debug(`pip_requirements.updateDependency(): ${upgrade.newValue}`); const lines = fileContent.split('\n'); - lines[upgrade.lineNumber] = `${upgrade.depName}==${upgrade.newValue}`; + lines[upgrade.lineNumber] = `${upgrade.depName}${upgrade.newValue}`; return lines.join('\n'); } catch (err) { logger.info({ err }, 'Error setting new package version'); diff --git a/lib/versioning/pep440/range.js b/lib/versioning/pep440/range.js index e4a9e21a6741137d82b352595bd9677ed267cb70..e2450b6c30b671d86a48f7047d79b06b60b0f383 100644 --- a/lib/versioning/pep440/range.js +++ b/lib/versioning/pep440/range.js @@ -3,11 +3,9 @@ module.exports = { }; function getNewValue(config, fromVersion, toVersion) { - const { rangeStrategy } = config; - // istanbul ignore if - if (rangeStrategy !== 'pin') { - logger.warn({ rangeStrategy }, 'Unsupported rangeStrategy'); - return null; + if (config.currentValue.startsWith('==')) { + return '==' + toVersion; } + logger.warn('Unsupported currentValue: ' + config.currentValue); return toVersion; } diff --git a/lib/workers/repository/process/lookup/index.js b/lib/workers/repository/process/lookup/index.js index af7d88d8b4d4fdbc658eeda35f5f12a36f03679d..bfd82fcd8edfe4668d65f3cadc71b6af8cfbf6f7 100644 --- a/lib/workers/repository/process/lookup/index.js +++ b/lib/workers/repository/process/lookup/index.js @@ -5,6 +5,7 @@ const { getRangeStrategy } = require('../../../../manager'); const { filterVersions } = require('./filter'); const npmApi = require('../../../../datasource/npm'); const github = require('../../../../datasource/github'); +const pypi = require('../../../../datasource/pypi'); const { parse } = require('../../../../../lib/util/purl'); module.exports = { @@ -38,6 +39,8 @@ async function lookupUpdates(config) { dependency = await npmApi.getDependency(purl.fullname); } else if (purl.type === 'github') { dependency = await github.getDependency(purl.fullname, purl.qualifiers); + } else if (purl.type === 'pypi') { + dependency = await pypi.getDependency(purl.fullname); } else { logger.warn({ config }, 'Unknown purl'); return []; diff --git a/test/_fixtures/pip_requirements/requirements.txt b/test/_fixtures/pip_requirements/requirements1.txt similarity index 100% rename from test/_fixtures/pip_requirements/requirements.txt rename to test/_fixtures/pip_requirements/requirements1.txt diff --git a/test/_fixtures/pip_requirements/requirements2.txt b/test/_fixtures/pip_requirements/requirements2.txt new file mode 100644 index 0000000000000000000000000000000000000000..aa058d733e99d2c7a03b26733abd2a19052f3f9c --- /dev/null +++ b/test/_fixtures/pip_requirements/requirements2.txt @@ -0,0 +1,5 @@ +Django==1 +distribute==0.6.27 +dj-database-url==0.2 +psycopg2==2.4.5 +wsgiref==0.1.2 diff --git a/test/manager/pip_requirements/__snapshots__/extract.spec.js.snap b/test/manager/pip_requirements/__snapshots__/extract.spec.js.snap index 8c8e233418f4e2e3d468153b7f21a065d6327eb2..76806148973d207b39e3bf34e9552b0d061417f4 100644 --- a/test/manager/pip_requirements/__snapshots__/extract.spec.js.snap +++ b/test/manager/pip_requirements/__snapshots__/extract.spec.js.snap @@ -3,15 +3,64 @@ exports[`lib/manager/pip_requirements/extract extractDependencies() extracts dependencies 1`] = ` Array [ Object { - "currentValue": "0.3.1", + "currentValue": "==0.3.1", "depName": "some-package", "lineNumber": 2, + "purl": "pkg:pypi/some-package", "versionScheme": "pep440", }, Object { - "currentValue": "1.0.0", + "currentValue": "==1.0.0", "depName": "some-other-package", "lineNumber": 3, + "purl": "pkg:pypi/some-other-package", + "versionScheme": "pep440", + }, + Object { + "currentValue": "==1.9", + "depName": "not_semver", + "lineNumber": 4, + "purl": "pkg:pypi/not_semver", + "versionScheme": "pep440", + }, +] +`; + +exports[`lib/manager/pip_requirements/extract extractDependencies() extracts multiple dependencies 1`] = ` +Array [ + Object { + "currentValue": "==1", + "depName": "Django", + "lineNumber": 0, + "purl": "pkg:pypi/Django", + "versionScheme": "pep440", + }, + Object { + "currentValue": "==0.6.27", + "depName": "distribute", + "lineNumber": 1, + "purl": "pkg:pypi/distribute", + "versionScheme": "pep440", + }, + Object { + "currentValue": "==0.2", + "depName": "dj-database-url", + "lineNumber": 2, + "purl": "pkg:pypi/dj-database-url", + "versionScheme": "pep440", + }, + Object { + "currentValue": "==2.4.5", + "depName": "psycopg2", + "lineNumber": 3, + "purl": "pkg:pypi/psycopg2", + "versionScheme": "pep440", + }, + Object { + "currentValue": "==0.1.2", + "depName": "wsgiref", + "lineNumber": 4, + "purl": "pkg:pypi/wsgiref", "versionScheme": "pep440", }, ] diff --git a/test/manager/pip_requirements/__snapshots__/package.spec.js.snap b/test/manager/pip_requirements/__snapshots__/package.spec.js.snap deleted file mode 100644 index 37f3a250c98baa5a30ab0cba8d397413f0fcc6f8..0000000000000000000000000000000000000000 --- a/test/manager/pip_requirements/__snapshots__/package.spec.js.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`lib/manager/pip_requirements/package getPackageUpdates() returns one upgrade 1`] = ` -Array [ - Object { - "depName": "some-package", - "fromVersion": "0.3.2", - "newMajor": 1, - "newValue": "1.0.2", - "toVersion": "1.0.2", - }, -] -`; diff --git a/test/manager/pip_requirements/extract.spec.js b/test/manager/pip_requirements/extract.spec.js index 9e40b56bd75f55b0f115cb96921b306c78cb5154..1f4a4b83848a2923ca3e6e8138d8e34175c4a87d 100644 --- a/test/manager/pip_requirements/extract.spec.js +++ b/test/manager/pip_requirements/extract.spec.js @@ -3,8 +3,12 @@ const { extractDependencies, } = require('../../../lib/manager/pip_requirements/extract'); -const requirements = fs.readFileSync( - 'test/_fixtures/pip_requirements/requirements.txt', +const requirements1 = fs.readFileSync( + 'test/_fixtures/pip_requirements/requirements1.txt', + 'utf8' +); +const requirements2 = fs.readFileSync( + 'test/_fixtures/pip_requirements/requirements2.txt', 'utf8' ); @@ -18,9 +22,14 @@ describe('lib/manager/pip_requirements/extract', () => { expect(extractDependencies('nothing here', config)).toBe(null); }); it('extracts dependencies', () => { - const res = extractDependencies(requirements, config).deps; + const res = extractDependencies(requirements1, config).deps; + expect(res).toMatchSnapshot(); + expect(res).toHaveLength(3); + }); + it('extracts multiple dependencies', () => { + const res = extractDependencies(requirements2, config).deps; expect(res).toMatchSnapshot(); - expect(res).toHaveLength(2); + expect(res).toHaveLength(5); }); }); }); diff --git a/test/manager/pip_requirements/package.spec.js b/test/manager/pip_requirements/package.spec.js deleted file mode 100644 index 794f69c527b068117b500b2685d4735317cc0d23..0000000000000000000000000000000000000000 --- a/test/manager/pip_requirements/package.spec.js +++ /dev/null @@ -1,59 +0,0 @@ -const { - getPackageUpdates, -} = require('../../../lib/manager/pip_requirements/package'); - -const defaultConfig = require('../../../lib/config/defaults').getConfig(); -const got = require('got'); - -jest.mock('got'); - -describe('lib/manager/pip_requirements/package', () => { - describe('getPackageUpdates()', () => { - let config; - beforeEach(() => { - config = { - ...defaultConfig, - }; - }); - it('returns empty on error', async () => { - config.depName = 'some-package'; - config.currentValue = '1.0.0'; - expect(await getPackageUpdates(config)).toEqual([]); - }); - it('returns empty if current version is not semver', async () => { - config.depName = 'some-package'; - config.currentValue = 'abcdefg'; - expect(await getPackageUpdates(config)).toEqual([]); - }); - it('returns empty if no newer versions', async () => { - config.depName = 'some-package'; - config.currentValue = '0.3.2'; - got.mockReturnValueOnce({ - body: { - releases: { - '0.2.0': [], - '0.3.2': [], - }, - }, - }); - expect(await getPackageUpdates(config)).toEqual([]); - }); - it('returns one upgrade', async () => { - config.depName = 'some-package'; - config.currentValue = '0.3.2'; - got.mockReturnValueOnce({ - body: { - releases: { - '0.2': [], - '0.2.0': [], - '0.3.2': [], - '0.3.4': [], - '1.0.0': [], - '1.0.2': [], - }, - }, - }); - expect(await getPackageUpdates(config)).toMatchSnapshot(); - }); - }); -}); diff --git a/test/manager/pip_requirements/update.spec.js b/test/manager/pip_requirements/update.spec.js index 6dada4f6bf529b7f558c52ff1d96a270a8ec93bf..086dd07b1a87086a2a36eb4477dd41eac176f113 100644 --- a/test/manager/pip_requirements/update.spec.js +++ b/test/manager/pip_requirements/update.spec.js @@ -4,7 +4,7 @@ const { } = require('../../../lib/manager/pip_requirements/update'); const requirements = fs.readFileSync( - 'test/_fixtures/pip_requirements/requirements.txt', + 'test/_fixtures/pip_requirements/requirements1.txt', 'utf8' );