def find_latest_package(self, package, include_dev): from clikit.io import NullIO from poetry.puzzle.provider import Provider from poetry.version.version_selector import VersionSelector # find the latest version allowed in this pool if package.source_type in ("git", "file", "directory"): requires = self.poetry.package.requires if include_dev: requires = requires + self.poetry.package.dev_requires for dep in requires: if dep.name == package.name: provider = Provider(self.poetry.package, self.poetry.pool, NullIO()) if dep.is_vcs(): return provider.search_for_vcs(dep)[0] if dep.is_file(): return provider.search_for_file(dep)[0] if dep.is_directory(): return provider.search_for_directory(dep)[0] name = package.name selector = VersionSelector(self.poetry.pool) return selector.find_best_candidate( name, ">={}".format(package.pretty_version))
def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): uploader_auth = mocker.patch( "poetry.masonry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch( "poetry.masonry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge( {"http-basic": { "pypi": { "username": "******", "password": "******" } }}) publisher = Publisher(poetry, NullIO()) publisher.publish(None, None, None) assert [("foo", "bar")] == uploader_auth.call_args assert [ ("https://upload.pypi.org/legacy/", ), { "cert": None, "client_cert": None }, ] == uploader_upload.call_args
def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): uploader_auth = mocker.patch( "poetry.masonry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch( "poetry.masonry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge({ "repositories": { "my-repo": { "url": "http://foo.bar" } }, "http-basic": { "my-repo": { "username": "******", "password": "******" } }, }) publisher = Publisher(poetry, NullIO()) publisher.publish("my-repo", None, None) assert [("foo", "bar")] == uploader_auth.call_args assert [ ("http://foo.bar", ), { "cert": None, "client_cert": None }, ] == uploader_upload.call_args
def test_publish_uses_client_cert(fixture_dir, mocker, config): client_cert = "path/to/client.pem" uploader_upload = mocker.patch( "poetry.masonry.publishing.uploader.Uploader.upload") poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge({ "repositories": { "foo": { "url": "https://foo.bar" } }, "certificates": { "foo": { "client-cert": client_cert } }, }) publisher = Publisher(poetry, NullIO()) publisher.publish("foo", None, None) assert [ ("https://foo.bar", ), { "cert": None, "client_cert": Path(client_cert) }, ] == uploader_upload.call_args
def test_install_with_client_cert(): client_path = "path/to/client.pem" pool = Pool() default = LegacyRepository("default", "https://foo.bar", client_cert=Path(client_path)) pool.add_repository(default, default=True) null_env = NullEnv() installer = PipInstaller(null_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(null_env.executed) == 1 cmd = null_env.executed[0] assert "--client-cert" in cmd cert_index = cmd.index("--client-cert") # 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_authenticator_uses_provided_certs_instead_of_config_certs( config, mock_remote, http, mocker): config.merge({ "repositories": { "foo": { "url": "https://foo.bar/simple/" } }, "http-basic": { "foo": { "username": "******", "password": "******" } }, "certificates": { "foo": { "cert": "/path/to/cert", "client-cert": "/path/to/client-cert", } }, }) authenticator = Authenticator(config, NullIO()) session_send = mocker.patch.object(authenticator.session, "send") authenticator.request( "get", "https://foo.bar/files/foo-0.1.0.tar.gz", verify="/path/to/provided/cert", cert="/path/to/provided/client-cert", ) call_args = session_send.call_args call_args.kwargs["verify"] == pathlib.Path("/path/to/provided/cert") call_args.kwargs["cert"] == pathlib.Path("/path/to/provided/client-cert")
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_builder_should_execute_build_scripts(extended_without_setup_poetry): env = MockEnv(path=Path("/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 install_directory(self, package): from poetry.factory import Factory from poetry.io.null_io import NullIO if package.root_dir: req = (package.root_dir / package.source_url).as_posix() else: req = os.path.realpath(package.source_url) args = ["install", "--no-deps", "-U"] 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: args.append("-e") args.append(req) return self.run_pip(*args) if package.develop: args.append("-e") args.append(req) return self.run(*args)
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_uploader_properly_handles_400_errors(http): http.register_uri(http.POST, "https://foo.com", status=400, body="Bad request") uploader = Uploader(Poetry.create(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") assert "HTTP Error 400: Bad Request" == str(e.value)
def test_requirement_source_type_url(): installer = PipInstaller(NullEnv(), NullIO(), Pool()) foo = Package("foo", "0.0.0") foo.source_type = "url" foo.source_url = "https://somehwere.com/releases/foo-1.0.0.tar.gz" result = installer.requirement(foo, formatted=True) expected = "{}#egg={}".format(foo.source_url, foo.name) assert expected == result
def test_uploader_registers_for_appropriate_400_errors(mocker, http): register = mocker.patch("poetry.masonry.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_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 build_packages() -> List[Package]: poetry = Factory().create_poetry(Path.cwd()) env = NullEnv() io = NullIO() with TemporaryDirectory() as temp_dir_str: temp_dir = Path(temp_dir_str) wheel_pkg_name = WheelBuilder.make_in(poetry, env, io, temp_dir) pkg_path = temp_dir / wheel_pkg_name pkg_bytes = pkg_path.read_bytes() pkgs = [Package(wheel_pkg_name, pkg_bytes)] return pkgs
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_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_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_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_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_requirement(): installer = PipInstaller(NullEnv(), NullIO(), Pool()) package = Package("ipython", "7.5.0") package.hashes = [ "md5:dbdc53e3918f28fa335a173432402a00", "e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26", ] result = installer.requirement(package, formatted=True) expected = ( "ipython==7.5.0 " "--hash md5:dbdc53e3918f28fa335a173432402a00 " "--hash sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26" "\n") assert expected == result
def build(self): # We start by building the tarball # We will use it to build the wheel sdist_builder = SdistBuilder(self._poetry, self._env, self._io) build_for_all_formats = False for p in self._package.packages: formats = p.get("format", []) if not isinstance(formats, list): formats = [formats] if formats and sdist_builder.format not in formats: build_for_all_formats = True break sdist_file = sdist_builder.build() self._io.write_line("") dist_dir = self._path / "dist" if build_for_all_formats: sdist_builder = SdistBuilder(self._poetry, self._env, NullIO(), ignore_packages_formats=True) with temporary_directory() as tmp_dir: sdist_file = sdist_builder.build(Path(tmp_dir)) with self.unpacked_tarball(sdist_file) as tmpdir: WheelBuilder.make_in( Factory().create_poetry(tmpdir), self._env, self._io, dist_dir, original=self._poetry, ) else: with self.unpacked_tarball(sdist_file) as tmpdir: WheelBuilder.make_in( Factory().create_poetry(tmpdir), self._env, self._io, dist_dir, original=self._poetry, )
def test_authenticator_request_retries_on_exception(mocker, config, http): sleep = mocker.patch("time.sleep") sdist_uri = "https://foo.bar/files/{}/foo-0.1.0.tar.gz".format( str(uuid.uuid4())) content = str(uuid.uuid4()) seen = list() def callback(request, uri, response_headers): if seen.count(uri) < 2: seen.append(uri) raise requests.exceptions.ConnectionError("Disconnected") return [200, response_headers, content] httpretty.register_uri(httpretty.GET, sdist_uri, body=callback) authenticator = Authenticator(config, NullIO()) response = authenticator.request("get", sdist_uri) assert response.text == content assert sleep.call_count == 2
def test_authenticator_request_retries_on_status_code(mocker, config, http, status, attempts): sleep = mocker.patch("time.sleep") sdist_uri = "https://foo.bar/files/{}/foo-0.1.0.tar.gz".format( str(uuid.uuid4())) content = str(uuid.uuid4()) def callback(request, uri, response_headers): return [status, response_headers, content] httpretty.register_uri(httpretty.GET, sdist_uri, body=callback) authenticator = Authenticator(config, NullIO()) with pytest.raises(requests.exceptions.HTTPError) as excinfo: authenticator.request("get", sdist_uri) assert excinfo.value.response.status_code == status assert excinfo.value.response.text == content assert sleep.call_count == attempts
def test_uninstall_git_package_nspkg_pth_cleanup(mocker, tmp_venv, pool): # this test scenario requires a real installation using the pip installer installer = PipInstaller(tmp_venv, NullIO(), pool) # use a namepspace 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", ) # we do this here because the virtual env might not be usable if failure case is triggered pth_file_candidate = tmp_venv.site_packages.path / "{}-nspkg.pth".format( package.name) # 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, dest): 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) assert not Path(pth_file_candidate).exists() # any command in the virtual environment should trigger the error message output = tmp_venv.run("python", "-m", "site") assert "Error processing line 1 of {}".format( pth_file_candidate) not in output
def test_builder_installs_proper_files_when_packages_configured( project_with_include, tmp_venv ): builder = EditableBuilder(project_with_include, tmp_venv, NullIO()) builder.build() pth_file = tmp_venv.site_packages.joinpath("with_include.pth") assert pth_file.is_file() paths = set() with pth_file.open() as f: for line in f.readlines(): line = line.strip(os.linesep) if line: paths.add(line) project_root = project_with_include.file.parent.resolve() expected = {project_root.as_posix(), project_root.joinpath("src").as_posix()} assert paths.issubset(expected) assert len(paths) == len(expected)
def test_authenticator_uses_empty_strings_as_default_password( config, mock_remote, http): 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_install_with_non_pypi_default_repository(): pool = Pool() default = LegacyRepository("default", "https://default.com") another = LegacyRepository("another", "https://another.com") pool.add_repository(default, default=True) pool.add_repository(another) installer = PipInstaller(NullEnv(), NullIO(), pool) foo = Package("foo", "0.0.0") foo.source_type = "legacy" foo.source_reference = default._name foo.source_url = default._url bar = Package("bar", "0.1.0") bar.source_type = "legacy" bar.source_reference = another._name bar.source_url = another._url installer.install(foo) installer.install(bar)
def test_publish_uses_cert(fixture_dir, mocker, config): cert = "path/to/ca.pem" 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( { "repositories": {"foo": {"url": "https://foo.bar"}}, "http-basic": {"foo": {"username": "******", "password": "******"}}, "certificates": {"foo": {"cert": cert}}, } ) publisher = Publisher(poetry, NullIO()) publisher.publish("foo", None, None) assert [("foo", "bar")] == uploader_auth.call_args assert [ ("https://foo.bar",), {"cert": Path(cert), "client_cert": None, "dry_run": False}, ] == uploader_upload.call_args
def test_authenticator_uses_credentials_from_config_if_not_provided( 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://foo.bar/files/foo-0.1.0.tar.gz") request = http.last_request() assert "Basic YmFyOmJheg==" == request.headers["Authorization"]