Skip to content

Commit

Permalink
Add --dry-run option for publish command
Browse files Browse the repository at this point in the history
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: python-poetry#2181
  • Loading branch information
abn committed Mar 27, 2020
1 parent 3b30d2a commit 3295a70
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 25 deletions.
1 change: 1 addition & 0 deletions docs/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* `--username (-u)`: The username to access the repository.
* `--password (-p)`: The password to access the repository.
* `--dry-run`: Perform all actions except upload the package.

## config

Expand Down
2 changes: 2 additions & 0 deletions poetry/console/commands/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class PublishCommand(Command):
flag=False,
),
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.
Expand Down Expand Up @@ -79,4 +80,5 @@ def handle(self):
self.option("password"),
cert,
client_cert,
self.option("dry-run"),
)
11 changes: 10 additions & 1 deletion poetry/publishing/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ def __init__(self, poetry, io):
def files(self):
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:
self._io.write_line(
"Publishing <c1>{}</c1> (<b>{}</b>) "
Expand Down Expand Up @@ -90,4 +98,5 @@ def publish(self, repository_name, username, password, cert=None, client_cert=No
url,
cert=cert or get_cert(self._poetry.config, repository_name),
client_cert=resolved_client_cert,
dry_run=dry_run,
)
36 changes: 20 additions & 16 deletions poetry/publishing/uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ def is_authenticated(self):
return self._username is not None and self._password is not None

def upload(
self, url, cert=None, client_cert=None
): # type: (str, Optional[Path], Optional[Path]) -> None
self, url, cert=None, client_cert=None, dry_run=False
): # type: (str, Optional[Path], Optional[Path], bool) -> None
session = self.make_session()

if cert:
Expand All @@ -106,7 +106,7 @@ def upload(
session.cert = str(client_cert)

try:
self._upload(session, url)
self._upload(session, url, dry_run)
finally:
session.close()

Expand Down Expand Up @@ -188,9 +188,9 @@ def post_data(self, file):

return data

def _upload(self, session, url):
def _upload(self, session, url, dry_run=False):
try:
self._do_upload(session, url)
self._do_upload(session, url, dry_run)
except HTTPError as e:
if (
e.response.status_code == 400
Expand All @@ -203,15 +203,16 @@ def _upload(self, session, url):

raise UploadError(e)

def _do_upload(self, session, url):
def _do_upload(self, session, url, dry_run=False):
for file in self.files:
# TODO: Check existence

resp = self._upload_file(session, url, file)
resp = self._upload_file(session, url, file, dry_run)

resp.raise_for_status()
if not dry_run:
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.update(
{
Expand All @@ -238,14 +239,17 @@ def _upload_file(self, session, url, file):

bar.start()

resp = session.post(
url,
data=monitor,
allow_redirects=False,
headers={"Content-Type": monitor.content_type},
)
resp = None

if not dry_run:
resp = session.post(
url,
data=monitor,
allow_redirects=False,
headers={"Content-Type": monitor.content_type},
)

if resp.ok:
if dry_run or resp.ok:
bar.set_format(
" - Uploading <c1>{0}</c1> <fg=green>%percent%%</>".format(
file.name
Expand Down
21 changes: 19 additions & 2 deletions tests/console/commands/test_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_publish_with_cert(app_tester, mocker):
app_tester.execute("publish --cert path/to/ca.pem")

assert [
(None, None, None, Path("path/to/ca.pem"), None)
(None, None, None, Path("path/to/ca.pem"), None, False)
] == publisher_publish.call_args


Expand All @@ -36,5 +36,22 @@ def test_publish_with_client_cert(app_tester, mocker):

app_tester.execute("publish --client-cert path/to/client.pem")
assert [
(None, None, None, None, Path("path/to/client.pem"))
(None, None, None, None, Path("path/to/client.pem"), False)
] == 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
12 changes: 6 additions & 6 deletions tests/publishing/test_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config):
assert [("foo", "bar")] == uploader_auth.call_args
assert [
("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None},
{"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args


Expand All @@ -45,7 +45,7 @@ def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config):
assert [("foo", "bar")] == uploader_auth.call_args
assert [
("http://foo.bar",),
{"cert": None, "client_cert": None},
{"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args


Expand Down Expand Up @@ -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 [
("https://upload.pypi.org/legacy/",),
{"cert": None, "client_cert": None},
{"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args


Expand All @@ -98,7 +98,7 @@ def test_publish_uses_cert(fixture_dir, mocker, config):
assert [("foo", "bar")] == uploader_auth.call_args
assert [
("https://foo.bar",),
{"cert": Path(cert), "client_cert": None},
{"cert": Path(cert), "client_cert": None, "dry_run": False},
] == uploader_upload.call_args


Expand All @@ -119,7 +119,7 @@ def test_publish_uses_client_cert(fixture_dir, mocker, config):

assert [
("https://foo.bar",),
{"cert": None, "client_cert": Path(client_cert)},
{"cert": None, "client_cert": Path(client_cert), "dry_run": False},
] == uploader_upload.call_args


Expand All @@ -137,5 +137,5 @@ def test_publish_read_from_environment_variable(fixture_dir, environ, mocker, co
assert [("bar", "baz")] == uploader_auth.call_args
assert [
("https://foo.bar",),
{"cert": None, "client_cert": None},
{"cert": None, "client_cert": None, "dry_run": False},
] == uploader_upload.call_args

0 comments on commit 3295a70

Please sign in to comment.