Commit f1137cf1 by Arun Babu Neelicattu Committed by GitHub

Add --dry-run option for publish command (#2199)

This change introduces `--dry-run` option for the publish
command. When used, will perform all actions required for
publishing except for uploading build artifacts.

Resolves: #2181
parent ab8ef199
...@@ -325,6 +325,7 @@ It can also build the package if you pass it the `--build` option. ...@@ -325,6 +325,7 @@ It can also build the package if you pass it the `--build` option.
Should match a repository name set by the [`config`](#config) command. Should match a repository name set by the [`config`](#config) command.
* `--username (-u)`: The username to access the repository. * `--username (-u)`: The username to access the repository.
* `--password (-p)`: The password to access the repository. * `--password (-p)`: The password to access the repository.
* `--dry-run`: Perform all actions except upload the package.
## config ## config
......
...@@ -26,6 +26,7 @@ class PublishCommand(Command): ...@@ -26,6 +26,7 @@ class PublishCommand(Command):
flag=False, flag=False,
), ),
option("build", None, "Build the package before publishing."), option("build", None, "Build the package before publishing."),
option("dry-run", None, "Perform all actions except upload the package."),
] ]
help = """The publish command builds and uploads the package to a remote repository. help = """The publish command builds and uploads the package to a remote repository.
...@@ -79,4 +80,5 @@ the config command. ...@@ -79,4 +80,5 @@ the config command.
self.option("password"), self.option("password"),
cert, cert,
client_cert, client_cert,
self.option("dry-run"),
) )
...@@ -26,7 +26,15 @@ class Publisher: ...@@ -26,7 +26,15 @@ class Publisher:
def files(self): def files(self):
return self._uploader.files return self._uploader.files
def publish(self, repository_name, username, password, cert=None, client_cert=None): def publish(
self,
repository_name,
username,
password,
cert=None,
client_cert=None,
dry_run=False,
):
if repository_name: if repository_name:
self._io.write_line( self._io.write_line(
"Publishing <c1>{}</c1> (<c2>{}</c2>) " "Publishing <c1>{}</c1> (<c2>{}</c2>) "
...@@ -90,4 +98,5 @@ class Publisher: ...@@ -90,4 +98,5 @@ class Publisher:
url, url,
cert=cert or get_cert(self._poetry.config, repository_name), cert=cert or get_cert(self._poetry.config, repository_name),
client_cert=resolved_client_cert, client_cert=resolved_client_cert,
dry_run=dry_run,
) )
...@@ -95,8 +95,8 @@ class Uploader: ...@@ -95,8 +95,8 @@ class Uploader:
return self._username is not None and self._password is not None return self._username is not None and self._password is not None
def upload( def upload(
self, url, cert=None, client_cert=None self, url, cert=None, client_cert=None, dry_run=False
): # type: (str, Optional[Path], Optional[Path]) -> None ): # type: (str, Optional[Path], Optional[Path], bool) -> None
session = self.make_session() session = self.make_session()
if cert: if cert:
...@@ -106,7 +106,7 @@ class Uploader: ...@@ -106,7 +106,7 @@ class Uploader:
session.cert = str(client_cert) session.cert = str(client_cert)
try: try:
self._upload(session, url) self._upload(session, url, dry_run)
finally: finally:
session.close() session.close()
...@@ -188,9 +188,9 @@ class Uploader: ...@@ -188,9 +188,9 @@ class Uploader:
return data return data
def _upload(self, session, url): def _upload(self, session, url, dry_run=False):
try: try:
self._do_upload(session, url) self._do_upload(session, url, dry_run)
except HTTPError as e: except HTTPError as e:
if ( if (
e.response.status_code == 400 e.response.status_code == 400
...@@ -203,15 +203,16 @@ class Uploader: ...@@ -203,15 +203,16 @@ class Uploader:
raise UploadError(e) raise UploadError(e)
def _do_upload(self, session, url): def _do_upload(self, session, url, dry_run=False):
for file in self.files: for file in self.files:
# TODO: Check existence # TODO: Check existence
resp = self._upload_file(session, url, file) resp = self._upload_file(session, url, file, dry_run)
if not dry_run:
resp.raise_for_status() resp.raise_for_status()
def _upload_file(self, session, url, file): def _upload_file(self, session, url, file, dry_run=False):
data = self.post_data(file) data = self.post_data(file)
data.update( data.update(
{ {
...@@ -238,6 +239,9 @@ class Uploader: ...@@ -238,6 +239,9 @@ class Uploader:
bar.start() bar.start()
resp = None
if not dry_run:
resp = session.post( resp = session.post(
url, url,
data=monitor, data=monitor,
...@@ -245,7 +249,7 @@ class Uploader: ...@@ -245,7 +249,7 @@ class Uploader:
headers={"Content-Type": monitor.content_type}, headers={"Content-Type": monitor.content_type},
) )
if resp.ok: if dry_run or resp.ok:
bar.set_format( bar.set_format(
" - Uploading <c1>{0}</c1> <fg=green>%percent%%</>".format( " - Uploading <c1>{0}</c1> <fg=green>%percent%%</>".format(
file.name file.name
......
...@@ -60,7 +60,7 @@ def test_publish_with_cert(app_tester, mocker): ...@@ -60,7 +60,7 @@ def test_publish_with_cert(app_tester, mocker):
app_tester.execute("publish --cert path/to/ca.pem") app_tester.execute("publish --cert path/to/ca.pem")
assert [ assert [
(None, None, None, Path("path/to/ca.pem"), None) (None, None, None, Path("path/to/ca.pem"), None, False)
] == publisher_publish.call_args ] == publisher_publish.call_args
...@@ -69,5 +69,22 @@ def test_publish_with_client_cert(app_tester, mocker): ...@@ -69,5 +69,22 @@ def test_publish_with_client_cert(app_tester, mocker):
app_tester.execute("publish --client-cert path/to/client.pem") app_tester.execute("publish --client-cert path/to/client.pem")
assert [ assert [
(None, None, None, None, Path("path/to/client.pem")) (None, None, None, None, Path("path/to/client.pem"), False)
] == publisher_publish.call_args ] == publisher_publish.call_args
def test_publish_dry_run(app_tester, http):
http.register_uri(
http.POST, "https://upload.pypi.org/legacy/", status=403, body="Forbidden"
)
exit_code = app_tester.execute("publish --dry-run --username foo --password bar")
assert 0 == exit_code
output = app_tester.io.fetch_output()
error = app_tester.io.fetch_error()
assert "Publishing simple-project (1.2.3) to PyPI" in output
assert "- Uploading simple-project-1.2.3.tar.gz" in error
assert "- Uploading simple_project-1.2.3-py2.py3-none-any.whl" in error
...@@ -23,7 +23,7 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): ...@@ -23,7 +23,7 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
assert [("foo", "bar")] == uploader_auth.call_args assert [("foo", "bar")] == uploader_auth.call_args
assert [ assert [
("https://upload.pypi.org/legacy/",), ("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None}, {"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args ] == uploader_upload.call_args
...@@ -45,7 +45,7 @@ def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): ...@@ -45,7 +45,7 @@ def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config):
assert [("foo", "bar")] == uploader_auth.call_args assert [("foo", "bar")] == uploader_auth.call_args
assert [ assert [
("http://foo.bar",), ("http://foo.bar",),
{"cert": None, "client_cert": None}, {"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args ] == uploader_upload.call_args
...@@ -74,7 +74,7 @@ def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config): ...@@ -74,7 +74,7 @@ def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config):
assert [("__token__", "my-token")] == uploader_auth.call_args assert [("__token__", "my-token")] == uploader_auth.call_args
assert [ assert [
("https://upload.pypi.org/legacy/",), ("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None}, {"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args ] == uploader_upload.call_args
...@@ -98,7 +98,7 @@ def test_publish_uses_cert(fixture_dir, mocker, config): ...@@ -98,7 +98,7 @@ def test_publish_uses_cert(fixture_dir, mocker, config):
assert [("foo", "bar")] == uploader_auth.call_args assert [("foo", "bar")] == uploader_auth.call_args
assert [ assert [
("https://foo.bar",), ("https://foo.bar",),
{"cert": Path(cert), "client_cert": None}, {"cert": Path(cert), "client_cert": None, "dry_run": False},
] == uploader_upload.call_args ] == uploader_upload.call_args
...@@ -119,7 +119,7 @@ def test_publish_uses_client_cert(fixture_dir, mocker, config): ...@@ -119,7 +119,7 @@ def test_publish_uses_client_cert(fixture_dir, mocker, config):
assert [ assert [
("https://foo.bar",), ("https://foo.bar",),
{"cert": None, "client_cert": Path(client_cert)}, {"cert": None, "client_cert": Path(client_cert), "dry_run": False},
] == uploader_upload.call_args ] == uploader_upload.call_args
...@@ -137,5 +137,5 @@ def test_publish_read_from_environment_variable(fixture_dir, environ, mocker, co ...@@ -137,5 +137,5 @@ def test_publish_read_from_environment_variable(fixture_dir, environ, mocker, co
assert [("bar", "baz")] == uploader_auth.call_args assert [("bar", "baz")] == uploader_auth.call_args
assert [ assert [
("https://foo.bar",), ("https://foo.bar",),
{"cert": None, "client_cert": None}, {"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args ] == uploader_upload.call_args
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