Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(poetry): support rangeStrategy=update-lockfile #8672

Merged
merged 20 commits into from
Feb 15, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1599,7 +1599,7 @@ Behavior:
- `bump` = e.g. bump the range even if the new version satisfies the existing range, e.g. `^1.0.0` -> `^1.1.0`
- `replace` = Replace the range with a newer one if the new version falls outside it, e.g. `^1.0.0` -> `^2.0.0`
- `widen` = Widen the range with newer one, e.g. `^1.0.0` -> `^1.0.0 || ^2.0.0`
- `update-lockfile` = Update the lock file when in-range updates are available, otherwise `replace` for updates out of range. Works for `bundler`, `composer`, `npm`, and `yarn`, so far
- `update-lockfile` = Update the lock file when in-range updates are available, otherwise `replace` for updates out of range. Works for `bundler`, `composer`, `npm`, `yarn` and `poetry` so far

Renovate's `"auto"` strategy works like this for npm:

Expand Down
20 changes: 3 additions & 17 deletions lib/manager/poetry/__fixtures__/pyproject.10.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
[[tool.poetry.source]]
viceice marked this conversation as resolved.
Show resolved Hide resolved
name = "one"
url = "some.url"

[[tool.poetry.source]]
name = "two"
url = "another.url"

[[tool.poetry.source]]
url = "missingname.url"

[[tool.poetry.source]]
name = "four"
url = "last.url"

[[tool.poetry.source]]
name = "five"
[tool.poetry.dependencies]
python = "^3.9"
boto3 = "*"
76 changes: 76 additions & 0 deletions lib/manager/poetry/__fixtures__/pyproject.10.toml.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
[[package]]
name = "boto3"
version = "1.17.5"
description = "The AWS SDK for Python"
category = "main"
optional = false
python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"

[package.dependencies]
botocore = ">=1.20.7,<1.21.0"
jmespath = ">=0.7.1,<1.0.0"
s3transfer = ">=0.3.0,<0.4.0"

[[package]]
name = "botocore"
version = "1.20.7"
description = "Low-level, data-driven core of boto 3."
category = "main"
optional = false
python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"

[package.dependencies]
jmespath = ">=0.7.1,<1.0.0"
python-dateutil = ">=2.1,<3.0.0"
urllib3 = ">=1.25.4,<1.27"

[[package]]
name = "jmespath"
version = "0.10.0"
description = "JSON Matching Expressions"
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"

[[package]]
name = "python-dateutil"
version = "2.8.1"
description = "Extensions to the standard Python datetime module"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"

[package.dependencies]
six = ">=1.5"

[[package]]
name = "s3transfer"
version = "0.3.4"
description = "An Amazon S3 Transfer Manager"
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
botocore = ">=1.12.36,<2.0a.0"

[[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"

[[package]]
name = "urllib3"
version = "1.26.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"

[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
35 changes: 2 additions & 33 deletions lib/manager/poetry/__snapshots__/artifacts.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`.updateArtifacts() catches errors 1`] = `
Array [
Object {
"artifactError": Object {
"lockFile": "poetry.lock",
"stderr": "undefined
undefined",
},
},
]
`;
exports[`.updateArtifacts() catches errors 1`] = `null`;

exports[`.updateArtifacts() passes private credential environment vars 1`] = `
Array [
Expand Down Expand Up @@ -39,28 +29,7 @@ Array [
]
`;

exports[`.updateArtifacts() returns null if unchanged 1`] = `
Array [
Object {
"cmd": "poetry update --lock --no-interaction dep1",
"options": Object {
"cwd": "/tmp/github/some/repo",
"encoding": "utf-8",
"env": Object {
"HOME": "/home/user",
"HTTPS_PROXY": "https://example.com",
"HTTP_PROXY": "http://example.com",
"LANG": "en_US.UTF-8",
"LC_ALL": "en_US",
"NO_PROXY": "localhost",
"PATH": "/tmp/path",
},
"maxBuffer": 10485760,
"timeout": 900000,
},
},
]
`;
exports[`.updateArtifacts() returns null if unchanged 1`] = `Array []`;

exports[`.updateArtifacts() returns updated poetry.lock 1`] = `
Array [
Expand Down
22 changes: 22 additions & 0 deletions lib/manager/poetry/__snapshots__/extract.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -548,3 +548,25 @@ Array [
},
]
`;

exports[`lib/manager/poetry/extract extractPackageFile() resolves lockedVersions from the lockfile 1`] = `
Object {
"constraints": Object {
"python": "^3.9",
},
"deps": Array [
Object {
"currentValue": "*",
"datasource": "pypi",
"depName": "boto3",
"depType": "dependencies",
"lockedVersion": "1.17.5",
"managerData": Object {
"nestedVersion": false,
},
"versioning": "poetry",
},
],
"registryUrls": null,
}
`;
1 change: 1 addition & 0 deletions lib/manager/poetry/artifacts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const pyproject10toml = readFileSync(

jest.mock('fs-extra');
jest.mock('child_process');
jest.mock('../../util/fs');
viceice marked this conversation as resolved.
Show resolved Hide resolved
jest.mock('../../util/exec/env');
jest.mock('../../datasource');
jest.mock('../../util/host-rules');
Expand Down
78 changes: 48 additions & 30 deletions lib/manager/poetry/extract.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { readFileSync } from 'fs';
import { fs } from '../../../test/util';
import { extractPackageFile } from './extract';

jest.mock('../../util/fs');
viceice marked this conversation as resolved.
Show resolved Hide resolved
viceice marked this conversation as resolved.
Show resolved Hide resolved

const pyproject1toml = readFileSync(
'lib/manager/poetry/__fixtures__/pyproject.1.toml',
'utf8'
Expand Down Expand Up @@ -46,6 +49,16 @@ const pyproject9toml = readFileSync(
'utf8'
);

const pyproject10toml = readFileSync(
'lib/manager/poetry/__fixtures__/pyproject.10.toml',
'utf8'
);

const pyproject10tomlLock = readFileSync(
'lib/manager/poetry/__fixtures__/pyproject.10.toml.lock',
'utf8'
);

describe('lib/manager/poetry/extract', () => {
describe('extractPackageFile()', () => {
let filename: string;
Expand All @@ -58,87 +71,92 @@ describe('lib/manager/poetry/extract', () => {
afterEach(() => {
process.env = OLD_ENV;
});
it('returns null for empty', () => {
expect(extractPackageFile('nothing here', filename)).toBeNull();
it('returns null for empty', async () => {
expect(await extractPackageFile('nothing here', filename)).toBeNull();
});
it('returns null for parsed file without poetry section', () => {
expect(extractPackageFile(pyproject5toml, filename)).toBeNull();
it('returns null for parsed file without poetry section', async () => {
expect(await extractPackageFile(pyproject5toml, filename)).toBeNull();
});
it('extracts multiple dependencies', () => {
const res = extractPackageFile(pyproject1toml, filename);
it('extracts multiple dependencies', async () => {
const res = await extractPackageFile(pyproject1toml, filename);
expect(res.deps).toMatchSnapshot();
expect(res.deps).toHaveLength(9);
expect(res.constraints).toEqual({
poetry: 'poetry>=1.0 wheel',
python: '~2.7 || ^3.4',
});
});
it('extracts multiple dependencies (with dep = {version = "1.2.3"} case)', () => {
const res = extractPackageFile(pyproject2toml, filename);
it('extracts multiple dependencies (with dep = {version = "1.2.3"} case)', async () => {
const res = await extractPackageFile(pyproject2toml, filename);
expect(res.deps).toMatchSnapshot();
expect(res.deps).toHaveLength(7);
});
it('handles case with no dependencies', () => {
const res = extractPackageFile(pyproject3toml, filename);
it('handles case with no dependencies', async () => {
const res = await extractPackageFile(pyproject3toml, filename);
expect(res).toBeNull();
});
it('handles multiple constraint dependencies', () => {
const res = extractPackageFile(pyproject4toml, filename);
it('handles multiple constraint dependencies', async () => {
const res = await extractPackageFile(pyproject4toml, filename);
expect(res.deps).toMatchSnapshot();
expect(res.deps).toHaveLength(1);
});
it('extracts registries', () => {
const res = extractPackageFile(pyproject6toml, filename);
it('extracts registries', async () => {
const res = await extractPackageFile(pyproject6toml, filename);
expect(res.registryUrls).toMatchSnapshot();
expect(res.registryUrls).toHaveLength(3);
});
it('can parse empty registries', () => {
const res = extractPackageFile(pyproject7toml, filename);
it('can parse empty registries', async () => {
const res = await extractPackageFile(pyproject7toml, filename);
expect(res.registryUrls).toBeNull();
});
it('can parse missing registries', () => {
const res = extractPackageFile(pyproject1toml, filename);
it('can parse missing registries', async () => {
const res = await extractPackageFile(pyproject1toml, filename);
expect(res.registryUrls).toBeNull();
});
it('dedupes registries', () => {
const res = extractPackageFile(pyproject8toml, filename);
it('dedupes registries', async () => {
const res = await extractPackageFile(pyproject8toml, filename);
expect(res.registryUrls).toMatchSnapshot();
});
it('extracts mixed versioning types', () => {
const res = extractPackageFile(pyproject9toml, filename);
it('extracts mixed versioning types', async () => {
const res = await extractPackageFile(pyproject9toml, filename);
expect(res).toMatchSnapshot();
});
it('resolves lockedVersions from the lockfile', async () => {
fs.readLocalFile.mockResolvedValue(pyproject10tomlLock);
const res = await extractPackageFile(pyproject10toml, filename);
expect(res).toMatchSnapshot();
});
it('skips git dependencies', () => {
it('skips git dependencies', async () => {
const content =
'[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git"}\r\nwerkzeug = ">=0.14"';
const res = extractPackageFile(content, filename).deps;
const res = (await extractPackageFile(content, filename)).deps;
expect(res[0].depName).toBe('flask');
expect(res[0].currentValue).toBe('');
expect(res[0].skipReason).toBe('git-dependency');
expect(res).toHaveLength(2);
});
it('skips git dependencies with version', () => {
it('skips git dependencies with version', async () => {
const content =
'[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git", version="1.2.3"}\r\nwerkzeug = ">=0.14"';
const res = extractPackageFile(content, filename).deps;
const res = (await extractPackageFile(content, filename)).deps;
expect(res[0].depName).toBe('flask');
expect(res[0].currentValue).toBe('1.2.3');
expect(res[0].skipReason).toBe('git-dependency');
expect(res).toHaveLength(2);
});
it('skips path dependencies', () => {
it('skips path dependencies', async () => {
const content =
'[tool.poetry.dependencies]\r\nflask = {path = "/some/path/"}\r\nwerkzeug = ">=0.14"';
const res = extractPackageFile(content, filename).deps;
const res = (await extractPackageFile(content, filename)).deps;
expect(res[0].depName).toBe('flask');
expect(res[0].currentValue).toBe('');
expect(res[0].skipReason).toBe('path-dependency');
expect(res).toHaveLength(2);
});
it('skips path dependencies with version', () => {
it('skips path dependencies with version', async () => {
const content =
'[tool.poetry.dependencies]\r\nflask = {path = "/some/path/", version = "1.2.3"}\r\nwerkzeug = ">=0.14"';
const res = extractPackageFile(content, filename).deps;
const res = (await extractPackageFile(content, filename)).deps;
expect(res[0].depName).toBe('flask');
expect(res[0].currentValue).toBe('1.2.3');
expect(res[0].skipReason).toBe('path-dependency');
Expand Down
Loading