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 osgi from './osgi';
|
||||
import * as pep621 from './pep621';
|
||||
import * as pep723 from './pep723';
|
||||
import * as pipCompile from './pip-compile';
|
||||
import * as pip_requirements from './pip_requirements';
|
||||
import * as pip_setup from './pip_setup';
|
||||
|
@ -174,6 +175,7 @@ api.set('nvm', nvm);
|
|||
api.set('ocb', ocb);
|
||||
api.set('osgi', osgi);
|
||||
api.set('pep621', pep621);
|
||||
api.set('pep723', pep723);
|
||||
api.set('pip-compile', pipCompile);
|
||||
api.set('pip_requirements', pip_requirements);
|
||||
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