def _install_directory(self, operation: Union[Install, Update]) -> int: from poetry.factory import Factory package = operation.package operation_message = self.get_operation_message(operation) message = ( " <fg=blue;options=bold>•</> {message}: <info>Building...</info>". format(message=operation_message, )) self._write(operation, message) if package.root_dir: req = package.root_dir / package.source_url else: req = Path(package.source_url).resolve(strict=False) pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) if pyproject.is_poetry_project(): # Even if there is a build system specified # some versions of pip (< 19.0.0) don't understand it # so we need to check the version of pip to know # if we can rely on the build system legacy_pip = (self._env.pip_version < self._env.pip_version.__class__.from_parts(19, 0, 0)) package_poetry = Factory().create_poetry( pyproject.file.path.parent) if package.develop and not package_poetry.package.build_script: from poetry.masonry.builders.editable import EditableBuilder # This is a Poetry package in editable mode # we can use the EditableBuilder without going through pip # to install it, unless it has a build script. builder = EditableBuilder(package_poetry, self._env, NullIO()) builder.build() return 0 elif legacy_pip or package_poetry.package.build_script: from poetry.core.masonry.builders.sdist import SdistBuilder # We need to rely on creating a temporary setup.py # file since the version of pip does not support # build-systems # We also need it for non-PEP-517 packages builder = SdistBuilder(package_poetry) with builder.setup_py(): if package.develop: return pip_editable_install(req, self._env) return pip_install(req, self._env, upgrade=True) if package.develop: return pip_editable_install(req, self._env) return pip_install(req, self._env, upgrade=True)
def test_activate_activates_recreates_for_different_patch( tmp_dir, manager, poetry, config, mocker): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) os.mkdir(os.path.join(tmp_dir, "{}-py3.7".format(venv_name))) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(), ) mocker.patch( "subprocess.Popen.communicate", side_effect=[ ("/prefix", None), ('{"version_info": [3, 7, 0]}', None), ("/prefix", None), ("/prefix", None), ("/prefix", None), ], ) build_venv_m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) remove_venv_m = mocker.patch("poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv) env = manager.activate("python3.7", NullIO()) build_venv_m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name), executable="python3.7", flags={ "always-copy": False, "system-site-packages": False }, ) remove_venv_m.assert_called_with( Path(tmp_dir) / "{}-py3.7".format(venv_name)) assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.7" assert envs[venv_name]["patch"] == "3.7.1" assert env.path == Path(tmp_dir) / "{}-py3.7".format(venv_name) assert env.base == Path("/prefix") assert (Path(tmp_dir) / "{}-py3.7".format(venv_name)).exists()
def create_poetry( self, cwd: Path | None = None, io: IO | None = None, disable_plugins: bool = False, ) -> Poetry: if io is None: io = NullIO() base_poetry = super().create_poetry(cwd) locker = Locker(base_poetry.file.parent / "poetry.lock", base_poetry.local_config) # Loading global configuration config = self.create_config(io) # Loading local configuration local_config_file = TOMLFile(base_poetry.file.parent / "poetry.toml") if local_config_file.exists(): if io.is_debug(): io.write_line( f"Loading configuration file {local_config_file.path}") config.merge(local_config_file.read()) # Load local sources repositories = {} existing_repositories = config.get("repositories", {}) for source in base_poetry.pyproject.poetry_config.get("source", []): name = source.get("name") url = source.get("url") if name and url and name not in existing_repositories: repositories[name] = {"url": url} config.merge({"repositories": repositories}) poetry = Poetry( base_poetry.file.path, base_poetry.local_config, base_poetry.package, locker, config, ) # Configuring sources self.configure_sources(poetry, poetry.local_config.get("source", []), config, io) plugin_manager = PluginManager("plugin", disable_plugins=disable_plugins) plugin_manager.load_plugins() poetry.set_plugin_manager(plugin_manager) plugin_manager.activate(poetry, io) return poetry
def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, config): poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge( {"http-basic": {"my-repo": {"username": "******", "password": "******"}}} ) publisher = Publisher(poetry, NullIO()) with pytest.raises(RuntimeError): publisher.publish("my-repo", None, None)
def test_authenticator_uses_url_provided_credentials( mock_config: Config, mock_remote: None, http: type[httpretty.httpretty]): authenticator = Authenticator(mock_config, NullIO()) authenticator.request( "get", "https://*****:*****@foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() assert request.headers["Authorization"] == "Basic Zm9vMDAxOmJhcjAwMg=="
def install_directory(self, package: "Package") -> Union[str, int]: from cleo.io.null_io import NullIO from poetry.factory import Factory req: Path if package.root_dir: req = (package.root_dir / package.source_url).as_posix() else: req = Path(package.source_url).resolve(strict=False) pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml")) if pyproject.is_poetry_project(): # Even if there is a build system specified # some versions of pip (< 19.0.0) don't understand it # so we need to check the version of pip to know # if we can rely on the build system legacy_pip = self._env.pip_version < self._env.pip_version.__class__( 19, 0, 0 ) package_poetry = Factory().create_poetry(pyproject.file.path.parent) if package.develop and not package_poetry.package.build_script: from poetry.masonry.builders.editable import EditableBuilder # This is a Poetry package in editable mode # we can use the EditableBuilder without going through pip # to install it, unless it has a build script. builder = EditableBuilder(package_poetry, self._env, NullIO()) builder.build() return 0 elif legacy_pip or package_poetry.package.build_script: from poetry.core.masonry.builders.sdist import SdistBuilder # We need to rely on creating a temporary setup.py # file since the version of pip does not support # build-systems # We also need it for non-PEP-517 packages builder = SdistBuilder(package_poetry) with builder.setup_py(): if package.develop: return pip_editable_install( directory=req, environment=self._env ) return pip_install( path=req, environment=self._env, deps=False, upgrade=True ) if package.develop: return pip_editable_install(directory=req, environment=self._env) return pip_install(path=req, environment=self._env, deps=False, upgrade=True)
def test_authenticator_uses_env_provided_credentials( config, environ, mock_remote, http, environment_repository_credentials): config.merge({"repositories": {"foo": {"url": "https://foo.bar/simple/"}}}) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() assert "Basic YmFyOmJheg==" == request.headers["Authorization"]
def call_silent(self, name, args=None): # type: (str, Optional[str]) -> int """ Call another command silently. """ if args is None: args = "" args = StringInput(args) command = self.application.get(name) return self.application._run_command(command, NullIO(input))
def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( mocker, extended_poetry, tmp_dir): pip_editable_install = mocker.patch( "poetry.masonry.builders.editable.pip_editable_install") env = MockEnv(path=Path(tmp_dir) / "foo") builder = EditableBuilder(extended_poetry, env, NullIO()) builder.build() pip_editable_install.assert_called_once_with( extended_poetry.pyproject.file.path.parent, env) assert [] == env.executed
def test_builder_should_execute_build_scripts(extended_without_setup_poetry, tmp_dir): env = MockEnv(path=Path(tmp_dir) / "foo") builder = EditableBuilder(extended_without_setup_poetry, env, NullIO()) builder.build() assert [[ "python", str(extended_without_setup_poetry.file.parent / "build.py") ]] == env.executed
def test_activate_activates_different_virtualenv_with_envs_file( tmp_dir: str, manager: EnvManager, poetry: Poetry, config: Config, mocker: MockerFixture, ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.1"} envs_file.write(doc) os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) mocker.patch( "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv) env = manager.activate("python3.6", NullIO()) m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.6", executable="/usr/bin/python3.6", flags={ "always-copy": False, "system-site-packages": False, "no-pip": False, "no-setuptools": False, }, prompt="simple-project-py3.6", ) assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.6" assert envs[venv_name]["patch"] == "3.6.6" assert env.path == Path(tmp_dir) / f"{venv_name}-py3.6" assert env.base == Path("/prefix")
def test_uploader_properly_handles_403_errors(http): http.register_uri(http.POST, "https://foo.com", status=403, body="Unauthorized") uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") assert "HTTP Error 403: Forbidden" == str(e.value)
def test_uploader_registers_for_appropriate_400_errors(mocker, http): register = mocker.patch("poetry.publishing.uploader.Uploader._register") http.register_uri(http.POST, "https://foo.com", status=400, body="No package was ever registered") uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError): uploader.upload("https://foo.com") assert 1 == register.call_count
def test_authenticator_uses_username_only_credentials( mock_config: Config, mock_remote: None, http: type[httpretty.httpretty], with_simple_keyring: None, ): authenticator = Authenticator(mock_config, NullIO()) authenticator.request("get", "https://[email protected]/files/foo-0.1.0.tar.gz") request = http.last_request() assert request.headers["Authorization"] == "Basic Zm9vMDAxOg=="
def test_uploader_properly_handles_301_redirects(http): http.register_uri(http.POST, "https://foo.com", status=301, body="Redirect") uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") assert "Redirects are not supported. Is the URL missing a trailing slash?" == str( e.value)
def test_requirement_source_type_url(): installer = PipInstaller(NullEnv(), NullIO(), Pool()) 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_authenticator_uses_password_only_credentials(config, mock_remote, http): config.merge( { "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, "http-basic": {"foo": {"username": "******", "password": "******"}}, } ) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://:[email protected]/files/foo-0.1.0.tar.gz") request = http.last_request() assert "Basic OmJhcjAwMg==" == request.headers["Authorization"]
def test_create_venv_fails_if_current_python_version_is_not_supported( manager: EnvManager, poetry: Poetry): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] manager.create_venv(NullIO()) current_version = Version.parse(".".join( str(c) for c in sys.version_info[:3])) next_version = ".".join( str(c) for c in (current_version.major, current_version.minor + 1, 0)) package_version = "~" + next_version poetry.package.python_versions = package_version with pytest.raises(InvalidCurrentPythonVersionError) as e: manager.create_venv(NullIO()) expected_message = ( f"Current Python version ({current_version}) is not allowed by the project" f' ({package_version}).\nPlease change python executable via the "env use"' " command.") assert expected_message == str(e.value)
def test_authenticator_git_repositories( config: Config, mock_remote: None, http: type[httpretty.httpretty], with_simple_keyring: None, dummy_keyring: DummyBackend, ): config.merge({ "repositories": { "one": { "url": "https://foo.bar/org/one.git" }, "two": { "url": "https://foo.bar/org/two.git" }, }, "http-basic": { "one": { "username": "******", "password": "******" }, "two": { "username": "******", "password": "******" }, }, }) authenticator = Authenticator(config, NullIO()) one = authenticator.get_credentials_for_git_url( "https://foo.bar/org/one.git") assert one.username == "foo" assert one.password == "bar" two = authenticator.get_credentials_for_git_url( "https://foo.bar/org/two.git") assert two.username == "baz" assert two.password == "qux" two_ssh = authenticator.get_credentials_for_git_url( "ssh://[email protected]/org/two.git") assert not two_ssh.username assert not two_ssh.password three = authenticator.get_credentials_for_git_url( "https://foo.bar/org/three.git") assert not three.username assert not three.password
def test_activate_does_not_recreate_when_switching_minor( tmp_dir: str, manager: EnvManager, poetry: "Poetry", config: "Config", mocker: "MockerFixture", ): if "VIRTUAL_ENV" in os.environ: del os.environ["VIRTUAL_ENV"] venv_name = manager.generate_env_name("simple-project", str(poetry.file.parent)) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") doc = tomlkit.document() doc[venv_name] = {"minor": "3.7", "patch": "3.7.0"} envs_file.write(doc) os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.7")) os.mkdir(os.path.join(tmp_dir, f"{venv_name}-py3.6")) config.merge({"virtualenvs": {"path": str(tmp_dir)}}) mocker.patch( "subprocess.check_output", side_effect=check_output_wrapper(Version.parse("3.6.6")), ) mocker.patch( "subprocess.Popen.communicate", side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)], ) build_venv_m = mocker.patch( "poetry.utils.env.EnvManager.build_venv", side_effect=build_venv ) remove_venv_m = mocker.patch( "poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv ) env = manager.activate("python3.6", NullIO()) build_venv_m.assert_not_called() remove_venv_m.assert_not_called() assert envs_file.exists() envs = envs_file.read() assert envs[venv_name]["minor"] == "3.6" assert envs[venv_name]["patch"] == "3.6.6" assert env.path == Path(tmp_dir) / f"{venv_name}-py3.6" assert env.base == Path("/prefix") assert (Path(tmp_dir) / f"{venv_name}-py3.6").exists()
def test_builder_should_execute_build_scripts( mocker: MockerFixture, extended_without_setup_poetry: Poetry, tmp_dir: str): env = MockEnv(path=Path(tmp_dir) / "foo") mocker.patch("poetry.masonry.builders.editable.build_environment" ).return_value.__enter__.return_value = env builder = EditableBuilder(extended_without_setup_poetry, env, NullIO()) builder.build() assert [[ "python", str(extended_without_setup_poetry.file.parent / "build.py") ]] == env.executed
def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config): uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge({"pypi-token": {"pypi": "my-token"}}) publisher = Publisher(poetry, NullIO()) publisher.publish(None, None, None) assert [("__token__", "my-token")] == uploader_auth.call_args assert [ ("https://upload.pypi.org/legacy/",), {"cert": None, "client_cert": None, "dry_run": False}, ] == uploader_upload.call_args
def test_publish_read_from_environment_variable(fixture_dir, environ, mocker, config): os.environ["POETRY_REPOSITORIES_FOO_URL"] = "https://foo.bar" os.environ["POETRY_HTTP_BASIC_FOO_USERNAME"] = "******" os.environ["POETRY_HTTP_BASIC_FOO_PASSWORD"] = "******" uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) publisher = Publisher(poetry, NullIO()) publisher.publish("foo", None, None) assert [("bar", "baz")] == uploader_auth.call_args assert [ ("https://foo.bar",), {"cert": None, "client_cert": None, "dry_run": False}, ] == uploader_upload.call_args
def test_authenticator_uses_empty_strings_as_default_password( config, mock_remote, http, with_simple_keyring ): config.merge( { "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, "http-basic": {"foo": {"username": "******"}}, } ) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() assert "Basic YmFyOg==" == request.headers["Authorization"]
def test_authenticator_request_raises_exception_when_attempts_exhausted( mocker, config, http): sleep = mocker.patch("time.sleep") sdist_uri = "https://foo.bar/files/{}/foo-0.1.0.tar.gz".format( str(uuid.uuid4())) def callback(*_, **__): raise requests.exceptions.ConnectionError(str(uuid.uuid4())) httpretty.register_uri(httpretty.GET, sdist_uri, body=callback) authenticator = Authenticator(config, NullIO()) with pytest.raises(requests.exceptions.ConnectionError): authenticator.request("get", sdist_uri) assert sleep.call_count == 5
def test_authenticator_uses_credentials_from_config_if_not_provided( config: "Config", mock_remote: None, http: Type[httpretty.httpretty] ): config.merge( { "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, "http-basic": {"foo": {"username": "******", "password": "******"}}, } ) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() assert request.headers["Authorization"] == "Basic YmFyOmJheg=="
def test_authenticator_request_raises_exception_when_attempts_exhausted( mocker: "MockerFixture", config: "Config", http: Type[httpretty.httpretty] ): sleep = mocker.patch("time.sleep") sdist_uri = f"https://foo.bar/files/{uuid.uuid4()!s}/foo-0.1.0.tar.gz" def callback(*_: Any, **___: Any) -> None: raise requests.exceptions.ConnectionError(str(uuid.uuid4())) httpretty.register_uri(httpretty.GET, sdist_uri, body=callback) authenticator = Authenticator(config, NullIO()) with pytest.raises(requests.exceptions.ConnectionError): authenticator.request("get", sdist_uri) assert sleep.call_count == 5
def test_authenticator_uses_empty_strings_as_default_username( config: "Config", mock_remote: None, http: Type[httpretty.httpretty] ): config.merge( { "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, "http-basic": {"foo": {"username": None, "password": "******"}}, } ) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() assert request.headers["Authorization"] == "Basic OmJhcg=="
def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts( extended_poetry, tmp_dir): env = MockEnv(path=Path(tmp_dir) / "foo") builder = EditableBuilder(extended_poetry, env, NullIO()) builder.build() assert [[ "python", "-m", "pip", "install", "-e", str(extended_poetry.file.parent), "--no-deps", ]] == env.executed
def test_authenticator_uses_url_provided_credentials( config: Config, mock_remote: None, http: type[httpretty.httpretty] ): config.merge( { "repositories": {"foo": {"url": "https://foo.bar/simple/"}}, "http-basic": {"foo": {"username": "******", "password": "******"}}, } ) authenticator = Authenticator(config, NullIO()) authenticator.request("get", "https://*****:*****@foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() assert request.headers["Authorization"] == "Basic Zm9vMDAxOmJhcjAwMg=="