diff --git a/src/poetry/console/commands/install.py b/src/poetry/console/commands/install.py index 9895745b48b..ad5b574ab37 100644 --- a/src/poetry/console/commands/install.py +++ b/src/poetry/console/commands/install.py @@ -93,6 +93,19 @@ def activated_groups(self) -> set[str]: def _alternative_sync_command(self) -> str: return "poetry sync" + @property + def _with_synchronization(self) -> bool: + with_synchronization = self.option("sync") + if with_synchronization: + self.line_error( + "The `--sync` option is" + " deprecated and slated for removal in the next minor release" + " after June 2025, use the" + f" `{self._alternative_sync_command}`" + " command instead." + ) + return bool(with_synchronization) + def handle(self) -> int: from poetry.core.masonry.utils.module import ModuleOrPackageNotFoundError @@ -150,20 +163,10 @@ def handle(self) -> int: self.installer.extras(extras) - with_synchronization = self.option("sync") - if with_synchronization: - self.line_error( - "The `--sync` option is" - " deprecated and slated for removal in the next minor release" - " after June 2025, use the" - f" `{self._alternative_sync_command}`" - " command instead." - ) - self.installer.only_groups(self.activated_groups) self.installer.skip_directory(self.option("no-directory")) self.installer.dry_run(self.option("dry-run")) - self.installer.requires_synchronization(with_synchronization) + self.installer.requires_synchronization(self._with_synchronization) self.installer.executor.enable_bytecode_compilation(self.option("compile")) self.installer.verbose(self.io.is_verbose()) diff --git a/src/poetry/console/commands/self/sync.py b/src/poetry/console/commands/self/sync.py index 0af576c7b80..05fc0c25a3b 100644 --- a/src/poetry/console/commands/self/sync.py +++ b/src/poetry/console/commands/self/sync.py @@ -29,3 +29,7 @@ class SelfSyncCommand(SelfInstallCommand): You can add more packages using the self add command and remove them using \ the self remove command. """ + + @property + def _with_synchronization(self) -> bool: + return True diff --git a/src/poetry/console/commands/sync.py b/src/poetry/console/commands/sync.py index 29d6c1be873..4723483f725 100644 --- a/src/poetry/console/commands/sync.py +++ b/src/poetry/console/commands/sync.py @@ -34,3 +34,7 @@ class SyncCommand(InstallCommand): If you want to use Poetry only for dependency management but not for packaging, you can set the "package-mode" to false in your pyproject.toml file. """ + + @property + def _with_synchronization(self) -> bool: + return True diff --git a/src/poetry/utils/env/__init__.py b/src/poetry/utils/env/__init__.py index c3834f4fbaf..9ab3b37d021 100644 --- a/src/poetry/utils/env/__init__.py +++ b/src/poetry/utils/env/__init__.py @@ -21,7 +21,6 @@ from poetry.utils.env.script_strings import GET_ENV_PATH_ONELINER from poetry.utils.env.script_strings import GET_ENVIRONMENT_INFO from poetry.utils.env.script_strings import GET_PATHS -from poetry.utils.env.script_strings import GET_PATHS_FOR_GENERIC_ENVS from poetry.utils.env.script_strings import GET_PYTHON_VERSION_ONELINER from poetry.utils.env.script_strings import GET_SYS_PATH from poetry.utils.env.site_packages import SitePackages @@ -97,7 +96,6 @@ def build_environment( "GET_SYS_PATH", "GET_ENV_PATH_ONELINER", "GET_PYTHON_VERSION_ONELINER", - "GET_PATHS_FOR_GENERIC_ENVS", "EnvError", "EnvCommandError", "IncorrectEnvError", diff --git a/src/poetry/utils/env/base_env.py b/src/poetry/utils/env/base_env.py index f57fb9cc2ae..3c0da4ffa20 100644 --- a/src/poetry/utils/env/base_env.py +++ b/src/poetry/utils/env/base_env.py @@ -175,13 +175,10 @@ def os(self) -> str: @property def site_packages(self) -> SitePackages: if self._site_packages is None: - # we disable write checks if no user site exist - fallbacks = [self.usersite] if self.usersite else [] self._site_packages = SitePackages( self.purelib, self.platlib, - fallbacks, - skip_write_checks=not fallbacks, + self.fallbacks, ) return self._site_packages @@ -214,8 +211,14 @@ def platlib(self) -> Path: return self._platlib + @cached_property + def fallbacks(self) -> list[Path]: + paths = [Path(path) for path in self.paths.get("fallbacks", [])] + paths += [self.usersite] if self.usersite else [] + return paths + def _get_lib_dirs(self) -> list[Path]: - return [self.purelib, self.platlib] + return [self.purelib, self.platlib, *self.fallbacks] def is_path_relative_to_lib(self, path: Path) -> bool: for lib_path in self._get_lib_dirs(): diff --git a/src/poetry/utils/env/generic_env.py b/src/poetry/utils/env/generic_env.py index 276d1b1eb18..28ab156d5a1 100644 --- a/src/poetry/utils/env/generic_env.py +++ b/src/poetry/utils/env/generic_env.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from typing import Any -from poetry.utils.env.script_strings import GET_PATHS_FOR_GENERIC_ENVS +from poetry.utils.env.script_strings import GET_PATHS from poetry.utils.env.virtual_env import VirtualEnv @@ -78,7 +78,7 @@ def find_executables(self) -> None: self._pip_executable = pip_executable def get_paths(self) -> dict[str, str]: - output = self.run_python_script(GET_PATHS_FOR_GENERIC_ENVS) + output = self.run_python_script(GET_PATHS) paths: dict[str, str] = json.loads(output) return paths diff --git a/src/poetry/utils/env/script_strings.py b/src/poetry/utils/env/script_strings.py index dc33e00ba05..a2935b566e9 100644 --- a/src/poetry/utils/env/script_strings.py +++ b/src/poetry/utils/env/script_strings.py @@ -92,18 +92,16 @@ def _version_nodot(version): GET_PATHS = """\ import json -import sysconfig - -print(json.dumps(sysconfig.get_paths())) -""" - -GET_PATHS_FOR_GENERIC_ENVS = """\ -import json import site import sysconfig paths = sysconfig.get_paths().copy() +paths["fallbacks"] = [ + p for p in site.getsitepackages() + if p and p not in {paths.get("purelib"), paths.get("platlib")} +] + if site.check_enableusersite(): paths["usersite"] = site.getusersitepackages() paths["userbase"] = site.getuserbase() diff --git a/src/poetry/utils/env/site_packages.py b/src/poetry/utils/env/site_packages.py index a082419031c..564aa003aa0 100644 --- a/src/poetry/utils/env/site_packages.py +++ b/src/poetry/utils/env/site_packages.py @@ -25,7 +25,6 @@ def __init__( purelib: Path, platlib: Path | None = None, fallbacks: list[Path] | None = None, - skip_write_checks: bool = False, ) -> None: self._purelib = purelib self._platlib = platlib or purelib @@ -40,7 +39,7 @@ def __init__( if path not in self._candidates: self._candidates.append(path) - self._writable_candidates = None if not skip_write_checks else self._candidates + self._writable_candidates: list[Path] | None = None @property def path(self) -> Path: diff --git a/tests/console/commands/self/test_install.py b/tests/console/commands/self/test_install.py index a5e15e41ed6..0cd9663a4f9 100644 --- a/tests/console/commands/self/test_install.py +++ b/tests/console/commands/self/test_install.py @@ -56,14 +56,9 @@ def test_self_install( tester.execute() - expected_output = """\ -Updating dependencies -Resolving dependencies... - -Writing lock file -""" - - assert tester.io.fetch_output() == expected_output + output = tester.io.fetch_output() + assert output.startswith("Updating dependencies") + assert output.endswith("Writing lock file\n") assert tester.io.fetch_error() == "" diff --git a/tests/console/commands/self/test_sync.py b/tests/console/commands/self/test_sync.py index 6fdc27fc764..d1c351c74d3 100644 --- a/tests/console/commands/self/test_sync.py +++ b/tests/console/commands/self/test_sync.py @@ -6,6 +6,8 @@ from cleo.exceptions import CleoNoSuchOptionError +from poetry.console.commands.self.sync import SelfSyncCommand + # import all tests from the self install command # and run them for sync by overriding the command fixture from tests.console.commands.self.test_install import * # noqa: F403 @@ -13,6 +15,7 @@ if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester + from pytest_mock import MockerFixture @pytest.fixture # type: ignore[no-redef] @@ -28,3 +31,15 @@ def test_sync_deprecation() -> None: def test_sync_option_not_available(tester: CommandTester) -> None: with pytest.raises(CleoNoSuchOptionError): tester.execute("--sync") + + +def test_synced_installer(tester: CommandTester, mocker: MockerFixture) -> None: + assert isinstance(tester.command, SelfSyncCommand) + mock = mocker.patch( + "poetry.console.commands.install.InstallCommand.installer", + new_callable=mocker.PropertyMock, + ) + + tester.execute() + + mock.return_value.requires_synchronization.assert_called_with(True) diff --git a/tests/console/commands/test_sync.py b/tests/console/commands/test_sync.py index af75afd86f3..89d7e8c7760 100644 --- a/tests/console/commands/test_sync.py +++ b/tests/console/commands/test_sync.py @@ -6,6 +6,8 @@ from cleo.exceptions import CleoNoSuchOptionError +from poetry.console.commands.sync import SyncCommand + # import all tests from the install command # and run them for sync by overriding the command fixture from tests.console.commands.test_install import * # noqa: F403 @@ -13,6 +15,7 @@ if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester + from pytest_mock import MockerFixture @pytest.fixture # type: ignore[no-redef] @@ -28,3 +31,15 @@ def test_sync_option_is_passed_to_the_installer() -> None: def test_sync_option_not_available(tester: CommandTester) -> None: with pytest.raises(CleoNoSuchOptionError): tester.execute("--sync") + + +def test_synced_installer(tester: CommandTester, mocker: MockerFixture) -> None: + assert isinstance(tester.command, SyncCommand) + mock = mocker.patch( + "poetry.console.commands.install.InstallCommand.installer", + new_callable=mocker.PropertyMock, + ) + + tester.execute() + + mock.return_value.requires_synchronization.assert_called_with(True) diff --git a/tests/utils/env/test_env.py b/tests/utils/env/test_env.py index b3f63a7339d..97eda471416 100644 --- a/tests/utils/env/test_env.py +++ b/tests/utils/env/test_env.py @@ -309,7 +309,7 @@ def test_env_system_packages_are_relative_to_lib( # These are the virtual environments' base env packages, # in this case the system site packages. - for dist in metadata.distributions(path=[str(env.parent_env.site_packages.path)]): + for dist in env.parent_env.site_packages.distributions(): assert ( env.is_path_relative_to_lib( Path(str(dist._path)) # type: ignore[attr-defined]