From 6d57e843b4a709f5a5156c9cc5661cdd0f718979 Mon Sep 17 00:00:00 2001
From: samypr100 <3933065+samypr100@users.noreply.github.com>
Date: Wed, 14 Jun 2023 11:44:56 -0400
Subject: [PATCH 01/10] Moving "poetry lock --check" to "poetry check --lock"
(#8015)
---
docs/cli.md | 13 ++-
src/poetry/console/commands/check.py | 27 +++++-
src/poetry/console/commands/lock.py | 7 +-
tests/console/commands/test_check.py | 124 ++++++++++++++++++++++++++-
tests/console/commands/test_lock.py | 10 ++-
5 files changed, 172 insertions(+), 9 deletions(-)
diff --git a/docs/cli.md b/docs/cli.md
index b5913b69040..5a5fd878f7e 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -625,8 +625,9 @@ As such, `exit` should be used to properly exit the shell and the virtual enviro
## check
-The `check` command validates the structure of the `pyproject.toml` file
-and returns a detailed report if there are any errors.
+The `check` command validates the content of the `pyproject.toml` file
+and its consistency with the `poetry.lock` file.
+It returns a detailed report if there are any errors.
{{% note %}}
This command is also available as a pre-commit hook. See [pre-commit hooks]({{< relref "pre-commit-hooks#poetry-check">}}) for more information.
@@ -636,6 +637,10 @@ This command is also available as a pre-commit hook. See [pre-commit hooks]({{<
poetry check
```
+### Options
+
+* `--lock`: Verifies that `poetry.lock` exists for the current `pyproject.toml`.
+
## search
This command searches for packages on a remote index.
@@ -659,7 +664,7 @@ poetry lock
### Options
-* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml`
+* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml`. (**Deprecated**) Use `poetry check --lock` instead.
* `--no-update`: Do not update locked versions, only refresh lock file.
## version
@@ -944,7 +949,7 @@ poetry self lock
#### Options
-* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml`
+* `--check`: Verify that `poetry.lock` is consistent with `pyproject.toml`. (**Deprecated**)
* `--no-update`: Do not update locked versions, only refresh lock file.
### self show
diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py
index 3ca831e7213..733f0a7c5d5 100644
--- a/src/poetry/console/commands/check.py
+++ b/src/poetry/console/commands/check.py
@@ -1,11 +1,27 @@
from __future__ import annotations
+from cleo.helpers import option
+
from poetry.console.commands.command import Command
class CheckCommand(Command):
name = "check"
- description = "Checks the validity of the pyproject.toml file."
+ description = (
+ "Validates the content of the pyproject.toml> file and its"
+ " consistency with the poetry.lock file."
+ )
+
+ options = [
+ option(
+ "lock",
+ None,
+ (
+ "Checks that poetry.lock> exists for the current"
+ " version of pyproject.toml>."
+ ),
+ ),
+ ]
def validate_classifiers(
self, project_classifiers: set[str]
@@ -72,6 +88,15 @@ def handle(self) -> int:
check_result["errors"].extend(errors)
check_result["warnings"].extend(warnings)
+ # Verify that lock file is consistent
+ if self.option("lock") and not self.poetry.locker.is_locked():
+ check_result["errors"] += ["poetry.lock was not found."]
+ if self.poetry.locker.is_locked() and not self.poetry.locker.is_fresh():
+ check_result["errors"] += [
+ "poetry.lock is not consistent with pyproject.toml. Run `poetry"
+ " lock [--no-update]` to fix it."
+ ]
+
if not check_result["errors"] and not check_result["warnings"]:
self.info("All set!")
diff --git a/src/poetry/console/commands/lock.py b/src/poetry/console/commands/lock.py
index c87f1359dc7..87273a66776 100644
--- a/src/poetry/console/commands/lock.py
+++ b/src/poetry/console/commands/lock.py
@@ -18,7 +18,8 @@ class LockCommand(InstallerCommand):
None,
(
"Check that the poetry.lock> file corresponds to the current"
- " version of pyproject.toml>."
+ " version of pyproject.toml>. (Deprecated>) Use"
+ " poetry check --lock> instead."
),
),
]
@@ -36,6 +37,10 @@ class LockCommand(InstallerCommand):
def handle(self) -> int:
if self.option("check"):
+ self.line_error(
+ "poetry lock --check is deprecated, use `poetry"
+ " check --lock` instead."
+ )
if self.poetry.locker.is_locked() and self.poetry.locker.is_fresh():
self.line("poetry.lock is consistent with pyproject.toml.")
return 0
diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py
index 323364c8def..6347d4585fb 100644
--- a/tests/console/commands/test_check.py
+++ b/tests/console/commands/test_check.py
@@ -4,13 +4,19 @@
import pytest
+from poetry.packages import Locker
+
if TYPE_CHECKING:
+ import httpretty
+
from cleo.testers.command_tester import CommandTester
from pytest_mock import MockerFixture
+ from poetry.poetry import Poetry
from tests.types import CommandTesterFactory
from tests.types import FixtureDirGetter
+ from tests.types import ProjectFactory
@pytest.fixture()
@@ -18,6 +24,36 @@ def tester(command_tester_factory: CommandTesterFactory) -> CommandTester:
return command_tester_factory("check")
+def _project_factory(
+ fixture_name: str,
+ project_factory: ProjectFactory,
+ fixture_dir: FixtureDirGetter,
+) -> Poetry:
+ source = fixture_dir(fixture_name)
+ pyproject_content = (source / "pyproject.toml").read_text(encoding="utf-8")
+ poetry_lock_content = (source / "poetry.lock").read_text(encoding="utf-8")
+ return project_factory(
+ name="foobar",
+ pyproject_content=pyproject_content,
+ poetry_lock_content=poetry_lock_content,
+ source=source,
+ )
+
+
+@pytest.fixture
+def poetry_with_outdated_lockfile(
+ project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
+) -> Poetry:
+ return _project_factory("outdated_lock", project_factory, fixture_dir)
+
+
+@pytest.fixture
+def poetry_with_up_to_date_lockfile(
+ project_factory: ProjectFactory, fixture_dir: FixtureDirGetter
+) -> Poetry:
+ return _project_factory("up_to_date_lock", project_factory, fixture_dir)
+
+
def test_check_valid(tester: CommandTester) -> None:
tester.execute()
@@ -39,12 +75,13 @@ def test_check_invalid(
new_callable=mocker.PropertyMock,
)
- tester.execute()
+ tester.execute("--lock")
expected = """\
Error: 'description' is a required property
Error: Project name (invalid) is same as one of its dependencies
Error: Unrecognized classifiers: ['Intended Audience :: Clowns'].
+Error: poetry.lock was not found.
Warning: A wildcard Python dependency is ambiguous.\
Consider specifying a more explicit one.
Warning: The "pendulum" dependency specifies the "allows-prereleases" property,\
@@ -74,3 +111,88 @@ def test_check_private(
"""
assert tester.io.fetch_output() == expected
+
+
+@pytest.mark.parametrize(
+ ("options", "expected", "expected_status"),
+ [
+ ("", "All set!\n", 0),
+ ("--lock", "Error: poetry.lock was not found.\n", 1),
+ ],
+)
+def test_check_lock_missing(
+ mocker: MockerFixture,
+ tester: CommandTester,
+ fixture_dir: FixtureDirGetter,
+ options: str,
+ expected: str,
+ expected_status: int,
+) -> None:
+ from poetry.toml import TOMLFile
+
+ mocker.patch(
+ "poetry.poetry.Poetry.file",
+ return_value=TOMLFile(fixture_dir("private_pyproject") / "pyproject.toml"),
+ new_callable=mocker.PropertyMock,
+ )
+
+ status_code = tester.execute(options)
+
+ assert status_code == expected_status
+
+ if status_code == 0:
+ assert tester.io.fetch_output() == expected
+ else:
+ assert tester.io.fetch_error() == expected
+
+
+@pytest.mark.parametrize("options", ["", "--lock"])
+def test_check_lock_outdated(
+ command_tester_factory: CommandTesterFactory,
+ poetry_with_outdated_lockfile: Poetry,
+ http: type[httpretty.httpretty],
+ options: str,
+) -> None:
+ http.disable()
+
+ locker = Locker(
+ lock=poetry_with_outdated_lockfile.pyproject.file.path.parent / "poetry.lock",
+ local_config=poetry_with_outdated_lockfile.locker._local_config,
+ )
+ poetry_with_outdated_lockfile.set_locker(locker)
+
+ tester = command_tester_factory("check", poetry=poetry_with_outdated_lockfile)
+ status_code = tester.execute(options)
+ expected = (
+ "Error: poetry.lock is not consistent with pyproject.toml. "
+ "Run `poetry lock [--no-update]` to fix it.\n"
+ )
+
+ assert tester.io.fetch_error() == expected
+
+ # exit with an error
+ assert status_code == 1
+
+
+@pytest.mark.parametrize("options", ["", "--lock"])
+def test_check_lock_up_to_date(
+ command_tester_factory: CommandTesterFactory,
+ poetry_with_up_to_date_lockfile: Poetry,
+ http: type[httpretty.httpretty],
+ options: str,
+) -> None:
+ http.disable()
+
+ locker = Locker(
+ lock=poetry_with_up_to_date_lockfile.pyproject.file.path.parent / "poetry.lock",
+ local_config=poetry_with_up_to_date_lockfile.locker._local_config,
+ )
+ poetry_with_up_to_date_lockfile.set_locker(locker)
+
+ tester = command_tester_factory("check", poetry=poetry_with_up_to_date_lockfile)
+ status_code = tester.execute(options)
+ expected = "All set!\n"
+ assert tester.io.fetch_output() == expected
+
+ # exit with an error
+ assert status_code == 0
diff --git a/tests/console/commands/test_lock.py b/tests/console/commands/test_lock.py
index 7a910caccf7..26e0b98277c 100644
--- a/tests/console/commands/test_lock.py
+++ b/tests/console/commands/test_lock.py
@@ -89,7 +89,7 @@ def poetry_with_invalid_lockfile(
return _project_factory("invalid_lock", project_factory, fixture_dir)
-def test_lock_check_outdated(
+def test_lock_check_outdated_legacy(
command_tester_factory: CommandTesterFactory,
poetry_with_outdated_lockfile: Poetry,
http: type[httpretty.httpretty],
@@ -105,6 +105,7 @@ def test_lock_check_outdated(
tester = command_tester_factory("lock", poetry=poetry_with_outdated_lockfile)
status_code = tester.execute("--check")
expected = (
+ "poetry lock --check is deprecated, use `poetry check --lock` instead.\n"
"Error: poetry.lock is not consistent with pyproject.toml. "
"Run `poetry lock [--no-update]` to fix it.\n"
)
@@ -115,7 +116,7 @@ def test_lock_check_outdated(
assert status_code == 1
-def test_lock_check_up_to_date(
+def test_lock_check_up_to_date_legacy(
command_tester_factory: CommandTesterFactory,
poetry_with_up_to_date_lockfile: Poetry,
http: type[httpretty.httpretty],
@@ -133,6 +134,11 @@ def test_lock_check_up_to_date(
expected = "poetry.lock is consistent with pyproject.toml.\n"
assert tester.io.fetch_output() == expected
+ expected_error = (
+ "poetry lock --check is deprecated, use `poetry check --lock` instead.\n"
+ )
+ assert tester.io.fetch_error() == expected_error
+
# exit with an error
assert status_code == 0
From f691320294cf076349d8a915687716b43b087bf7 Mon Sep 17 00:00:00 2001
From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com>
Date: Sat, 17 Jun 2023 15:40:39 +0800
Subject: [PATCH 02/10] `poetry check` command validates readme (files must
exist) (#7444)
---
docs/pyproject.md | 6 +++++
src/poetry/console/commands/check.py | 26 +++++++++++++++++--
tests/console/commands/test_check.py | 1 +
.../fixtures/invalid_pyproject/pyproject.toml | 1 +
tests/fixtures/private_pyproject/README.md | 0
5 files changed, 32 insertions(+), 2 deletions(-)
create mode 100644 tests/fixtures/private_pyproject/README.md
diff --git a/docs/pyproject.md b/docs/pyproject.md
index a344ffc0db5..f123313d883 100644
--- a/docs/pyproject.md
+++ b/docs/pyproject.md
@@ -114,6 +114,12 @@ The file(s) can be of any format, but if you intend to publish to PyPI keep the
https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/) in
mind. README paths are implicitly relative to `pyproject.toml`.
+{{% note %}}
+Whether paths are case-sensitive follows platform defaults, but it is recommended to keep cases.
+
+To be specific, you can set `readme = "rEaDmE.mD"` for `README.md` on macOS and Windows, but Linux users can't `poetry install` after cloning your repo. This is because macOS and Windows are case-insensitive and case-preserving.
+{{% /note %}}
+
The contents of the README file(s) are used to populate the [Description
field](https://packaging.python.org/en/latest/specifications/core-metadata/#description-optional)
of your distribution's metadata (similar to `long_description` in setuptools).
diff --git a/src/poetry/console/commands/check.py b/src/poetry/console/commands/check.py
index 733f0a7c5d5..95bd9912195 100644
--- a/src/poetry/console/commands/check.py
+++ b/src/poetry/console/commands/check.py
@@ -1,10 +1,16 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
from cleo.helpers import option
from poetry.console.commands.command import Command
+if TYPE_CHECKING:
+ from pathlib import Path
+
+
class CheckCommand(Command):
name = "check"
description = (
@@ -23,7 +29,7 @@ class CheckCommand(Command):
),
]
- def validate_classifiers(
+ def _validate_classifiers(
self, project_classifiers: set[str]
) -> tuple[list[str], list[str]]:
"""Identify unrecognized and deprecated trove classifiers.
@@ -73,6 +79,17 @@ def validate_classifiers(
return errors, warnings
+ def _validate_readme(self, readme: str | list[str], poetry_file: Path) -> list[str]:
+ """Check existence of referenced readme files"""
+
+ readmes = [readme] if isinstance(readme, str) else readme
+
+ errors = []
+ for name in readmes:
+ if not (poetry_file.parent / name).exists():
+ errors.append(f"Declared README file does not exist: {name}")
+ return errors
+
def handle(self) -> int:
from poetry.factory import Factory
from poetry.pyproject.toml import PyProjectTOML
@@ -84,10 +101,15 @@ def handle(self) -> int:
# Validate trove classifiers
project_classifiers = set(config.get("classifiers", []))
- errors, warnings = self.validate_classifiers(project_classifiers)
+ errors, warnings = self._validate_classifiers(project_classifiers)
check_result["errors"].extend(errors)
check_result["warnings"].extend(warnings)
+ # Validate readme (files must exist)
+ if "readme" in config:
+ errors = self._validate_readme(config["readme"], poetry_file)
+ check_result["errors"].extend(errors)
+
# Verify that lock file is consistent
if self.option("lock") and not self.poetry.locker.is_locked():
check_result["errors"] += ["poetry.lock was not found."]
diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py
index 6347d4585fb..aeed833002a 100644
--- a/tests/console/commands/test_check.py
+++ b/tests/console/commands/test_check.py
@@ -81,6 +81,7 @@ def test_check_invalid(
Error: 'description' is a required property
Error: Project name (invalid) is same as one of its dependencies
Error: Unrecognized classifiers: ['Intended Audience :: Clowns'].
+Error: Declared README file does not exist: never/exists.md
Error: poetry.lock was not found.
Warning: A wildcard Python dependency is ambiguous.\
Consider specifying a more explicit one.
diff --git a/tests/fixtures/invalid_pyproject/pyproject.toml b/tests/fixtures/invalid_pyproject/pyproject.toml
index 55b0b6282af..bafa0936489 100644
--- a/tests/fixtures/invalid_pyproject/pyproject.toml
+++ b/tests/fixtures/invalid_pyproject/pyproject.toml
@@ -4,6 +4,7 @@ version = "1.0.0"
authors = [
"Foo "
]
+readme = "never/exists.md"
license = "INVALID"
classifiers = [
"Environment :: Console",
diff --git a/tests/fixtures/private_pyproject/README.md b/tests/fixtures/private_pyproject/README.md
new file mode 100644
index 00000000000..e69de29bb2d
From 7f79fac5df1768fc041a6a42430bbc30207baea0 Mon Sep 17 00:00:00 2001
From: David Hotham
Date: Sun, 18 Jun 2023 12:32:45 +0100
Subject: [PATCH 03/10] have subprocess deal with text (#8060)
cf python-poetry#7313, python-poetry#7643
---
src/poetry/utils/env/base_env.py | 13 +++----
src/poetry/utils/env/env_manager.py | 54 +++++++++++------------------
src/poetry/vcs/git/system.py | 15 ++++----
tests/utils/test_env.py | 2 +-
4 files changed, 34 insertions(+), 50 deletions(-)
diff --git a/src/poetry/utils/env/base_env.py b/src/poetry/utils/env/base_env.py
index 00957285b62..487a0aa2276 100644
--- a/src/poetry/utils/env/base_env.py
+++ b/src/poetry/utils/env/base_env.py
@@ -14,8 +14,6 @@
from virtualenv.seed.wheels.embed import get_embed_wheel
-from poetry.utils._compat import decode
-from poetry.utils._compat import encode
from poetry.utils.env.exceptions import EnvCommandError
from poetry.utils.env.site_packages import SitePackages
from poetry.utils.helpers import get_real_windows_path
@@ -343,13 +341,14 @@ def _run(self, cmd: list[str], **kwargs: Any) -> str:
try:
if input_:
- output = subprocess.run(
+ output: str = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=stderr,
- input=encode(input_),
+ input=input_,
check=True,
env=env,
+ text=True,
**kwargs,
).stdout
elif call:
@@ -357,11 +356,13 @@ def _run(self, cmd: list[str], **kwargs: Any) -> str:
subprocess.check_call(cmd, stderr=stderr, env=env, **kwargs)
output = ""
else:
- output = subprocess.check_output(cmd, stderr=stderr, env=env, **kwargs)
+ output = subprocess.check_output(
+ cmd, stderr=stderr, env=env, text=True, **kwargs
+ )
except CalledProcessError as e:
raise EnvCommandError(e, input=input_)
- return decode(output)
+ return output
def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
command = self.get_command_from_bin(bin) + list(args)
diff --git a/src/poetry/utils/env/env_manager.py b/src/poetry/utils/env/env_manager.py
index a2f45ad799e..438dc7455f5 100644
--- a/src/poetry/utils/env/env_manager.py
+++ b/src/poetry/utils/env/env_manager.py
@@ -23,7 +23,6 @@
from poetry.toml.file import TOMLFile
from poetry.utils._compat import WINDOWS
-from poetry.utils._compat import decode
from poetry.utils._compat import encode
from poetry.utils.env.exceptions import EnvCommandError
from poetry.utils.env.exceptions import IncorrectEnvError
@@ -67,11 +66,9 @@ def _full_python_path(python: str) -> Path | None:
return None
try:
- executable = decode(
- subprocess.check_output(
- [path_python, "-c", "import sys; print(sys.executable)"],
- ).strip()
- )
+ executable = subprocess.check_output(
+ [path_python, "-c", "import sys; print(sys.executable)"], text=True
+ ).strip()
return Path(executable)
except CalledProcessError:
@@ -115,11 +112,9 @@ def get_python_version(
executable = EnvManager._detect_active_python(io)
if executable:
- python_patch = decode(
- subprocess.check_output(
- [executable, "-c", GET_PYTHON_VERSION_ONELINER],
- ).strip()
- )
+ python_patch = subprocess.check_output(
+ [executable, "-c", GET_PYTHON_VERSION_ONELINER], text=True
+ ).strip()
version = ".".join(str(v) for v in python_patch.split(".")[:precision])
@@ -150,10 +145,8 @@ def activate(self, python: str) -> Env:
raise PythonVersionNotFound(python)
try:
- python_version_string = decode(
- subprocess.check_output(
- [python_path, "-c", GET_PYTHON_VERSION_ONELINER],
- )
+ python_version_string = subprocess.check_output(
+ [python_path, "-c", GET_PYTHON_VERSION_ONELINER], text=True
)
except CalledProcessError as e:
raise EnvCommandError(e)
@@ -334,10 +327,8 @@ def remove(self, python: str) -> Env:
if python_path.is_file():
# Validate env name if provided env is a full path to python
try:
- env_dir = decode(
- subprocess.check_output(
- [python, "-c", GET_ENV_PATH_ONELINER],
- )
+ env_dir = subprocess.check_output(
+ [python, "-c", GET_ENV_PATH_ONELINER], text=True
).strip("\n")
env_name = Path(env_dir).name
if not self.check_env_is_for_current_project(env_name, base_env_name):
@@ -393,10 +384,8 @@ def remove(self, python: str) -> Env:
pass
try:
- python_version_string = decode(
- subprocess.check_output(
- [python, "-c", GET_PYTHON_VERSION_ONELINER],
- )
+ python_version_string = subprocess.check_output(
+ [python, "-c", GET_PYTHON_VERSION_ONELINER], text=True
)
except CalledProcessError as e:
raise EnvCommandError(e)
@@ -485,11 +474,9 @@ def create_venv(
python_patch = ".".join([str(v) for v in sys.version_info[:3]])
python_minor = ".".join([str(v) for v in sys.version_info[:2]])
if executable:
- python_patch = decode(
- subprocess.check_output(
- [executable, "-c", GET_PYTHON_VERSION_ONELINER],
- ).strip()
- )
+ python_patch = subprocess.check_output(
+ [executable, "-c", GET_PYTHON_VERSION_ONELINER], text=True
+ ).strip()
python_minor = ".".join(python_patch.split(".")[:2])
supported_python = self._poetry.package.python_constraint
@@ -533,12 +520,11 @@ def create_venv(
continue
try:
- python_patch = decode(
- subprocess.check_output(
- [python, "-c", GET_PYTHON_VERSION_ONELINER],
- stderr=subprocess.STDOUT,
- ).strip()
- )
+ python_patch = subprocess.check_output(
+ [python, "-c", GET_PYTHON_VERSION_ONELINER],
+ stderr=subprocess.STDOUT,
+ text=True,
+ ).strip()
except CalledProcessError:
continue
diff --git a/src/poetry/vcs/git/system.py b/src/poetry/vcs/git/system.py
index 6be52747ddf..935fc732344 100644
--- a/src/poetry/vcs/git/system.py
+++ b/src/poetry/vcs/git/system.py
@@ -53,15 +53,12 @@ def run(*args: Any, **kwargs: Any) -> str:
git_command = find_git_command()
env = os.environ.copy()
env["GIT_TERMINAL_PROMPT"] = "0"
- return (
- subprocess.check_output(
- git_command + list(args),
- stderr=subprocess.STDOUT,
- env=env,
- )
- .decode()
- .strip()
- )
+ return subprocess.check_output(
+ git_command + list(args),
+ stderr=subprocess.STDOUT,
+ env=env,
+ text=True,
+ ).strip()
@staticmethod
def _check_parameter(parameter: str) -> None:
diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py
index 0b4395e1c9e..269287dea5e 100644
--- a/tests/utils/test_env.py
+++ b/tests/utils/test_env.py
@@ -968,7 +968,7 @@ def test_env_has_symlinks_on_nix(tmp_path: Path, tmp_venv: VirtualEnv) -> None:
def test_run_with_input(tmp_path: Path, tmp_venv: VirtualEnv) -> None:
result = tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT)
- assert result == "Minimal Output" + os.linesep
+ assert result == "Minimal Output\n"
def test_run_with_input_non_zero_return(tmp_path: Path, tmp_venv: VirtualEnv) -> None:
From fedff6da43f33a350598ec0ea85b5dca30178a03 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 20 Jun 2023 11:01:34 +0200
Subject: [PATCH 04/10] [pre-commit.ci] pre-commit autoupdate (#8116)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
.pre-commit-config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d1abfbab76c..812efe2f970 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -26,7 +26,7 @@ repos:
- id: black
- repo: https://github.com/pre-commit/pre-commit
- rev: v3.3.2
+ rev: v3.3.3
hooks:
- id: validate_manifest
From c719dcedb8b39965bb03e69cee63129a18143331 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?=
Date: Sat, 24 Jun 2023 02:45:56 -0700
Subject: [PATCH 05/10] Support non SHA256/SHA384/SHA512 HTTPRepository (#8118)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Bernát Gábor
---
src/poetry/installation/chooser.py | 7 ++++
src/poetry/repositories/http_repository.py | 38 ++++++++++----------
tests/helpers.py | 3 +-
tests/installation/test_chooser.py | 22 ++++++++++++
tests/repositories/fixtures/legacy/demo.html | 6 ++++
5 files changed, 57 insertions(+), 19 deletions(-)
create mode 100644 tests/repositories/fixtures/legacy/demo.html
diff --git a/src/poetry/installation/chooser.py b/src/poetry/installation/chooser.py
index b484504ed9a..4601189d16d 100644
--- a/src/poetry/installation/chooser.py
+++ b/src/poetry/installation/chooser.py
@@ -8,6 +8,7 @@
from poetry.config.config import Config
from poetry.config.config import PackageFilterPolicy
+from poetry.repositories.http_repository import HTTPRepository
from poetry.utils.wheel import Wheel
@@ -103,6 +104,12 @@ def _get_links(self, package: Package) -> list[Link]:
assert link.hash_name is not None
h = link.hash_name + ":" + link.hash
+ if (
+ h not in hashes
+ and link.hash_name not in ("sha256", "sha384", "sha512")
+ and isinstance(repository, HTTPRepository)
+ ):
+ h = repository.calculate_sha256(link) or h
if h not in hashes:
logger.debug(
"Skipping %s as %s checksum does not match expected value",
diff --git a/src/poetry/repositories/http_repository.py b/src/poetry/repositories/http_repository.py
index 9427d72bfe7..ebf8b1a4d77 100644
--- a/src/poetry/repositories/http_repository.py
+++ b/src/poetry/repositories/http_repository.py
@@ -226,24 +226,7 @@ def _links_to_data(self, links: list[Link], data: PackageInfo) -> dict[str, Any]
and link.hash_name not in ("sha256", "sha384", "sha512")
and hasattr(hashlib, link.hash_name)
):
- with self._cached_or_downloaded_file(link) as filepath:
- known_hash = (
- getattr(hashlib, link.hash_name)() if link.hash_name else None
- )
- required_hash = hashlib.sha256()
-
- chunksize = 4096
- with filepath.open("rb") as f:
- while True:
- chunk = f.read(chunksize)
- if not chunk:
- break
- if known_hash:
- known_hash.update(chunk)
- required_hash.update(chunk)
-
- if not known_hash or known_hash.hexdigest() == link.hash:
- file_hash = f"{required_hash.name}:{required_hash.hexdigest()}"
+ file_hash = self.calculate_sha256(link) or file_hash
files.append({"file": link.filename, "hash": file_hash})
@@ -257,6 +240,25 @@ def _links_to_data(self, links: list[Link], data: PackageInfo) -> dict[str, Any]
return data.asdict()
+ def calculate_sha256(self, link: Link) -> str | None:
+ with self._cached_or_downloaded_file(link) as filepath:
+ known_hash = getattr(hashlib, link.hash_name)() if link.hash_name else None
+ required_hash = hashlib.sha256()
+
+ chunksize = 4096
+ with filepath.open("rb") as f:
+ while True:
+ chunk = f.read(chunksize)
+ if not chunk:
+ break
+ if known_hash:
+ known_hash.update(chunk)
+ required_hash.update(chunk)
+
+ if not known_hash or known_hash.hexdigest() == link.hash:
+ return f"{required_hash.name}:{required_hash.hexdigest()}"
+ return None
+
def _get_response(self, endpoint: str) -> requests.Response | None:
url = self._url + endpoint
try:
diff --git a/tests/helpers.py b/tests/helpers.py
index eefe9c74c1a..3dd5b1d8915 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -36,6 +36,7 @@
from poetry.installation.operations.operation import Operation
from poetry.poetry import Poetry
+ from poetry.utils.authenticator import Authenticator
FIXTURE_PATH = Path(__file__).parent / "fixtures"
@@ -120,7 +121,7 @@ def mock_clone(
return MockDulwichRepo(dest)
-def mock_download(url: str, dest: Path) -> None:
+def mock_download(url: str, dest: Path, session: Authenticator | None = None) -> None:
parts = urllib.parse.urlparse(url)
fixture = FIXTURE_PATH / parts.path.lstrip("/")
diff --git a/tests/installation/test_chooser.py b/tests/installation/test_chooser.py
index fbd3861ad05..6c9930f899a 100644
--- a/tests/installation/test_chooser.py
+++ b/tests/installation/test_chooser.py
@@ -405,3 +405,25 @@ def test_chooser_throws_an_error_if_package_hashes_do_not_match(
with pytest.raises(RuntimeError) as e:
chooser.choose_for(package)
assert files[0]["hash"] in str(e)
+
+
+@pytest.mark.usefixtures("mock_legacy")
+def test_chooser_md5_remote_fallback_to_sha256_inline_calculation(
+ env: MockEnv, pool: RepositoryPool
+) -> None:
+ chooser = Chooser(pool, env)
+ package = Package(
+ "demo",
+ "0.1.0",
+ source_type="legacy",
+ source_reference="foo",
+ source_url="https://foo.bar/simple/",
+ )
+ package.files = [
+ {
+ "hash": "sha256:9fa123ad707a5c6c944743bf3e11a0e80d86cb518d3cf25320866ca3ef43e2ad", # noqa: E501
+ "filename": "demo-0.1.0.tar.gz",
+ }
+ ]
+ res = chooser.choose_for(package)
+ assert res.filename == "demo-0.1.0.tar.gz"
diff --git a/tests/repositories/fixtures/legacy/demo.html b/tests/repositories/fixtures/legacy/demo.html
new file mode 100644
index 00000000000..ae9506cd5a1
--- /dev/null
+++ b/tests/repositories/fixtures/legacy/demo.html
@@ -0,0 +1,6 @@
+
+Simple Index
+
+demo-0.1.0.tar.gz
+
+
From 7ee2a8b4944fc15afd8a6c40cc399974fe89f80e Mon Sep 17 00:00:00 2001
From: Peter
Date: Mon, 26 Jun 2023 02:21:47 +1000
Subject: [PATCH 06/10] Add warning about pip ignoring lock files (#8117)
---
docs/basic-usage.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/basic-usage.md b/docs/basic-usage.md
index a22d5280f71..dd872f4a1c5 100644
--- a/docs/basic-usage.md
+++ b/docs/basic-usage.md
@@ -234,6 +234,10 @@ Even if you develop alone, in six months when reinstalling the project you can f
the dependencies installed are still working even if your dependencies released many new versions since then.
(See note below about using the update command.)
+{{% warning %}} If you have added the recommended [`[build-system]`]({{< relref "pyproject#poetry-and-pep-517" >}}) section to your project's pyproject.toml then you _can_ successfully install your project and its dependencies into a virtual environment using a command like `pip install -e .`. However, pip will not use the lock file to determine dependency versions as the poetry-core build system is intended for library developers (see next section).
+{{% /warning %}}
+
+
#### As a library developer
Library developers have more to consider. Your users are application developers, and your library will run in a Python environment you don't control.
From 5b0cb4be5931a28d7b23083a31e4e2103af44e15 Mon Sep 17 00:00:00 2001
From: Davi Gray <124840911+Davi-Gray@users.noreply.github.com>
Date: Tue, 27 Jun 2023 10:35:50 -0500
Subject: [PATCH 07/10] Clarify use of virtualenvs.in-project setting. (#8126)
---
docs/configuration.md | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index d9c074262c3..43be234a587 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -309,12 +309,21 @@ might contain additional Python packages as well.
Create the virtualenv inside the project's root directory.
-If not set explicitly, `poetry` by default will create virtual environment under
-`{cache-dir}/virtualenvs` or use the `{project-dir}/.venv` directory when one is available.
+If not set explicitly, `poetry` by default will create a virtual environment under
+`{cache-dir}/virtualenvs` or use the `{project-dir}/.venv` directory if one already exists.
If set to `true`, the virtualenv will be created and expected in a folder named
`.venv` within the root directory of the project.
+{{% note %}}
+If a virtual environment has already been created for the project under `{cache-dir}/virtualenvs`, setting this variable to `true` will not cause `poetry` to create or use a local virtual environment.
+
+In order for this setting to take effect for a project already in that state, you must delete the virtual environment folder located in `{cache-dir}/virtualenvs`.
+
+You can find out where the current project's virtual environment (if there is one) is stored
+with the command `poetry env info --path`.
+{{% /note %}}
+
If set to `false`, `poetry` will ignore any existing `.venv` directory.
### `virtualenvs.options.always-copy`
From d508c9f05dbebdede74fdee5a995d81bb405d7df Mon Sep 17 00:00:00 2001
From: Micael Jarniac
Date: Mon, 3 Jul 2023 19:11:21 -0300
Subject: [PATCH 08/10] docs(pre-commit): fix FAQ grammar (#8150)
---
docs/pre-commit-hooks.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/pre-commit-hooks.md b/docs/pre-commit-hooks.md
index ab833dab3f3..e526a739079 100644
--- a/docs/pre-commit-hooks.md
+++ b/docs/pre-commit-hooks.md
@@ -109,16 +109,16 @@ repos:
`pre-commit autoupdate` updates the `rev` for each repository defined in your `.pre-commit-config.yaml`
to the latest available tag in the default branch.
-Poetry follows a branching strategy, where the default branch is the active development branch
-and fixes gets back ported to stable branches. New tags are assigned in these stable branches.
+Poetry follows a branching strategy where the default branch is the active development branch,
+and fixes get backported to stable branches. New tags are assigned in these stable branches.
`pre-commit` does not support such a branching strategy and has decided to not implement
-an option, either on the [user side](https://github.com/pre-commit/pre-commit/issues/2512)
-or [hook author side](https://github.com/pre-commit/pre-commit/issues/2508), to define a branch for lookup the latest
-available tag.
+an option, either on the [user's side](https://github.com/pre-commit/pre-commit/issues/2512)
+or the [hook author's side](https://github.com/pre-commit/pre-commit/issues/2508), to define a branch for looking
+up the latest available tag.
Thus, `pre-commit autoupdate` is not usable for the hooks described here.
-You can avoid changing the `rev` to an unexpected value, by using the `--repo` parameter (may be specified multiple
-times), to explicit list repositories that should be updated. An option to explicit exclude
+You can avoid changing the `rev` to an unexpected value by using the `--repo` parameter (may be specified multiple
+times), to explicitly list repositories that should be updated. An option to explicitly exclude
repositories [will not be implemented](https://github.com/pre-commit/pre-commit/issues/1959) into `pre-commit`.
From 79f26683423f12d701d739ec7cab5bdf12e9d97a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Randy=20D=C3=B6ring?=
<30527984+radoering@users.noreply.github.com>
Date: Tue, 4 Jul 2023 06:36:28 +0200
Subject: [PATCH 09/10] sources: change future warning that PyPI will only be
disabled automatically if there are no primary sources (#8151)
---
docs/repositories.md | 2 +-
src/poetry/factory.py | 6 +++---
tests/test_factory.py | 8 ++++++--
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/docs/repositories.md b/docs/repositories.md
index 541236c8d04..0446d3ec1cc 100644
--- a/docs/repositories.md
+++ b/docs/repositories.md
@@ -164,7 +164,7 @@ poetry source add --priority=default foo https://foo.bar/simple/
{{% warning %}}
In a future version of Poetry, PyPI will be disabled automatically
-if there is at least one custom source configured with another priority than `explicit`.
+if at least one custom primary source is configured.
If you are using custom sources in addition to PyPI, you should configure PyPI explicitly
with a certain priority, e.g.
diff --git a/src/poetry/factory.py b/src/poetry/factory.py
index 5e926b6177a..828ce26bc24 100644
--- a/src/poetry/factory.py
+++ b/src/poetry/factory.py
@@ -181,12 +181,12 @@ def create_pool(
else:
from poetry.repositories.pypi_repository import PyPiRepository
- if pool.repositories:
+ if pool.has_primary_repositories():
io.write_error_line(
""
"Warning: In a future version of Poetry, PyPI will be disabled"
- " automatically if at least one custom source is configured"
- " with another priority than 'explicit'. In order to avoid"
+ " automatically if at least one custom primary source is"
+ " configured. In order to avoid"
" a breaking change and make your pyproject.toml forward"
" compatible, add PyPI explicitly via 'poetry source add pypi'."
" By the way, this has the advantage that you can set the"
diff --git a/tests/test_factory.py b/tests/test_factory.py
index b57030709ea..fcb0bbbc1d2 100644
--- a/tests/test_factory.py
+++ b/tests/test_factory.py
@@ -420,7 +420,8 @@ def test_poetry_with_no_default_source(fixture_dir: FixtureDirGetter) -> None:
def test_poetry_with_supplemental_source(
fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
- poetry = Factory().create_poetry(fixture_dir("with_supplemental_source"))
+ io = BufferedIO()
+ poetry = Factory().create_poetry(fixture_dir("with_supplemental_source"), io=io)
assert poetry.pool.has_repository("PyPI")
assert poetry.pool.get_priority("PyPI") is Priority.DEFAULT
@@ -429,12 +430,14 @@ def test_poetry_with_supplemental_source(
assert poetry.pool.get_priority("supplemental") is Priority.SUPPLEMENTAL
assert isinstance(poetry.pool.repository("supplemental"), LegacyRepository)
assert {repo.name for repo in poetry.pool.repositories} == {"PyPI", "supplemental"}
+ assert io.fetch_error() == ""
def test_poetry_with_explicit_source(
fixture_dir: FixtureDirGetter, with_simple_keyring: None
) -> None:
- poetry = Factory().create_poetry(fixture_dir("with_explicit_source"))
+ io = BufferedIO()
+ poetry = Factory().create_poetry(fixture_dir("with_explicit_source"), io=io)
assert len(poetry.pool.repositories) == 1
assert len(poetry.pool.all_repositories) == 2
@@ -444,6 +447,7 @@ def test_poetry_with_explicit_source(
assert poetry.pool.has_repository("explicit")
assert isinstance(poetry.pool.repository("explicit"), LegacyRepository)
assert {repo.name for repo in poetry.pool.repositories} == {"PyPI"}
+ assert io.fetch_error() == ""
def test_poetry_with_explicit_pypi_and_other(
From d31dfa8312e72f6f16fefb5203a5e3bcac7daece Mon Sep 17 00:00:00 2001
From: johnthagen
Date: Thu, 6 Jul 2023 09:09:24 -0400
Subject: [PATCH 10/10] Document official Poetry badge (#8066)
---
README.md | 1 +
docs/community.md | 30 ++++++++++++++++++++++++++++++
2 files changed, 31 insertions(+)
create mode 100644 docs/community.md
diff --git a/README.md b/README.md
index 4de19ca2f08..d0669832d13 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
# Poetry: Python packaging and dependency management made easy
+[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
[![Stable Version](https://img.shields.io/pypi/v/poetry?label=stable)][PyPI Releases]
[![Pre-release Version](https://img.shields.io/github/v/release/python-poetry/poetry?label=pre-release&include_prereleases&sort=semver)][PyPI Releases]
[![Python Versions](https://img.shields.io/pypi/pyversions/poetry)][PyPI]
diff --git a/docs/community.md b/docs/community.md
new file mode 100644
index 00000000000..d29a08f7936
--- /dev/null
+++ b/docs/community.md
@@ -0,0 +1,30 @@
+---
+title: "Community"
+draft: false
+type: docs
+layout: single
+
+menu:
+ docs:
+ weight: 105
+---
+
+# Community
+
+## Badge
+
+For any projects using Poetry, you may add its official badge somewhere prominent like the README.
+
+[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
+
+**Markdown**
+```md
+[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
+```
+
+**reStructuredText**
+```rst
+.. image:: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json
+ :alt: Poetry
+ :target: https://python-poetry.org/
+```