mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 14:36:25 +00:00
feat(manager): add inline script metadata (PEP 723) support (#31266)
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
This commit is contained in:
parent
959e493c32
commit
dcaf51c9f7
6 changed files with 197 additions and 0 deletions
|
@ -71,6 +71,7 @@ import * as nvm from './nvm';
|
||||||
import * as ocb from './ocb';
|
import * as ocb from './ocb';
|
||||||
import * as osgi from './osgi';
|
import * as osgi from './osgi';
|
||||||
import * as pep621 from './pep621';
|
import * as pep621 from './pep621';
|
||||||
|
import * as pep723 from './pep723';
|
||||||
import * as pipCompile from './pip-compile';
|
import * as pipCompile from './pip-compile';
|
||||||
import * as pip_requirements from './pip_requirements';
|
import * as pip_requirements from './pip_requirements';
|
||||||
import * as pip_setup from './pip_setup';
|
import * as pip_setup from './pip_setup';
|
||||||
|
@ -174,6 +175,7 @@ api.set('nvm', nvm);
|
||||||
api.set('ocb', ocb);
|
api.set('ocb', ocb);
|
||||||
api.set('osgi', osgi);
|
api.set('osgi', osgi);
|
||||||
api.set('pep621', pep621);
|
api.set('pep621', pep621);
|
||||||
|
api.set('pep723', pep723);
|
||||||
api.set('pip-compile', pipCompile);
|
api.set('pip-compile', pipCompile);
|
||||||
api.set('pip_requirements', pip_requirements);
|
api.set('pip_requirements', pip_requirements);
|
||||||
api.set('pip_setup', pip_setup);
|
api.set('pip_setup', pip_setup);
|
||||||
|
|
113
lib/modules/manager/pep723/extract.spec.ts
Normal file
113
lib/modules/manager/pep723/extract.spec.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { codeBlock } from 'common-tags';
|
||||||
|
import { extractPackageFile } from '.';
|
||||||
|
|
||||||
|
describe('modules/manager/pep723/extract', () => {
|
||||||
|
describe('extractPackageFile()', () => {
|
||||||
|
it('should extract dependencies', () => {
|
||||||
|
const res = extractPackageFile(
|
||||||
|
codeBlock`
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "requests==2.32.3",
|
||||||
|
# "rich>=13.8.0",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
`,
|
||||||
|
'foo.py',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res).toEqual({
|
||||||
|
deps: [
|
||||||
|
{
|
||||||
|
currentValue: '==2.32.3',
|
||||||
|
currentVersion: '2.32.3',
|
||||||
|
datasource: 'pypi',
|
||||||
|
depName: 'requests',
|
||||||
|
depType: 'project.dependencies',
|
||||||
|
packageName: 'requests',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currentValue: '>=13.8.0',
|
||||||
|
datasource: 'pypi',
|
||||||
|
depName: 'rich',
|
||||||
|
depType: 'project.dependencies',
|
||||||
|
packageName: 'rich',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractedConstraints: { python: '>=3.11' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip invalid dependencies', () => {
|
||||||
|
const res = extractPackageFile(
|
||||||
|
codeBlock`
|
||||||
|
# /// script
|
||||||
|
# requires-python = "==3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "requests==2.32.3",
|
||||||
|
# "==1.2.3",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
`,
|
||||||
|
'foo.py',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res).toEqual({
|
||||||
|
deps: [
|
||||||
|
{
|
||||||
|
currentValue: '==2.32.3',
|
||||||
|
currentVersion: '2.32.3',
|
||||||
|
datasource: 'pypi',
|
||||||
|
depName: 'requests',
|
||||||
|
depType: 'project.dependencies',
|
||||||
|
packageName: 'requests',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extractedConstraints: { python: '==3.11' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null on missing dependencies', () => {
|
||||||
|
const res = extractPackageFile(
|
||||||
|
codeBlock`
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# ///
|
||||||
|
`,
|
||||||
|
'foo.py',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null on invalid TOML', () => {
|
||||||
|
const res = extractPackageFile(
|
||||||
|
codeBlock`
|
||||||
|
# /// script
|
||||||
|
# requires-python
|
||||||
|
# dependencies = [
|
||||||
|
# "requests==2.32.3",
|
||||||
|
# "rich>=13.8.0",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
`,
|
||||||
|
'foo.py',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null if there is no PEP 723 metadata', () => {
|
||||||
|
const res = extractPackageFile(
|
||||||
|
codeBlock`
|
||||||
|
if True:
|
||||||
|
print("requires-python>=3.11")
|
||||||
|
`,
|
||||||
|
'foo.py',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
40
lib/modules/manager/pep723/extract.ts
Normal file
40
lib/modules/manager/pep723/extract.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { logger } from '../../../logger';
|
||||||
|
import { newlineRegex, regEx } from '../../../util/regex';
|
||||||
|
import type { PackageFileContent } from '../types';
|
||||||
|
import { Pep723Schema } from './schema';
|
||||||
|
|
||||||
|
// Adapted regex from the Python reference implementation: https://packaging.python.org/en/latest/specifications/inline-script-metadata/#reference-implementation
|
||||||
|
const regex = regEx(
|
||||||
|
/^# \/\/\/ (?<type>[a-zA-Z0-9-]+)$\s(?<content>(^#(| .*)$\s)+)^# \/\/\/$/,
|
||||||
|
'm',
|
||||||
|
);
|
||||||
|
|
||||||
|
export function extractPackageFile(
|
||||||
|
content: string,
|
||||||
|
packageFile: string,
|
||||||
|
): PackageFileContent | null {
|
||||||
|
const match = regex.exec(content);
|
||||||
|
const matchedContent = match?.groups?.content;
|
||||||
|
|
||||||
|
if (!matchedContent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted code from the Python reference implementation: https://packaging.python.org/en/latest/specifications/inline-script-metadata/#reference-implementation
|
||||||
|
const parsedToml = matchedContent
|
||||||
|
.split(newlineRegex)
|
||||||
|
.map((line) => line.substring(line.startsWith('# ') ? 2 : 1))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const { data: res, error } = Pep723Schema.safeParse(parsedToml);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
logger.debug(
|
||||||
|
{ packageFile, error },
|
||||||
|
`Error parsing PEP 723 inline script metadata`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.deps.length ? res : null;
|
||||||
|
}
|
12
lib/modules/manager/pep723/index.ts
Normal file
12
lib/modules/manager/pep723/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import type { Category } from '../../../constants';
|
||||||
|
import { PypiDatasource } from '../../datasource/pypi';
|
||||||
|
export { extractPackageFile } from './extract';
|
||||||
|
|
||||||
|
export const supportedDatasources = [PypiDatasource.id];
|
||||||
|
|
||||||
|
export const categories: Category[] = ['python'];
|
||||||
|
|
||||||
|
export const defaultConfig = {
|
||||||
|
// Since any Python file can embed PEP 723 metadata, make the feature opt-in, to avoid parsing all Python files.
|
||||||
|
fileMatch: [],
|
||||||
|
};
|
1
lib/modules/manager/pep723/readme.md
Normal file
1
lib/modules/manager/pep723/readme.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This manager supports updating dependencies inside Python files that use [inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/), also known as PEP 723.
|
29
lib/modules/manager/pep723/schema.ts
Normal file
29
lib/modules/manager/pep723/schema.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { Toml } from '../../../util/schema-utils';
|
||||||
|
import { depTypes, pep508ToPackageDependency } from '../pep621/utils';
|
||||||
|
import type { PackageFileContent } from '../types';
|
||||||
|
|
||||||
|
const Pep723Dep = z
|
||||||
|
.string()
|
||||||
|
.transform((dep) => pep508ToPackageDependency(depTypes.dependencies, dep));
|
||||||
|
|
||||||
|
export const Pep723Schema = Toml.pipe(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
'requires-python': z.string().optional(),
|
||||||
|
dependencies: z
|
||||||
|
.array(Pep723Dep)
|
||||||
|
.transform((deps) => deps.filter((dep) => !!dep))
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.transform(({ 'requires-python': requiresPython, dependencies }) => {
|
||||||
|
const res: PackageFileContent = { deps: dependencies ?? [] };
|
||||||
|
|
||||||
|
if (is.nonEmptyString(requiresPython)) {
|
||||||
|
res.extractedConstraints = { python: requiresPython };
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
|
);
|
Loading…
Reference in a new issue