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

Calculate and store hash for url dependencies #7121

Merged
merged 5 commits into from Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/poetry/installation/executor.py
Expand Up @@ -15,7 +15,6 @@
from typing import Any

from cleo.io.null_io import NullIO
from poetry.core.packages.file_dependency import FileDependency
from poetry.core.packages.utils.link import Link
from poetry.core.pyproject.toml import PyProjectTOML

Expand All @@ -28,6 +27,7 @@
from poetry.utils.authenticator import Authenticator
from poetry.utils.env import EnvCommandError
from poetry.utils.helpers import atomic_open
from poetry.utils.helpers import get_file_hash
from poetry.utils.helpers import pluralize
from poetry.utils.helpers import remove_directory
from poetry.utils.pip import pip_install
Expand Down Expand Up @@ -666,8 +666,7 @@ def _download_link(self, operation: Install | Update, link: Link) -> Path:

@staticmethod
def _validate_archive_hash(archive: Path, package: Package) -> str:
file_dep = FileDependency(package.name, archive)
archive_hash: str = "sha256:" + file_dep.hash()
archive_hash: str = "sha256:" + get_file_hash(archive)
known_hashes = {f["hash"] for f in package.files}

if archive_hash not in known_hashes:
Expand Down
10 changes: 9 additions & 1 deletion src/poetry/puzzle/provider.py
Expand Up @@ -33,6 +33,7 @@
from poetry.puzzle.exceptions import OverrideNeeded
from poetry.repositories.exceptions import PackageNotFound
from poetry.utils.helpers import download_file
from poetry.utils.helpers import get_file_hash
from poetry.vcs.git import Git


Expand Down Expand Up @@ -396,7 +397,10 @@ def _search_for_file(self, dependency: FileDependency) -> Package:
package.root_dir = dependency.base

package.files = [
{"file": dependency.path.name, "hash": "sha256:" + dependency.hash()}
{
"file": dependency.path.name,
"hash": "sha256:" + get_file_hash(dependency.full_path),
}
]

return package
Expand Down Expand Up @@ -453,6 +457,10 @@ def get_package_from_url(cls, url: str) -> Package:
download_file(url, dest)
package = cls.get_package_from_file(dest)

package.files = [
{"file": file_name, "hash": "sha256:" + get_file_hash(dest)}
]

package._source_type = "url"
package._source_url = url

Expand Down
11 changes: 11 additions & 0 deletions src/poetry/utils/helpers.py
@@ -1,5 +1,7 @@
from __future__ import annotations

import hashlib
import io
import os
import shutil
import stat
Expand Down Expand Up @@ -252,3 +254,12 @@ def get_real_windows_path(path: str | Path) -> Path:
path = path.resolve()

return path


def get_file_hash(path: Path, hash_name: str = "sha256") -> str:
h = hashlib.new(hash_name)
with path.open("rb") as fp:
for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""):
h.update(content)

return h.hexdigest()
Expand Up @@ -5,7 +5,9 @@ description = ""
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = []
files = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"}
]

[package.source]
type = "url"
Expand All @@ -25,8 +27,9 @@ description = ""
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = []

files = [
{file = "demo-0.1.0.tar.gz", hash = "sha256:72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6"}
]
[package.source]
type = "url"
url = "https://python-poetry.org/distributions/demo-0.1.0.tar.gz"
Expand Down
4 changes: 3 additions & 1 deletion tests/installation/fixtures/with-url-dependency.test
Expand Up @@ -5,7 +5,9 @@ description = ""
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = []
files = [
{file = "demo-0.1.0-py2.py3-none-any.whl", hash = "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"}
]

[package.source]
type = "url"
Expand Down
76 changes: 76 additions & 0 deletions tests/utils/test_helpers.py
@@ -1,7 +1,18 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from poetry.core.utils.helpers import parse_requires

from poetry.utils.helpers import get_file_hash


if TYPE_CHECKING:
from tests.types import FixtureDirGetter


def test_parse_requires():
requires = """\
Expand Down Expand Up @@ -57,3 +68,68 @@ def test_parse_requires():
]
# fmt: on
assert result == expected


def test_default_hash(fixture_dir: FixtureDirGetter) -> None:
root_dir = Path(__file__).parent.parent.parent
file_path = root_dir / fixture_dir("distributions/demo-0.1.0.tar.gz")
sha_256 = "72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6"
assert get_file_hash(file_path) == sha_256


try:
from hashlib import algorithms_guaranteed
except ImportError:
algorithms_guaranteed = {"md5", "sha1", "sha224", "sha256", "sha384", "sha512"}


@pytest.mark.parametrize(
"hash_name,expected",
[
(hash_name, value)
for hash_name, value in [
("sha224", "972d02f36539a98599aed0566bc8aaf3e6701f4e895dd797d8f5248e"),
(
"sha3_512",
"c04ee109ae52d6440445e24dbd6d244a1d0f0289ef79cb7ba9bc3c139c0237169af9a8f61cd1cf4fc17f853ddf84f97c475ac5bb6c91a4aff0b825b884d4896c", # noqa: E501
),
(
"blake2s",
"c336ecbc9d867c9d860accfba4c3723c51c4b5c47a1e0a955e1c8df499e36741",
),
(
"sha3_384",
"d4abb2459941369aabf8880c5287b7eeb80678e14f13c71b9ecf64c772029dc3f93939590bea9ecdb51a1d1a74fefc5a", # noqa: E501
),
(
"blake2b",
"48e70abac547ab38e2330e6e6743a0c0f6274dcaa6df2c98135a78a9dd5b04a072d551fc3851b34da03eb0bf50dd71c7f32a8c36956e99fd6c66491bc7844800", # noqa: E501
),
(
"sha256",
"72e8531e49038c5f9c4a837b088bfcb8011f4a9f76335c8f0654df6ac539b3d6",
),
(
"sha512",
"e08a00a4b86358e49a318e7e3ba7a3d2fabdd17a2fef95559a0af681ea07ab1296b0b8e11e645297da296290661dc07ae3c8f74eab66bd18a80dce0c0ccb355b", # noqa: E501
),
(
"sha384",
"aa3144e28c6700a83247e8ec8711af5d3f5f75997990d48ec41e66bd275b3d0e19ee6f2fe525a358f874aa717afd06a9", # noqa: E501
),
("sha3_224", "64bfc6e4125b4c6d67fd88ad1c7d1b5c4dc11a1970e433cd576c91d4"),
("sha1", "4c057579005ac3e68e951a11ffdc4b27c6ae16af"),
(
"sha3_256",
"ba3d2a964b0680b6dc9565a03952e29c294c785d5a2307d3e2d785d73b75ed7e",
),
]
if hash_name in algorithms_guaranteed
],
)
def test_guaranteed_hash(
hash_name: str, expected: str, fixture_dir: FixtureDirGetter
) -> None:
root_dir = Path(__file__).parent.parent.parent
file_path = root_dir / fixture_dir("distributions/demo-0.1.0.tar.gz")
assert get_file_hash(file_path, hash_name) == expected