Commit 735235a0 by Randy Döring Committed by GitHub

installer: remove old installer and config setting `experimental.new-installer` (#7356)

parent 6b4b5391
......@@ -555,7 +555,7 @@ $ poetry cache clear pypi --all
- Added in info output to `poetry lock --check` ([#5081](https://github.com/python-poetry/poetry/pull/5081)).
- Added new argument `--all` for `poetry env remove` to delete all venv of a project at once ([#3212](https://github.com/python-poetry/poetry/pull/3212)).
- Added new argument `--without-urls` for `poetry export` to exclude source repository urls from the exported file ([#4763](https://github.com/python-poetry/poetry/pull/4763)).
- Added a `new installer.max-workers` property to the configuration ([#3516](https://github.com/python-poetry/poetry/pull/3516)).
- Added a new `installer.max-workers` property to the configuration ([#3516](https://github.com/python-poetry/poetry/pull/3516)).
- Added experimental option `virtualenvs.prefer-active-python` to detect current activated python ([#4852](https://github.com/python-poetry/poetry/pull/4852)).
- Added better windows shell support ([#5053](https://github.com/python-poetry/poetry/pull/5053)).
......
......@@ -229,9 +229,6 @@ specific packages.
| `package[,package,..]` | Disallow binary distributions for specified packages only. |
{{% note %}}
This configuration is only respected when using the new installer. If you have disabled it please
consider re-enabling it.
As with all configurations described here, this is a user specific configuration. This means that this
is not taken into consideration when a lockfile is generated or dependencies are resolved. This is
applied only when selecting which distribution for dependency should be installed into a Poetry managed
......
......@@ -124,7 +124,6 @@ class Config:
"prompt": "{project_name}-py{python_version}",
},
"experimental": {
"new-installer": True,
"system-git-client": False,
},
"installer": {
......@@ -276,7 +275,6 @@ class Config:
"virtualenvs.options.always-copy",
"virtualenvs.options.system-site-packages",
"virtualenvs.options.prefer-active-python",
"experimental.new-installer",
"experimental.system-git-client",
"installer.modern-installation",
"installer.parallel",
......
......@@ -335,10 +335,6 @@ class Application(BaseApplication):
poetry.config,
disable_cache=poetry.disable_cache,
)
use_executor = poetry.config.get("experimental.new-installer", False)
if not use_executor:
# only set if false because the method is deprecated
installer.use_executor(False)
command.set_installer(installer)
def _load_plugins(self, io: IO | None = None) -> None:
......
......@@ -68,7 +68,6 @@ To remove a repository (repo is a short alias for repositories):
),
"virtualenvs.path": (str, lambda val: str(Path(val))),
"virtualenvs.prefer-active-python": (boolean_validator, boolean_normalizer),
"experimental.new-installer": (boolean_validator, boolean_normalizer),
"experimental.system-git-client": (boolean_validator, boolean_normalizer),
"installer.modern-installation": (boolean_validator, boolean_normalizer),
"installer.parallel": (boolean_validator, boolean_normalizer),
......
......@@ -105,11 +105,6 @@ dependencies and not including the current project, run the command with the
from poetry.masonry.builders.editable import EditableBuilder
use_executor = self.poetry.config.get("experimental.new-installer", False)
if not use_executor:
# only set if false because the method is deprecated
self.installer.use_executor(False)
if self.option("extras") and self.option("all-extras"):
self.line_error(
"<error>You cannot specify explicit"
......
......@@ -35,11 +35,6 @@ file.
loggers = ["poetry.repositories.pypi_repository"]
def handle(self) -> int:
use_executor = self.poetry.config.get("experimental.new-installer", False)
if not use_executor:
# only set if false because the method is deprecated
self.installer.use_executor(False)
if self.option("check"):
if self.poetry.locker.is_locked() and self.poetry.locker.is_fresh():
self.line("poetry.lock is consistent with pyproject.toml.")
......
......@@ -40,12 +40,6 @@ class UpdateCommand(InstallerCommand):
def handle(self) -> int:
packages = self.argument("packages")
use_executor = self.poetry.config.get("experimental.new-installer", False)
if not use_executor:
# only set if false because the method is deprecated
self.installer.use_executor(False)
if packages:
self.installer.whitelist({name: "*" for name in packages})
......
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class BaseInstaller:
def install(self, package: Package) -> None:
raise NotImplementedError
def update(self, source: Package, target: Package) -> None:
raise NotImplementedError
def remove(self, package: Package) -> None:
raise NotImplementedError
from __future__ import annotations
import warnings
from typing import TYPE_CHECKING
from cleo.io.null_io import NullIO
......@@ -11,13 +9,11 @@ from poetry.installation.executor import Executor
from poetry.installation.operations import Install
from poetry.installation.operations import Uninstall
from poetry.installation.operations import Update
from poetry.installation.pip_installer import PipInstaller
from poetry.repositories import Repository
from poetry.repositories import RepositoryPool
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.lockfile_repository import LockfileRepository
from poetry.utils.extras import get_extra_package_names
from poetry.utils.helpers import pluralize
if TYPE_CHECKING:
......@@ -28,7 +24,6 @@ if TYPE_CHECKING:
from poetry.core.packages.project_package import ProjectPackage
from poetry.config.config import Config
from poetry.installation.base_installer import BaseInstaller
from poetry.installation.operations.operation import Operation
from poetry.packages import Locker
from poetry.utils.env import Env
......@@ -74,9 +69,7 @@ class Installer:
)
self._executor = executor
self._use_executor = True
self._installer = self._get_installer()
if installed is None:
installed = self._get_installed()
......@@ -86,10 +79,6 @@ class Installer:
def executor(self) -> Executor:
return self._executor
@property
def installer(self) -> BaseInstaller:
return self._installer
def set_package(self, package: ProjectPackage) -> Installer:
self._package = package
......@@ -187,19 +176,6 @@ class Installer:
return self
def use_executor(self, use_executor: bool = True) -> Installer:
warnings.warn(
(
"Calling use_executor() is deprecated since it's true by default now"
" and deactivating it will be removed in a future release."
),
DeprecationWarning,
stacklevel=2,
)
self._use_executor = use_executor
return self
def _do_refresh(self) -> int:
from poetry.puzzle.solver import Solver
......@@ -384,128 +360,8 @@ class Installer:
self._io.write_line("<info>Writing lock file</>")
def _execute(self, operations: list[Operation]) -> int:
if self._use_executor:
return self._executor.execute(operations)
self._io.write_error(
"<warning>"
"Setting `experimental.new-installer` to false is deprecated and"
" slated for removal in an upcoming minor release.\n"
"(Despite of the setting's name the new installer is not experimental!)"
"</warning>"
)
if not operations and (self._execute_operations or self._dry_run):
self._io.write_line("No dependencies to install or update")
if operations and (self._execute_operations or self._dry_run):
installs = 0
updates = 0
uninstalls = 0
skipped = 0
for op in operations:
if op.skipped:
skipped += 1
elif op.job_type == "install":
installs += 1
elif op.job_type == "update":
updates += 1
elif op.job_type == "uninstall":
uninstalls += 1
self._io.write_line("")
self._io.write("Package operations: ")
self._io.write(f"<info>{installs}</> install{pluralize(installs)}, ")
self._io.write(f"<info>{updates}</> update{pluralize(updates)}, ")
self._io.write(f"<info>{uninstalls}</> removal{pluralize(uninstalls)}")
if skipped and self.is_verbose():
self._io.write(f", <info>{skipped}</> skipped")
self._io.write_line("")
self._io.write_line("")
for op in operations:
self._execute_operation(op)
return 0
def _execute_operation(self, operation: Operation) -> None:
"""
Execute a given operation.
"""
method = operation.job_type
getattr(self, f"_execute_{method}")(operation)
def _execute_install(self, operation: Install) -> None:
target = operation.package
if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line(
f" - Skipping <c1>{target.pretty_name}</c1>"
f" (<c2>{target.full_pretty_version}</c2>) {operation.skip_reason}"
)
return
if self._execute_operations or self.is_dry_run():
self._io.write_line(
f" - Installing <c1>{target.pretty_name}</c1>"
f" (<c2>{target.full_pretty_version}</c2>)"
)
if not self._execute_operations:
return
self._installer.install(operation.package)
def _execute_update(self, operation: Update) -> None:
source = operation.initial_package
target = operation.target_package
if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line(
f" - Skipping <c1>{target.pretty_name}</c1> "
f"(<c2>{target.full_pretty_version}</c2>) {operation.skip_reason}"
)
return
if self._execute_operations or self.is_dry_run():
self._io.write_line(
f" - Updating <c1>{target.pretty_name}</c1>"
f" (<c2>{source.full_pretty_version}</c2> ->"
f" <c2>{target.full_pretty_version}</c2>)"
)
if not self._execute_operations:
return
self._installer.update(source, target)
def _execute_uninstall(self, operation: Uninstall) -> None:
target = operation.package
if operation.skipped:
if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
self._io.write_line(
f" - Not removing <c1>{target.pretty_name}</c1>"
f" (<c2>{target.pretty_version}</c2>) {operation.skip_reason}"
)
return
if self._execute_operations or self.is_dry_run():
self._io.write_line(
f" - Removing <c1>{target.pretty_name}</c1>"
f" (<c2>{target.pretty_version}</c2>)"
)
if not self._execute_operations:
return
self._installer.remove(operation.package)
def _populate_lockfile_repo(
self, repo: LockfileRepository, ops: Iterable[Operation]
) -> None:
......@@ -588,8 +444,5 @@ class Installer:
return get_extra_package_names(repo.packages, extras, self._extras)
def _get_installer(self) -> BaseInstaller:
return PipInstaller(self._env, self._io, self._pool)
def _get_installed(self) -> InstalledRepository:
return InstalledRepository.load(self._env)
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.installation.base_installer import BaseInstaller
if TYPE_CHECKING:
from poetry.core.packages.package import Package
class NoopInstaller(BaseInstaller):
def __init__(self) -> None:
self._installs: list[Package] = []
self._updates: list[tuple[Package, Package]] = []
self._removals: list[Package] = []
@property
def installs(self) -> list[Package]:
return self._installs
@property
def updates(self) -> list[tuple[Package, Package]]:
return self._updates
@property
def removals(self) -> list[Package]:
return self._removals
def install(self, package: Package) -> None:
self._installs.append(package)
def update(self, source: Package, target: Package) -> None:
self._updates.append((source, target))
def remove(self, package: Package) -> None:
self._removals.append(package)
......@@ -52,7 +52,6 @@ def test_list_displays_default_value_if_not_set(
cache_dir = json.dumps(str(config_cache_dir))
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.system-git-client = false
installer.max-workers = null
installer.modern-installation = true
......@@ -82,7 +81,6 @@ def test_list_displays_set_get_setting(
cache_dir = json.dumps(str(config_cache_dir))
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.system-git-client = false
installer.max-workers = null
installer.modern-installation = true
......@@ -136,7 +134,6 @@ def test_list_displays_set_get_local_setting(
cache_dir = json.dumps(str(config_cache_dir))
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.system-git-client = false
installer.max-workers = null
installer.modern-installation = true
......@@ -174,7 +171,6 @@ def test_list_must_not_display_sources_from_pyproject_toml(
cache_dir = json.dumps(str(config_cache_dir))
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.system-git-client = false
installer.max-workers = null
installer.modern-installation = true
......
......@@ -11,7 +11,6 @@ from cleo.testers.application_tester import ApplicationTester
from cleo.testers.command_tester import CommandTester
from poetry.installation import Installer
from poetry.installation.noop_installer import NoopInstaller
from poetry.utils.env import MockEnv
from tests.helpers import MOCK_DEFAULT_GIT_REVISION
from tests.helpers import PoetryTestApplication
......@@ -35,11 +34,6 @@ if TYPE_CHECKING:
from tests.types import ProjectFactory
@pytest.fixture()
def installer() -> NoopInstaller:
return NoopInstaller()
@pytest.fixture
def env(tmp_path: Path) -> MockEnv:
path = tmp_path / ".venv"
......@@ -50,15 +44,10 @@ def env(tmp_path: Path) -> MockEnv:
@pytest.fixture(autouse=True)
def setup(
mocker: MockerFixture,
installer: NoopInstaller,
installed: Repository,
config: Config,
env: MockEnv,
) -> Iterator[None]:
# Set Installer's installer
p = mocker.patch("poetry.installation.installer.Installer._get_installer")
p.return_value = installer
# Do not run pip commands of the executor
mocker.patch("poetry.installation.executor.Executor.run_pip")
......@@ -117,11 +106,6 @@ def app_tester(app: PoetryTestApplication) -> ApplicationTester:
return ApplicationTester(app)
@pytest.fixture
def new_installer_disabled(config: Config) -> None:
config.merge({"experimental": {"new-installer": False}})
@pytest.fixture()
def executor(poetry: Poetry, config: Config, env: MockEnv) -> TestExecutor:
return TestExecutor(env, poetry.pool, config, NullIO())
......
......@@ -20,9 +20,8 @@ from poetry.core.packages.package import Package
from poetry.core.packages.project_package import ProjectPackage
from poetry.factory import Factory
from poetry.installation import Installer as BaseInstaller
from poetry.installation import Installer
from poetry.installation.executor import Executor as BaseExecutor
from poetry.installation.noop_installer import NoopInstaller
from poetry.packages import Locker as BaseLocker
from poetry.repositories import Repository
from poetry.repositories import RepositoryPool
......@@ -51,11 +50,6 @@ if TYPE_CHECKING:
RESERVED_PACKAGES = ("pip", "setuptools", "wheel")
class Installer(BaseInstaller):
def _get_installer(self) -> NoopInstaller:
return NoopInstaller()
class Executor(BaseExecutor):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
......@@ -190,7 +184,7 @@ def installer(
installed: CustomInstalledRepository,
config: Config,
) -> Installer:
installer = Installer(
return Installer(
NullIO(),
env,
package,
......@@ -200,7 +194,6 @@ def installer(
installed=installed,
executor=Executor(env, pool, config, NullIO()),
)
return installer
def fixture(name: str) -> dict[str, Any]:
......@@ -2264,7 +2257,6 @@ def test_installer_uses_prereleases_if_they_are_compatible(
result = installer.run()
assert result == 0
del installer.installer.installs[:]
locker.locked(True)
locker.mock_lock_data(locker.written_data)
......
from __future__ import annotations
import re
import shutil
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from cleo.io.null_io import NullIO
from poetry.core.packages.package import Package
from poetry.installation.pip_installer import PipInstaller
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.repository_pool import Priority
from poetry.repositories.repository_pool import RepositoryPool
from poetry.utils.authenticator import RepositoryCertificateConfig
from poetry.utils.env import NullEnv
if TYPE_CHECKING:
from pytest_mock import MockerFixture
from poetry.utils.env import VirtualEnv
from tests.conftest import Config
from tests.types import FixtureDirGetter
@pytest.fixture
def package_git() -> Package:
package = Package(
"demo",
"1.0.0",
source_type="git",
source_url="git@github.com:demo/demo.git",
source_reference="master",
)
return package
@pytest.fixture
def package_git_with_subdirectory() -> Package:
package = Package(
"subdirectories",
"2.0.0",
source_type="git",
source_url="https://github.com/demo/subdirectories.git",
source_reference="master",
source_subdirectory="two",
)
return package
@pytest.fixture
def pool() -> RepositoryPool:
return RepositoryPool()
@pytest.fixture()
def env(tmp_path: Path) -> NullEnv:
return NullEnv(path=tmp_path)
@pytest.fixture
def installer(pool: RepositoryPool, env: NullEnv) -> PipInstaller:
return PipInstaller(env, NullIO(), pool)
def test_requirement(installer: PipInstaller) -> None:
package = Package("ipython", "7.5.0")
package.files = [
{"file": "foo-0.1.0.tar.gz", "hash": "md5:dbdc53e3918f28fa335a173432402a00"},
{
"file": "foo.0.1.0.whl",
"hash": "e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26",
},
]
result = installer.requirement(package, formatted=True)
expected = (
"ipython==7.5.0 "
"--hash md5:dbdc53e3918f28fa335a173432402a00 "
"--hash sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26"
"\n"
)
assert result == expected
def test_requirement_source_type_url(env: NullEnv) -> None:
installer = PipInstaller(env, NullIO(), RepositoryPool())
foo = Package(
"foo",
"0.0.0",
source_type="url",
source_url="https://somewhere.com/releases/foo-1.0.0.tar.gz",
)
result = installer.requirement(foo, formatted=True)
expected = f"{foo.source_url}#egg={foo.name}"
assert result == expected
def test_requirement_git_subdirectory(
pool: RepositoryPool, package_git_with_subdirectory: Package, env: NullEnv
) -> None:
installer = PipInstaller(env, NullIO(), pool)
result = installer.requirement(package_git_with_subdirectory)
expected = (
"git+https://github.com/demo/subdirectories.git"
"@master#egg=subdirectories&subdirectory=two"
)
assert result == expected
installer.install(package_git_with_subdirectory)
assert len(env.executed) == 1
cmd = env.executed[0]
assert Path(cmd[-1]).parts[-3:] == ("demo", "subdirectories", "two")
def test_requirement_git_develop_false(
installer: PipInstaller, package_git: Package
) -> None:
package_git.develop = False
result = installer.requirement(package_git)
expected = "git+git@github.com:demo/demo.git@master#egg=demo"
assert result == expected
def test_install_with_non_pypi_default_repository(
pool: RepositoryPool, installer: PipInstaller
) -> None:
default = LegacyRepository("default", "https://default.com")
another = LegacyRepository("another", "https://another.com")
pool.add_repository(default, priority=Priority.DEFAULT)
pool.add_repository(another)
foo = Package(
"foo",
"0.0.0",
source_type="legacy",
source_reference=default.name,
source_url=default.url,
)
bar = Package(
"bar",
"0.1.0",
source_type="legacy",
source_reference=another.name,
source_url=another.url,
)
installer.install(foo)
installer.install(bar)
@pytest.mark.parametrize(
("key", "option"),
[
("client_cert", "client-cert"),
("cert", "cert"),
],
)
def test_install_with_certs(
mocker: MockerFixture, key: str, option: str, env: NullEnv
) -> None:
client_path = "path/to/client.pem"
mocker.patch(
"poetry.utils.authenticator.Authenticator.get_certs_for_url",
return_value=RepositoryCertificateConfig(**{key: Path(client_path)}),
)
default = LegacyRepository("default", "https://foo.bar")
pool = RepositoryPool()
pool.add_repository(default, priority=Priority.DEFAULT)
installer = PipInstaller(env, NullIO(), pool)
foo = Package(
"foo",
"0.0.0",
source_type="legacy",
source_reference=default.name,
source_url=default.url,
)
installer.install(foo)
assert len(env.executed) == 1
cmd = env.executed[0]
assert f"--{option}" in cmd
cert_index = cmd.index(f"--{option}")
# Need to do the str(Path()) bit because Windows paths get modified by Path
assert cmd[cert_index + 1] == str(Path(client_path))
def test_requirement_git_develop_true(
installer: PipInstaller, package_git: Package
) -> None:
package_git.develop = True
result = installer.requirement(package_git)
expected = ["-e", "git+git@github.com:demo/demo.git@master#egg=demo"]
assert result == expected
def test_uninstall_git_package_nspkg_pth_cleanup(
mocker: MockerFixture, tmp_venv: VirtualEnv, pool: RepositoryPool
) -> None:
# this test scenario requires a real installation using the pip installer
installer = PipInstaller(tmp_venv, NullIO(), pool)
# use a namespace package
package = Package(
"namespace-package-one",
"1.0.0",
source_type="git",
source_url="https://github.com/demo/namespace-package-one.git",
source_reference="master",
)
# in order to reproduce the scenario where the git source is removed prior to proper
# clean up of nspkg.pth file, we need to make sure the fixture is copied and not
# symlinked into the git src directory
def copy_only(source: Path, dest: Path) -> None:
if dest.exists():
dest.unlink()
if source.is_dir():
shutil.copytree(str(source), str(dest))
else:
shutil.copyfile(str(source), str(dest))
mocker.patch("tests.helpers.copy_or_symlink", new=copy_only)
# install package and then remove it
installer.install(package)
installer.remove(package)
pth_file = Path(f"{package.name}-nspkg.pth")
assert not tmp_venv.site_packages.exists(pth_file)
# any command in the virtual environment should trigger the error message
output = tmp_venv.run("python", "-m", "site")
assert not re.match(rf"Error processing line 1 of .*{pth_file}", output)
def test_install_with_trusted_host(config: Config, env: NullEnv) -> None:
config.merge({"certificates": {"default": {"cert": False}}})
default = LegacyRepository("default", "https://foo.bar")
pool = RepositoryPool()
pool.add_repository(default, priority=Priority.DEFAULT)
installer = PipInstaller(env, NullIO(), pool)
foo = Package(
"foo",
"0.0.0",
source_type="legacy",
source_reference=default.name,
source_url=default.url,
)
installer.install(foo)
assert len(env.executed) == 1
cmd = env.executed[0]
assert "--trusted-host" in cmd
cert_index = cmd.index("--trusted-host")
assert cmd[cert_index + 1] == "foo.bar"
def test_install_directory_fallback_on_poetry_create_error(
mocker: MockerFixture,
tmp_venv: VirtualEnv,
pool: RepositoryPool,
fixture_dir: FixtureDirGetter,
) -> None:
mock_create_poetry = mocker.patch(
"poetry.factory.Factory.create_poetry", side_effect=RuntimeError
)
mock_sdist_builder = mocker.patch("poetry.core.masonry.builders.sdist.SdistBuilder")
mock_editable_builder = mocker.patch(
"poetry.masonry.builders.editable.EditableBuilder"
)
mock_pip_install = mocker.patch("poetry.installation.pip_installer.pip_install")
package = Package(
"demo",
"1.0.0",
source_type="directory",
source_url=str(fixture_dir("inspection") / "demo_poetry_package"),
)
installer = PipInstaller(tmp_venv, NullIO(), pool)
installer.install_directory(package)
assert mock_create_poetry.call_count == 1
assert mock_sdist_builder.call_count == 0
assert mock_editable_builder.call_count == 0
assert mock_pip_install.call_count == 1
assert mock_pip_install.call_args[1].get("deps") is None
assert mock_pip_install.call_args[1].get("upgrade") is True
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment