From 0220278c8ae933bda7521a3fb8c119881e9c8f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Tue, 21 Jul 2020 20:51:32 +0200 Subject: [PATCH] Add lock versioning support --- poetry/console/config/application_config.py | 7 ++- poetry/packages/locker.py | 32 ++++++++++- .../fixtures/extras-with-dependencies.test | 1 + tests/installation/fixtures/extras.test | 1 + .../installation/fixtures/install-no-dev.test | 1 + .../fixtures/no-dependencies.test | 1 + tests/installation/fixtures/remove.test | 1 + .../fixtures/update-with-lock.test | 1 + .../fixtures/update-with-locked-extras.test | 1 + .../fixtures/with-category-change.test | 1 + .../fixtures/with-conditional-dependency.test | 1 + .../fixtures/with-dependencies-extras.test | 1 + .../fixtures/with-dependencies.test | 1 + ...irectory-dependency-poetry-transitive.test | 1 + .../with-directory-dependency-poetry.test | 1 + .../with-directory-dependency-setuptools.test | 1 + .../with-duplicate-dependencies-update.test | 1 + .../fixtures/with-duplicate-dependencies.test | 1 + .../with-file-dependency-transitive.test | 1 + .../fixtures/with-file-dependency.test | 1 + .../fixtures/with-multiple-updates.test | 1 + .../fixtures/with-optional-dependencies.test | 1 + .../fixtures/with-platform-dependencies.test | 1 + .../fixtures/with-prereleases.test | 1 + .../fixtures/with-pypi-repository.test | 1 + .../fixtures/with-python-versions.test | 1 + .../fixtures/with-sub-dependencies.test | 1 + .../fixtures/with-url-dependency.test | 1 + ...ith-wheel-dependency-no-requires-dist.test | 1 + tests/packages/test_locker.py | 56 +++++++++++++++++++ 30 files changed, 119 insertions(+), 3 deletions(-) diff --git a/poetry/console/config/application_config.py b/poetry/console/config/application_config.py index 97570956b09..e845386f96a 100644 --- a/poetry/console/config/application_config.py +++ b/poetry/console/config/application_config.py @@ -54,7 +54,11 @@ def register_command_loggers( io = event.io - loggers = ["poetry.packages.package", "poetry.utils.password_manager"] + loggers = [ + "poetry.packages.locker", + "poetry.packages.package", + "poetry.utils.password_manager", + ] loggers += command.loggers @@ -65,7 +69,6 @@ def register_command_loggers( logger = logging.getLogger(logger) logger.handlers = [handler] - logger.propagate = False level = logging.WARNING if io.is_debug(): diff --git a/poetry/packages/locker.py b/poetry/packages/locker.py index f2eca202e81..ccfb54a21b3 100644 --- a/poetry/packages/locker.py +++ b/poetry/packages/locker.py @@ -1,4 +1,5 @@ import json +import logging import re from hashlib import sha256 @@ -13,13 +14,20 @@ import poetry.packages import poetry.repositories +from poetry.semver import parse_constraint +from poetry.semver.version import Version from poetry.utils._compat import Path from poetry.utils.toml_file import TomlFile from poetry.version.markers import parse_marker +logger = logging.getLogger(__name__) + + class Locker(object): + _VERSION = "1.0" + _relevant_keys = ["dependencies", "dev-dependencies", "source", "extras"] def __init__(self, lock, local_config): # type: (Path, dict) -> None @@ -180,6 +188,7 @@ def set_lock_data(self, root, packages): # type: (...) -> bool } lock["metadata"] = { + "lock-version": self._VERSION, "python-versions": root.python_versions, "content-hash": self._content_hash, "files": files, @@ -222,10 +231,31 @@ def _get_lock_data(self): # type: () -> dict raise RuntimeError("No lockfile found. Unable to read locked packages") try: - return self._lock.read() + lock_data = self._lock.read() except TOMLKitError as e: raise RuntimeError("Unable to read the lock file ({}).".format(e)) + lock_version = Version.parse(lock_data["metadata"].get("lock-version", "1.0")) + current_version = Version.parse(self._VERSION) + accepted_versions = parse_constraint( + "^{}".format(Version(current_version.major, current_version.minor)) + ) + lock_version_allowed = accepted_versions.allows(lock_version) + if lock_version_allowed and current_version < lock_version: + logger.warning( + "The lock file might not be compatible with the current version of Poetry.\n" + "Upgrade Poetry to ensure the lock file is read properly or, alternatively, " + "regenerate the lock file with the `poetry lock` command." + ) + elif not lock_version_allowed: + raise RuntimeError( + "The lock file is not compatible with the current version of Poetry.\n" + "Upgrade Poetry to be able to read the lock file or, alternatively, " + "regenerate the lock file with the `poetry lock` command." + ) + + return lock_data + def _lock_packages( self, packages ): # type: (List['poetry.packages.Package']) -> list diff --git a/tests/installation/fixtures/extras-with-dependencies.test b/tests/installation/fixtures/extras-with-dependencies.test index 9520c5665ba..dfc3e4f6353 100644 --- a/tests/installation/fixtures/extras-with-dependencies.test +++ b/tests/installation/fixtures/extras-with-dependencies.test @@ -38,6 +38,7 @@ foo = ["C"] [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/extras.test b/tests/installation/fixtures/extras.test index 9ebdd8588fa..772899331aa 100644 --- a/tests/installation/fixtures/extras.test +++ b/tests/installation/fixtures/extras.test @@ -35,6 +35,7 @@ foo = ["D"] [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/install-no-dev.test b/tests/installation/fixtures/install-no-dev.test index 589c20c50f0..a7fe88ef866 100644 --- a/tests/installation/fixtures/install-no-dev.test +++ b/tests/installation/fixtures/install-no-dev.test @@ -24,6 +24,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/no-dependencies.test b/tests/installation/fixtures/no-dependencies.test index af6ab887d40..0c70713cd36 100644 --- a/tests/installation/fixtures/no-dependencies.test +++ b/tests/installation/fixtures/no-dependencies.test @@ -2,6 +2,7 @@ package = [] [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/remove.test b/tests/installation/fixtures/remove.test index ef989e03a56..8f7007ffc1f 100644 --- a/tests/installation/fixtures/remove.test +++ b/tests/installation/fixtures/remove.test @@ -8,6 +8,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/update-with-lock.test b/tests/installation/fixtures/update-with-lock.test index f092b94f68d..2d361ac6b89 100644 --- a/tests/installation/fixtures/update-with-lock.test +++ b/tests/installation/fixtures/update-with-lock.test @@ -8,6 +8,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/update-with-locked-extras.test b/tests/installation/fixtures/update-with-locked-extras.test index 83aecc522d6..16ea1b70338 100644 --- a/tests/installation/fixtures/update-with-locked-extras.test +++ b/tests/installation/fixtures/update-with-locked-extras.test @@ -40,6 +40,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-category-change.test b/tests/installation/fixtures/with-category-change.test index 198c0d17186..550b9bbbc42 100644 --- a/tests/installation/fixtures/with-category-change.test +++ b/tests/installation/fixtures/with-category-change.test @@ -19,6 +19,7 @@ A = "^1.0" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-conditional-dependency.test b/tests/installation/fixtures/with-conditional-dependency.test index 5cf53a08497..fe040011c55 100644 --- a/tests/installation/fixtures/with-conditional-dependency.test +++ b/tests/installation/fixtures/with-conditional-dependency.test @@ -22,6 +22,7 @@ python = ">=3.6,<4.0" [metadata] python-versions = "~2.7 || ^3.4" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-dependencies-extras.test b/tests/installation/fixtures/with-dependencies-extras.test index 22bb5921115..ce85e449d47 100644 --- a/tests/installation/fixtures/with-dependencies-extras.test +++ b/tests/installation/fixtures/with-dependencies-extras.test @@ -30,6 +30,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-dependencies.test b/tests/installation/fixtures/with-dependencies.test index 9b07daae56b..2e2abddd2e4 100644 --- a/tests/installation/fixtures/with-dependencies.test +++ b/tests/installation/fixtures/with-dependencies.test @@ -16,6 +16,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test index ad29dd7abe6..78d8978d793 100644 --- a/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test +++ b/tests/installation/fixtures/with-directory-dependency-poetry-transitive.test @@ -96,6 +96,7 @@ url = "project_with_transitive_file_dependencies" [metadata] content-hash = "123456789" +lock-version = "1.0" python-versions = "*" [metadata.files] diff --git a/tests/installation/fixtures/with-directory-dependency-poetry.test b/tests/installation/fixtures/with-directory-dependency-poetry.test index df8b282d684..c1b0ca43712 100644 --- a/tests/installation/fixtures/with-directory-dependency-poetry.test +++ b/tests/installation/fixtures/with-directory-dependency-poetry.test @@ -29,6 +29,7 @@ url = "tests/fixtures/project_with_extras" [metadata] content-hash = "123456789" +lock-version = "1.0" python-versions = "*" [metadata.files] diff --git a/tests/installation/fixtures/with-directory-dependency-setuptools.test b/tests/installation/fixtures/with-directory-dependency-setuptools.test index b9a97381503..4ae719f6b0a 100644 --- a/tests/installation/fixtures/with-directory-dependency-setuptools.test +++ b/tests/installation/fixtures/with-directory-dependency-setuptools.test @@ -34,6 +34,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-duplicate-dependencies-update.test b/tests/installation/fixtures/with-duplicate-dependencies-update.test index 89956f45bea..99b37ea97cf 100644 --- a/tests/installation/fixtures/with-duplicate-dependencies-update.test +++ b/tests/installation/fixtures/with-duplicate-dependencies-update.test @@ -31,6 +31,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-duplicate-dependencies.test b/tests/installation/fixtures/with-duplicate-dependencies.test index 7149b87d02b..7029f7658ec 100644 --- a/tests/installation/fixtures/with-duplicate-dependencies.test +++ b/tests/installation/fixtures/with-duplicate-dependencies.test @@ -56,6 +56,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-file-dependency-transitive.test b/tests/installation/fixtures/with-file-dependency-transitive.test index 2cbd16e1a53..d4b47765430 100644 --- a/tests/installation/fixtures/with-file-dependency-transitive.test +++ b/tests/installation/fixtures/with-file-dependency-transitive.test @@ -60,6 +60,7 @@ url = "project_with_transitive_file_dependencies" [metadata] content-hash = "123456789" +lock-version = "1.0" python-versions = "*" [metadata.files] diff --git a/tests/installation/fixtures/with-file-dependency.test b/tests/installation/fixtures/with-file-dependency.test index da10f203e46..6998bfce12e 100644 --- a/tests/installation/fixtures/with-file-dependency.test +++ b/tests/installation/fixtures/with-file-dependency.test @@ -28,6 +28,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-multiple-updates.test b/tests/installation/fixtures/with-multiple-updates.test index 961817afea1..4923e4c9b38 100644 --- a/tests/installation/fixtures/with-multiple-updates.test +++ b/tests/installation/fixtures/with-multiple-updates.test @@ -49,6 +49,7 @@ python-versions = "*" [metadata] python-versions = "~2.7 || ^3.4" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-optional-dependencies.test b/tests/installation/fixtures/with-optional-dependencies.test index 3e392ff9782..9bec5cbebba 100644 --- a/tests/installation/fixtures/with-optional-dependencies.test +++ b/tests/installation/fixtures/with-optional-dependencies.test @@ -32,6 +32,7 @@ foo = ["A"] [metadata] python-versions = "~2.7 || ^3.4" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-platform-dependencies.test b/tests/installation/fixtures/with-platform-dependencies.test index 260e413f701..1ea908d7e8f 100644 --- a/tests/installation/fixtures/with-platform-dependencies.test +++ b/tests/installation/fixtures/with-platform-dependencies.test @@ -41,6 +41,7 @@ foo = ["A"] [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-prereleases.test b/tests/installation/fixtures/with-prereleases.test index 855dbd7f616..a6638fd41d0 100644 --- a/tests/installation/fixtures/with-prereleases.test +++ b/tests/installation/fixtures/with-prereleases.test @@ -16,6 +16,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-pypi-repository.test b/tests/installation/fixtures/with-pypi-repository.test index fc16d3d6187..94980ce4452 100644 --- a/tests/installation/fixtures/with-pypi-repository.test +++ b/tests/installation/fixtures/with-pypi-repository.test @@ -84,6 +84,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-python-versions.test b/tests/installation/fixtures/with-python-versions.test index 49a53211d06..c9e040dbfcf 100644 --- a/tests/installation/fixtures/with-python-versions.test +++ b/tests/installation/fixtures/with-python-versions.test @@ -24,6 +24,7 @@ python-versions = "~2.7 || ^3.3" [metadata] python-versions = "~2.7 || ^3.4" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-sub-dependencies.test b/tests/installation/fixtures/with-sub-dependencies.test index 306bf818685..312a49a83a5 100644 --- a/tests/installation/fixtures/with-sub-dependencies.test +++ b/tests/installation/fixtures/with-sub-dependencies.test @@ -38,6 +38,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-url-dependency.test b/tests/installation/fixtures/with-url-dependency.test index 25c2cff6ce7..1aee0ba9367 100644 --- a/tests/installation/fixtures/with-url-dependency.test +++ b/tests/installation/fixtures/with-url-dependency.test @@ -28,6 +28,7 @@ python-versions = "*" [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/installation/fixtures/with-wheel-dependency-no-requires-dist.test b/tests/installation/fixtures/with-wheel-dependency-no-requires-dist.test index 36741ded5e8..d60432c2417 100644 --- a/tests/installation/fixtures/with-wheel-dependency-no-requires-dist.test +++ b/tests/installation/fixtures/with-wheel-dependency-no-requires-dist.test @@ -13,6 +13,7 @@ url = "tests/fixtures/wheel_with_no_requires_dist/demo-0.1.0-py2.py3-none-any.wh [metadata] python-versions = "*" +lock-version = "1.0" content-hash = "123456789" [metadata.files] diff --git a/tests/packages/test_locker.py b/tests/packages/test_locker.py index 2e166880612..20a9dc9c703 100644 --- a/tests/packages/test_locker.py +++ b/tests/packages/test_locker.py @@ -1,3 +1,4 @@ +import logging import tempfile import pytest @@ -56,6 +57,7 @@ def test_lock_file_data_is_ordered(locker, root): [metadata] content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +lock-version = "1.0" python-versions = "*" [metadata.files] @@ -93,6 +95,7 @@ def test_locker_properly_loads_extras(locker): [metadata] content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77" +lock-version = "1.0" python-versions = "~2.7 || ^3.4" [metadata.files] @@ -132,6 +135,7 @@ def test_lock_packages_with_null_description(locker, root): [metadata] content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +lock-version = "1.0" python-versions = "*" [metadata.files] @@ -171,6 +175,7 @@ def test_lock_file_should_not_have_mixed_types(locker, root): [metadata] content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +lock-version = "1.0" python-versions = "*" [metadata.files] @@ -200,6 +205,7 @@ def test_reading_lock_file_should_raise_an_error_on_invalid_data(locker): [metadata] content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +lock-version = "1.0" python-versions = "*" [metadata.files] @@ -239,6 +245,7 @@ def test_locking_legacy_repository_package_should_include_source_section(root, l [metadata] content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" +lock-version = "1.0" python-versions = "*" [metadata.files] @@ -246,3 +253,52 @@ def test_locking_legacy_repository_package_should_include_source_section(root, l """ assert expected == content + + +def test_locker_should_emit_warnings_if_lock_version_is_newer_but_allowed( + locker, caplog +): + content = """\ +[metadata] +content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77" +lock-version = "1.1" +python-versions = "~2.7 || ^3.4" + +[metadata.files] +""" + caplog.set_level(logging.WARNING, logger="poetry.packages.locker") + + locker.lock.write(tomlkit.parse(content)) + + _ = locker.lock_data + + assert 1 == len(caplog.records) + + record = caplog.records[0] + assert "WARNING" == record.levelname + + expected = """\ +The lock file might not be compatible with the current version of Poetry. +Upgrade Poetry to ensure the lock file is read properly or, alternatively, \ +regenerate the lock file with the `poetry lock` command.\ +""" + assert expected == record.message + + +def test_locker_should_raise_an_error_if_lock_version_is_newer_and_not_allowed( + locker, caplog +): + content = """\ +[metadata] +content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77" +lock-version = "2.0" +python-versions = "~2.7 || ^3.4" + +[metadata.files] +""" + caplog.set_level(logging.WARNING, logger="poetry.packages.locker") + + locker.lock.write(tomlkit.parse(content)) + + with pytest.raises(RuntimeError, match="^The lock file is not compatible"): + _ = locker.lock_data