diff --git a/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap index 7e3b20c69204e83cd1338d7338c1d244645612ba..8c05260849cf787dd78c3de42a18ad6bfbaca0c8 100644 --- a/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/pypi/__snapshots__/index.spec.ts.snap @@ -44,7 +44,7 @@ Object { exports[`modules/datasource/pypi/index getReleases parses data-requires-python and respects constraints from simple endpoint 1`] = ` Object { - "registryUrl": "https://pypi.org/simple", + "registryUrl": "https://some.registry.org/simple", "releases": Array [ Object { "version": "0.1.2", @@ -112,7 +112,7 @@ Object { exports[`modules/datasource/pypi/index getReleases process data from simple endpoint 1`] = ` Object { - "registryUrl": "https://pypi.org/simple", + "registryUrl": "https://some.registry.org/simple", "releases": Array [ Object { "version": "0.1.2", @@ -154,7 +154,7 @@ Object { exports[`modules/datasource/pypi/index getReleases process data from simple endpoint with hyphens replaced with underscores 1`] = ` Object { - "registryUrl": "https://pypi.org/simple", + "registryUrl": "https://some.registry.org/simple", "releases": Array [ Object { "version": "0.0.5", @@ -280,3 +280,101 @@ Object { ], } `; + +exports[`modules/datasource/pypi/index uses https://pypi.org/pypi/ instead of https://pypi.org/simple/ 1`] = ` +Object { + "registryUrl": "https://pypi.org/simple", + "releases": Array [ + Object { + "releaseTimestamp": "2017-04-03T16:55:14.000Z", + "version": "0.0.1", + }, + Object { + "releaseTimestamp": "2017-04-17T20:32:30.000Z", + "version": "0.0.2", + }, + Object { + "releaseTimestamp": "2017-04-28T21:18:54.000Z", + "version": "0.0.3", + }, + Object { + "releaseTimestamp": "2017-05-09T21:36:51.000Z", + "version": "0.0.4", + }, + Object { + "releaseTimestamp": "2017-05-30T23:13:49.000Z", + "version": "0.0.5", + }, + Object { + "releaseTimestamp": "2017-06-13T22:21:05.000Z", + "version": "0.0.6", + }, + Object { + "releaseTimestamp": "2017-06-21T22:12:36.000Z", + "version": "0.0.7", + }, + Object { + "releaseTimestamp": "2017-07-07T16:22:26.000Z", + "version": "0.0.8", + }, + Object { + "releaseTimestamp": "2017-08-28T20:14:33.000Z", + "version": "0.0.9", + }, + Object { + "releaseTimestamp": "2017-09-22T23:47:59.000Z", + "version": "0.0.10", + }, + Object { + "releaseTimestamp": "2017-10-24T02:14:07.000Z", + "version": "0.0.11", + }, + Object { + "releaseTimestamp": "2017-11-14T18:31:57.000Z", + "version": "0.0.12", + }, + Object { + "releaseTimestamp": "2017-12-05T18:57:54.000Z", + "version": "0.0.13", + }, + Object { + "releaseTimestamp": "2018-01-05T21:26:03.000Z", + "version": "0.0.14", + }, + Object { + "releaseTimestamp": "2018-01-17T18:36:39.000Z", + "version": "0.1.0", + }, + Object { + "releaseTimestamp": "2018-01-31T18:05:22.000Z", + "version": "0.1.1", + }, + Object { + "releaseTimestamp": "2018-02-13T18:17:52.000Z", + "version": "0.1.2", + }, + Object { + "releaseTimestamp": "2018-03-13T17:08:20.000Z", + "version": "0.1.3", + }, + Object { + "releaseTimestamp": "2018-03-27T17:55:25.000Z", + "version": "0.1.4", + }, + Object { + "releaseTimestamp": "2018-04-10T17:25:47.000Z", + "version": "0.1.5", + }, + Object { + "isDeprecated": true, + "releaseTimestamp": "2018-05-07T17:59:09.000Z", + "version": "0.1.6", + }, + Object { + "releaseTimestamp": "2018-05-22T17:25:23.000Z", + "version": "0.1.7", + }, + ], + "sourceUrl": "https://github.com/Azure/azure-cli", +} +`; diff --git a/lib/modules/datasource/pypi/common.ts b/lib/modules/datasource/pypi/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..50310011e54b8faa99ead54e626c5e26750ed332 --- /dev/null +++ b/lib/modules/datasource/pypi/common.ts @@ -0,0 +1,7 @@ +import { regEx } from '../../../util/regex'; + +const githubRepoPattern = regEx(/^https?:\/\/github\.com\/[^\\/]+\/[^\\/]+$/); + +export function isGitHubRepo(url: string): boolean { + return !url.includes('sponsors') && githubRepoPattern.test(url); +} diff --git a/lib/modules/datasource/pypi/index.spec.ts b/lib/modules/datasource/pypi/index.spec.ts index 619cd15cc7844f76d103d229a43afa84c4a89f34..90a1a252ef73588d9e2345b1633b18c3fc7d06b0 100644 --- a/lib/modules/datasource/pypi/index.spec.ts +++ b/lib/modules/datasource/pypi/index.spec.ts @@ -178,6 +178,25 @@ describe('modules/datasource/pypi/index', () => { expect(result?.changelogUrl).toBe(info.project_urls.changelog); }); + it('excludes gh sponsors url from project_urls', async () => { + const info = { + name: 'flexget', + home_page: 'https://flexget.com', + project_urls: { + random: 'https://github.com/sponsors/Flexget', + }, + }; + httpMock + .scope(baseUrl) + .get('/flexget/json') + .reply(200, { ...JSON.parse(res1), info }); + const result = await getPkgReleases({ + datasource, + depName: 'flexget', + }); + expect(result?.sourceUrl).toBeUndefined(); + }); + it('normalizes the package name according to PEP 503', async () => { const expectedHttpCall = httpMock .scope(baseUrl) @@ -213,7 +232,7 @@ describe('modules/datasource/pypi/index', () => { }); it('normalizes the package name according to PEP 503 querying a simple endpoint', async () => { - const simpleRegistryUrl = 'https://pypi.org/simple/'; + const simpleRegistryUrl = 'https://some.registry.org/simple/'; const expectedHttpCall = httpMock .scope(simpleRegistryUrl) .get('/not-normalized-package/') @@ -258,11 +277,11 @@ describe('modules/datasource/pypi/index', () => { it('process data from simple endpoint', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/dj-database-url/') .reply(200, htmlResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; expect( await getPkgReleases({ @@ -315,11 +334,11 @@ describe('modules/datasource/pypi/index', () => { it('process data from simple endpoint with hyphens', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/package-with-hyphens/') .reply(200, hyphensResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; const res = await getPkgReleases({ datasource, @@ -335,11 +354,11 @@ describe('modules/datasource/pypi/index', () => { it('process data from simple endpoint with hyphens replaced with underscores', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/image-collector/') .reply(200, mixedHyphensResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; expect( await getPkgReleases({ @@ -353,11 +372,11 @@ describe('modules/datasource/pypi/index', () => { it('process data from simple endpoint with mixed-case characters', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/packagewithmixedcase/') .reply(200, mixedCaseResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; const res = await getPkgReleases({ datasource, @@ -373,11 +392,11 @@ describe('modules/datasource/pypi/index', () => { it('process data from simple endpoint with mixed-case characters when using lower case dependency name', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/packagewithmixedcase/') .reply(200, mixedCaseResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; const res = await getPkgReleases({ datasource, @@ -393,11 +412,11 @@ describe('modules/datasource/pypi/index', () => { it('process data from simple endpoint with periods', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/package-with-periods/') .reply(200, withPeriodsResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; const res = await getPkgReleases({ datasource, @@ -413,11 +432,11 @@ describe('modules/datasource/pypi/index', () => { it('returns null for empty response', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/dj-database-url/') .reply(200); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; expect( await getPkgReleases({ @@ -431,11 +450,11 @@ describe('modules/datasource/pypi/index', () => { it('returns null for 404 response from simple endpoint', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/dj-database-url/') .replyWithError('error'); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; expect( await getPkgReleases({ @@ -449,11 +468,11 @@ describe('modules/datasource/pypi/index', () => { it('returns null for response with no versions', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/dj-database-url/') .reply(200, badResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; expect( await getPkgReleases({ @@ -487,11 +506,11 @@ describe('modules/datasource/pypi/index', () => { it('parses data-requires-python and respects constraints from simple endpoint', async () => { httpMock - .scope('https://pypi.org/simple/') + .scope('https://some.registry.org/simple/') .get('/dj-database-url/') .reply(200, dataRequiresPythonResponse); const config = { - registryUrls: ['https://pypi.org/simple/'], + registryUrls: ['https://some.registry.org/simple/'], }; expect( await getPkgReleases({ @@ -503,4 +522,19 @@ describe('modules/datasource/pypi/index', () => { ).toMatchSnapshot(); }); }); + + it('uses https://pypi.org/pypi/ instead of https://pypi.org/simple/', async () => { + httpMock.scope(baseUrl).get('/azure-cli-monitor/json').reply(200, res1); + const config = { + registryUrls: ['https://pypi.org/simple/'], + }; + expect( + await getPkgReleases({ + datasource, + ...config, + constraints: { python: '2.7' }, + depName: 'azure-cli-monitor', + }) + ).toMatchSnapshot(); + }); }); diff --git a/lib/modules/datasource/pypi/index.ts b/lib/modules/datasource/pypi/index.ts index dae80c60cd99ccca68dc6ae131b01de5d926be87..e3985ecab9e1bea595feefaf2b98911a8159a9b2 100644 --- a/lib/modules/datasource/pypi/index.ts +++ b/lib/modules/datasource/pypi/index.ts @@ -7,10 +7,9 @@ import { ensureTrailingSlash } from '../../../util/url'; import * as pep440 from '../../versioning/pep440'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; +import { isGitHubRepo } from './common'; import type { PypiJSON, PypiJSONRelease, Releases } from './types'; -const githubRepoPattern = regEx(/^https?:\/\/github\.com\/[^\\/]+\/[^\\/]+$/); - export class PypiDatasource extends Datasource { static readonly id = 'pypi'; @@ -35,7 +34,10 @@ export class PypiDatasource extends Datasource { registryUrl, }: GetReleasesConfig): Promise<ReleaseResult | null> { let dependency: ReleaseResult | null = null; - const hostUrl = ensureTrailingSlash(`${registryUrl}`); + // TODO: null check (#7154) + const hostUrl = ensureTrailingSlash( + registryUrl!.replace('https://pypi.org/simple', 'https://pypi.org/pypi') + ); const normalizedLookupName = PypiDatasource.normalizeName(packageName); // not all simple indexes use this identifier, but most do @@ -103,7 +105,7 @@ export class PypiDatasource extends Datasource { if (dep.info?.home_page) { dependency.homepage = dep.info.home_page; - if (githubRepoPattern.exec(dep.info.home_page)) { + if (isGitHubRepo(dep.info.home_page)) { dependency.sourceUrl = dep.info.home_page.replace( 'http://', 'https://' @@ -120,7 +122,7 @@ export class PypiDatasource extends Datasource { (lower.startsWith('repo') || lower === 'code' || lower === 'source' || - githubRepoPattern.exec(projectUrl)) + isGitHubRepo(projectUrl)) ) { dependency.sourceUrl = projectUrl; }