Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "check" command #395

Merged
merged 8 commits into from
Sep 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ The next time you run ``twine`` it will prompt you for a username and will grab
Options
-------

``twine upload``
^^^^^^^^^^^^^^^^

Uploads one or more distributions to a repository.

.. code-block:: console

$ twine upload -h
Expand Down Expand Up @@ -191,16 +196,29 @@ Options
containing the private key and the certificate in PEM
format.

Twine also includes a ``register`` command.
``twine check``
^^^^^^^^^^^^^^^

Checks whether your distributions long description will render correctly on PyPI.

.. code-block:: console

$ twine check -h
usage: twine check [-h] dist [dist ...]

positional arguments:
dist The distribution files to check, usually dist/*

optional arguments:
-h, --help show this help message and exit

``twine register``
^^^^^^^^^^^^^^^^^^

.. WARNING::
``register`` is `no longer necessary if you are
uploading to pypi.org
<https://packaging.python.org/guides/migrating-to-pypi-org/#registering-package-names-metadata>`_. As
such, it is `no longer supported
<https://github.com/pypa/warehouse/issues/1627>`_ in `Warehouse`_
(the new PyPI software running on pypi.org). However, you may need
this if you are using a different package index.
**WARNING**: The ``register`` command is `no longer necessary if you are uploading to
pypi.org`_. As such, it is `no longer supported`_ in `Warehouse`_ (the new
PyPI software running on pypi.org). However, you may need this if you are using
a different package index.

For completeness, its usage:

Expand Down Expand Up @@ -300,3 +318,5 @@ trackers, chat rooms, and mailing lists is expected to follow the
.. _`PyPA Code of Conduct`: https://www.pypa.io/en/latest/code-of-conduct/
.. _`Warehouse`: https://github.com/pypa/warehouse
.. _`wheels`: https://packaging.python.org/glossary/#term-wheel
.. _`no longer necessary if you are uploading to pypi.org`: https://packaging.python.org/guides/migrating-to-pypi-org/#registering-package-names-metadata
.. _`no longer supported`: https://github.com/pypa/warehouse/issues/1627
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Changelog
=========

* :feature:`395 major` Add ``twine check`` command to check long description
* :feature:`392 major` Drop support for Python 3.3
* :release:`1.11.0 <2018-03-19>`
* :bug:`269 major` Avoid uploading to PyPI when given alternate
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
install_requires = [
"tqdm >= 4.14",
"pkginfo >= 1.4.2",
"readme_renderer >= 21.0",
"requests >= 2.5.0, != 2.15, != 2.16",
"requests-toolbelt >= 0.8.0",
"setuptools >= 0.7.0",
Expand Down Expand Up @@ -81,6 +82,7 @@

entry_points={
"twine.registered_commands": [
"check = twine.commands.check:main",
"upload = twine.commands.upload:main",
"register = twine.commands.register:main",
],
Expand Down
119 changes: 119 additions & 0 deletions tests/test_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright 2018 Dustin Ingram
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import unicode_literals

import pretend

from twine.commands import check


def test_warningstream_write_match():
stream = check._WarningStream()
stream.output = pretend.stub(write=pretend.call_recorder(lambda a: None))

stream.write("<string>:2: (WARNING/2) Title underline too short.")

assert stream.output.write.calls == [
pretend.call("line 2: Warning: Title underline too short.\n")
]


def test_warningstream_write_nomatch():
stream = check._WarningStream()
stream.output = pretend.stub(write=pretend.call_recorder(lambda a: None))

stream.write("this does not match")

assert stream.output.write.calls == [pretend.call("this does not match")]


def test_warningstream_str():
stream = check._WarningStream()
stream.output = pretend.stub(getvalue=lambda: "result")

assert str(stream) == "result"


def test_check_no_distributions(monkeypatch):
stream = check.StringIO()

monkeypatch.setattr(check, "_find_dists", lambda a: [])

assert not check.check("dist/*", output_stream=stream)
assert stream.getvalue() == ""


def test_check_passing_distribution(monkeypatch):
renderer = pretend.stub(
render=pretend.call_recorder(lambda *a, **kw: "valid")
)
package = pretend.stub(metadata_dictionary=lambda: {"description": "blah"})
output_stream = check.StringIO()
warning_stream = ""

monkeypatch.setattr(check, "_RENDERERS", {"": renderer})
monkeypatch.setattr(check, "_find_dists", lambda a: ["dist/dist.tar.gz"])
monkeypatch.setattr(
check,
"PackageFile",
pretend.stub(from_filename=lambda *a, **kw: package),
)
monkeypatch.setattr(check, "_WarningStream", lambda: warning_stream)

assert not check.check("dist/*", output_stream=output_stream)
assert (
output_stream.getvalue()
== "Checking distribution dist/dist.tar.gz: Passed\n"
)
assert renderer.render.calls == [
pretend.call("blah", stream=warning_stream)
]


def test_check_failing_distribution(monkeypatch):
renderer = pretend.stub(
render=pretend.call_recorder(lambda *a, **kw: None)
)
package = pretend.stub(metadata_dictionary=lambda: {"description": "blah"})
output_stream = check.StringIO()
warning_stream = "WARNING"

monkeypatch.setattr(check, "_RENDERERS", {"": renderer})
monkeypatch.setattr(check, "_find_dists", lambda a: ["dist/dist.tar.gz"])
monkeypatch.setattr(
check,
"PackageFile",
pretend.stub(from_filename=lambda *a, **kw: package),
)
monkeypatch.setattr(check, "_WarningStream", lambda: warning_stream)

assert check.check("dist/*", output_stream=output_stream)
assert output_stream.getvalue() == (
"Checking distribution dist/dist.tar.gz: Failed\n"
"The project's long_description has invalid markup which will not be "
"rendered on PyPI. The following syntax errors were detected:\n"
"WARNING"
)
assert renderer.render.calls == [
pretend.call("blah", stream=warning_stream)
]


def test_main(monkeypatch):
check_result = pretend.stub()
check_stub = pretend.call_recorder(lambda a: check_result)
monkeypatch.setattr(check, "check", check_stub)

assert check.main(["dist/*"]) == check_result
assert check_stub.calls == [pretend.call(["dist/*"])]
50 changes: 50 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os

import pytest

from twine.commands import _find_dists, _group_wheel_files_first


def test_ensure_wheel_files_uploaded_first():
files = _group_wheel_files_first(
["twine/foo.py", "twine/first.whl", "twine/bar.py", "twine/second.whl"]
)
expected = [
"twine/first.whl",
"twine/second.whl",
"twine/foo.py",
"twine/bar.py",
]
assert expected == files


def test_ensure_if_no_wheel_files():
files = _group_wheel_files_first(["twine/foo.py", "twine/bar.py"])
expected = ["twine/foo.py", "twine/bar.py"]
assert expected == files


def test_find_dists_expands_globs():
files = sorted(_find_dists(["twine/__*.py"]))
expected = [
os.path.join("twine", "__init__.py"),
os.path.join("twine", "__main__.py"),
]
assert expected == files


def test_find_dists_errors_on_invalid_globs():
with pytest.raises(ValueError):
_find_dists(["twine/*.rb"])


def test_find_dists_handles_real_files():
expected = [
"twine/__init__.py",
"twine/__main__.py",
"twine/cli.py",
"twine/utils.py",
"twine/wheel.py",
]
files = _find_dists(expected)
assert expected == files
39 changes: 0 additions & 39 deletions tests/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,45 +28,6 @@
WHEEL_FIXTURE = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl'


def test_ensure_wheel_files_uploaded_first():
files = upload.group_wheel_files_first(["twine/foo.py",
"twine/first.whl",
"twine/bar.py",
"twine/second.whl"])
expected = ["twine/first.whl",
"twine/second.whl",
"twine/foo.py",
"twine/bar.py"]
assert expected == files


def test_ensure_if_no_wheel_files():
files = upload.group_wheel_files_first(["twine/foo.py",
"twine/bar.py"])
expected = ["twine/foo.py",
"twine/bar.py"]
assert expected == files


def test_find_dists_expands_globs():
files = sorted(upload.find_dists(['twine/__*.py']))
expected = [os.path.join('twine', '__init__.py'),
os.path.join('twine', '__main__.py')]
assert expected == files


def test_find_dists_errors_on_invalid_globs():
with pytest.raises(ValueError):
upload.find_dists(['twine/*.rb'])


def test_find_dists_handles_real_files():
expected = ['twine/__init__.py', 'twine/__main__.py', 'twine/cli.py',
'twine/utils.py', 'twine/wheel.py']
files = upload.find_dists(expected)
assert expected == files


def test_get_config_old_format(tmpdir):
pypirc = os.path.join(str(tmpdir), ".pypirc")

Expand Down
13 changes: 1 addition & 12 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@
import os.path
import textwrap

try:
import builtins
except ImportError:
import __builtin__ as builtins

import pytest

from twine import utils
Expand Down Expand Up @@ -239,13 +234,7 @@ def keyring_missing(monkeypatch):
"""
Simulate that 'import keyring' raises an ImportError
"""
real_import = builtins.__import__

def my_import(name, *args, **kwargs):
if name == 'keyring':
raise ImportError
return real_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, '__import__', my_import)
monkeypatch.delitem(sys.modules, 'keyring', raising=False)


@pytest.fixture
Expand Down
13 changes: 9 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@ deps =
pretend
pytest
py27,py34,py35: pyblake2
readme_renderer
commands =
coverage run --source twine -m pytest {posargs:tests}
coverage report -m

[testenv:docs]
basepython = python3.6
deps = -rdocs/requirements.txt
deps =
.
-rdocs/requirements.txt
commands =
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
doc8 docs
sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/linkcheck
python setup.py check -r -s
python setup.py sdist
twine check dist/*

[testenv:release]
deps =
Expand All @@ -32,12 +36,13 @@ commands =
[testenv:lint]
basepython = python3.6
deps =
.
flake8
check-manifest
readme_renderer
mypy
commands =
flake8 twine/ tests/
check-manifest -v
python setup.py check -r -s
python setup.py sdist
twine check dist/*
-mypy -s twine/ tests/
2 changes: 1 addition & 1 deletion twine/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ def dispatch(argv):

main = registered_commands[args.command].load()

main(args.args)
return main(args.args)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very breaking change since the CLI now returns 1 (which is the standard code for "failure") on a successful execution.

Loading