Commit 9f7d2133 authored by Stefano Rivera's avatar Stefano Rivera

Update upstream source from tag 'upstream/1.10.0'

Update to upstream version '1.10.0'
with Debian dir acc31e116b2831e3af73c9d04add3ec58a0236e8
parents 9985f435 47188868
......@@ -16,3 +16,4 @@ Adam Talsma <adam@talsma.ca>
Jens Diemer <github@jensdiemer.de> (http://jensdiemer.de/)
Andrew Watts <andrewwatts@gmail.com>
Anna Martelli Ravenscroft <annaraven@gmail.com>
Sumana Harihareswara <sh@changeset.nyc>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -26,7 +26,7 @@ import twine
# -- General configuration ----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
needs_sphinx = '1.7.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
......@@ -43,6 +43,8 @@ extensions = [
releases_issue_uri = "https://github.com/pypa/twine/issues/%s"
releases_release_uri = "https://github.com/pypa/twine/tree/%s"
releases_debug = False # Change to True to see debug output
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
......@@ -57,7 +59,7 @@ master_doc = "index"
# General information about the project.
project = "twine"
copyright = "2013, Donald Stufft and individual contributors"
copyright = "2018, Donald Stufft and individual contributors"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
......
Contributing
============
We are happy you have decided to contribute to ``twine``.
Please see `the GitHub repository`_ for code and more documentation,
and the `official Python Packaging User Guide`_ for user documentation. You can
also join ``#pypa`` or ``#pypa-dev`` `on Freenode`_, or the `pypa-dev
mailing list`_, to ask questions or get involved.
Getting started
---------------
We recommend you use a development environment. Using a ``virtualenv``
keeps your development environment isolated, so that ``twine`` and its
dependencies do not interfere with packages already installed on your
machine. You can use `virtualenv`_ or `pipenv`_ to isolate your
development environment.
Clone the twine repository from GitHub, and then make and activate
your virtual environment, using Python 3.6 as the Python version in
the virtual environment. Example:
.. code-block:: console
mkvirtualenv -p /usr/bin/python3.6 twine
Then, run the following command:
.. code-block:: console
pip install -e /path/to/your/local/twine
Now, in your virtual environment, ``twine`` is pointing at your local copy, so
when you have made changes, you can easily see their effect.
Building the documentation
^^^^^^^^^^^^^^^^^^^^^^^^^^
Additions and edits to twine's documentation are welcome and
appreciated.
We use ``tox`` to build docs. Activate your virtual environment, then
install ``tox``.
.. code-block:: console
pip install tox
If you are using ``pipenv`` to manage your virtual environment, you
may need the `tox-pipenv`_ plugin so that tox can use pipenv
environments instead of virtualenvs.
To build the docs locally using ``tox``, activate your virtual
environment, then run:
.. code-block:: console
tox -e docs
The HTML of the docs will be visible in :file:`twine/docs/_build/`.
When you have made your changes to the docs, please lint them before making a
pull request. To run the linter from the root directory:
.. code-block:: console
doc8 docs
Testing
^^^^^^^
Tests with twine are run using `tox`_, and tested against the following Python
versions: 2.7, 3.4, 3,5, and 3.6. To run these tests locally, you will need to
have these versions of Python installed on your machine.
Either use ``tox`` to build against all supported Python versions (if
you have them installed) or use ``tox -e py{version}`` to test against
a specific version, e.g., ``tox -e py27`` or ``tox -e py34``.
Also, always run ``tox -e pep8`` before submitting a pull request.
Submitting changes
^^^^^^^^^^^^^^^^^^
1. Fork `the GitHub repository`_.
2. Make a branch off of ``master`` and commit your changes to it.
3. Run the tests with ``tox`` and lint any docs changes with ``doc8``.
4. Ensure that your name is added to the end of the :file:`AUTHORS`
file using the format ``Name <email@domain.com> (url)``, where the
``(url)`` portion is optional.
5. Submit a Pull Request to the ``master`` branch on GitHub.
Architectural overview
----------------------
Twine is a command-line tool for interacting with PyPI securely over
HTTPS. Its command line arguments are parsed in
:file:`twine/cli.py`. Currently, twine has two principal functions:
uploading new packages and registering new `projects`_. The code for
registering new projects is in :file:`twine/commands/register.py`, and
the code for uploading is in :file:`twine/commands/upload.py`. The
file :file:`twine/package.py` contains a single class,
``PackageFile``, which hashes the project files and extracts their
metadata. The file :file:`twine/repository.py` contains the
``Repository`` class, whose methods control the URL the package is
uploaded to (which the user can specify either as a default, in the
:file:`.pypirc` file, or pass on the command line), and the methods
that upload the package securely to a URL.
Future development
------------------
See our `open issues`_.
In the future, ``pip`` and ``twine`` may
merge into a single tool; see `ongoing discussion
<https://github.com/pypa/packaging-problems/issues/60>`_.
.. _`official Python Packaging User Guide`: https://packaging.python.org/tutorials/distributing-packages/
.. _`the GitHub repository`: https://github.com/pypa/twine
.. _`on Freenode`: https://webchat.freenode.net/?channels=%23pypa-dev,pypa
.. _`pypa-dev mailing list`: https://groups.google.com/forum/#!forum/pypa-dev
.. _`virtualenv`: https://virtualenv.pypa.io/en/stable/installation/
.. _`pipenv`: https://pipenv.readthedocs.io/en/latest/
.. _`tox`: https://tox.readthedocs.io/en/latest/
.. _`tox-pipenv`: https://pypi.python.org/pypi/tox-pipenv
.. _`plugin`: https://github.com/bitprophet/releases
.. _`projects`: https://packaging.python.org/glossary/#term-project
.. _`open issues`: https://github.com/pypa/twine/issues
.. twine documentation master file, created by
.. twine documentation master file, originally created by
sphinx-quickstart on Tue Aug 13 11:51:54 2013.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
......@@ -6,17 +6,20 @@
Welcome to twine's documentation!
=================================
Contents:
.. toctree::
:maxdepth: 2
.. contents:: Table of Contents
:local:
Twine user documentation
------------------------
.. include:: ../README.rst
:start-after: rtd-inclusion-marker-do-not-remove
.. toctree::
:caption: Further documentation
:maxdepth: 3
Indices and tables
==================
contributing
changelog
Python Packaging User Guide <https://packaging.python.org/tutorials/distributing-packages/>
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
......@@ -8,16 +8,16 @@ ignore =
[metadata]
requires-dist =
clint
requests >= 2.5.0
requests-toolbelt >= 0.5.1
tqdm >= 4.14
requests >= 2.5.0, != 2.15, != 2.16
requests-toolbelt >= 0.8.0
pkginfo >= 1.0
setuptools >= 0.7.0
argparse; python_version == '2.6'
pyblake2; extra == 'with-blake2'
pyblake2; extra == 'with-blake2' and python_version < '3.6'
keyring; extra == 'keyring'
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
# Copyright 2013 Donald Stufft
# Copyright 2018 Donald Stufft and individual contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......@@ -19,10 +19,10 @@ import twine
install_requires = [
"clint",
"tqdm >= 4.14",
"pkginfo >= 1.0",
"requests >= 2.5.0",
"requests-toolbelt >= 0.5.1",
"requests >= 2.5.0, != 2.15, != 2.16",
"requests-toolbelt >= 0.8.0",
"setuptools >= 0.7.0",
]
......@@ -32,6 +32,14 @@ if sys.version_info[:2] < (2, 7):
]
blake2_requires = []
if sys.version_info[:2] < (3, 6):
blake2_requires += [
"pyblake2",
]
setup(
name=twine.__title__,
version=twine.__version__,
......@@ -40,6 +48,11 @@ setup(
long_description=open("README.rst").read(),
license=twine.__license__,
url=twine.__uri__,
project_urls={
'Packaging tutorial': 'https://packaging.python.org/tutorials/distributing-packages/',
'Twine documentation': 'https://twine.readthedocs.io/en/latest/',
'Twine source': 'https://github.com/pypa/twine/',
},
author=twine.__author__,
author_email=twine.__email__,
......@@ -57,10 +70,10 @@ setup(
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
......@@ -79,8 +92,9 @@ setup(
install_requires=install_requires,
extras_require={
'with-blake2': [
'pyblake2',
]
'with-blake2': blake2_requires,
'keyring': [
'keyring',
],
},
)
# 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 twine import __main__ as dunder_main
import pretend
def test_exception_handling(monkeypatch):
replaced_dispatch = pretend.raiser(KeyError('foo'))
monkeypatch.setattr(dunder_main, 'dispatch', replaced_dispatch)
assert dunder_main.main() == 'KeyError: foo'
......@@ -11,8 +11,12 @@
# 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.
import requests
from twine import repository
import pretend
def test_gpg_signature_structure_is_preserved():
data = {
......@@ -90,3 +94,48 @@ def test_make_user_agent_string():
assert 'requests-toolbelt/' in user_agent
assert 'pkginfo/' in user_agent
assert 'setuptools/' in user_agent
def response_with(**kwattrs):
resp = requests.Response()
for attr, value in kwattrs.items():
if hasattr(resp, attr):
setattr(resp, attr, value)
return resp
def test_package_is_uploaded_404s():
repo = repository.Repository(
repository_url='https://pypi.python.org/pypi',
username='username',
password='password',
)
repo.session = pretend.stub(
get=lambda url, headers: response_with(status_code=404)
)
package = pretend.stub(
safe_name='fake',
metadata=pretend.stub(version='2.12.0'),
)
assert repo.package_is_uploaded(package) is False
def test_package_is_uploaded_200s_with_no_releases():
repo = repository.Repository(
repository_url='https://pypi.python.org/pypi',
username='username',
password='password',
)
repo.session = pretend.stub(
get=lambda url, headers: response_with(status_code=200,
_content=b'{"releases": {}}',
_content_consumed=True),
)
package = pretend.stub(
safe_name='fake',
metadata=pretend.stub(version='2.12.0'),
)
assert repo.package_is_uploaded(package) is False
......@@ -88,7 +88,7 @@ def test_get_config_old_format(tmpdir):
except KeyError as err:
assert err.args[0] == (
"Missing 'pypi' section from the configuration file\n"
"or not a complete URL in --repository.\n"
"or not a complete URL in --repository-url.\n"
"Maybe you have a out-dated '{0}' format?\n"
"more info: "
"https://docs.python.org/distutils/packageindex.html#pypirc\n"
......@@ -132,20 +132,23 @@ def test_skip_upload_respects_skip_existing(monkeypatch):
package=pkg) is False
def test_password_and_username_from_env(monkeypatch):
def test_values_from_env(monkeypatch):
def none_upload(*args, **kwargs):
pass
replaced_upload = pretend.call_recorder(none_upload)
monkeypatch.setattr(twine.commands.upload, "upload", replaced_upload)
testenv = {"TWINE_USERNAME": "pypiuser",
"TWINE_PASSWORD": "pypipassword"}
"TWINE_PASSWORD": "pypipassword",
"TWINE_CERT": "/foo/bar.crt"}
with helpers.set_env(**testenv):
cli.dispatch(["upload", "path/to/file"])
cli.dispatch(["upload", "path/to/file"])
result_kwargs = replaced_upload.calls[0].kwargs
assert "pypipassword" == result_kwargs["password"]
assert "pypiuser" == result_kwargs["username"]
assert "/foo/bar.crt" == result_kwargs["cert"]
result_kwargs = replaced_upload.calls[1].kwargs
assert None is result_kwargs["password"]
assert None is result_kwargs["username"]
assert None is result_kwargs["cert"]
......@@ -14,9 +14,15 @@
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
import sys
import os.path
import textwrap
try:
import builtins
except ImportError:
import __builtin__ as builtins
import pytest
from twine import utils
......@@ -115,6 +121,8 @@ def test_get_repository_config_missing(tmpdir):
}
assert (utils.get_repository_from_config(pypirc, 'foo', repository_url) ==
exp)
assert (utils.get_repository_from_config(pypirc, 'pypi', repository_url) ==
exp)
exp = {
"repository": utils.DEFAULT_REPOSITORY,
"username": None,
......@@ -172,3 +180,75 @@ def test_default_to_environment_action(env_name, default, environ, expected):
)
assert action.env == env_name
assert action.default == expected
def test_get_password_keyring_overrides_prompt(monkeypatch):
class MockKeyring:
@staticmethod
def get_password(system, user):
return '{user}@{system} sekure pa55word'.format(**locals())
monkeypatch.setitem(sys.modules, 'keyring', MockKeyring)
pw = utils.get_password('system', 'user', None, {})
assert pw == 'user@system sekure pa55word'
def test_get_password_keyring_defers_to_prompt(monkeypatch):
monkeypatch.setattr(utils, 'password_prompt', lambda prompt: 'entered pw')
class MockKeyring:
@staticmethod
def get_password(system, user):
return
monkeypatch.setitem(sys.modules, 'keyring', MockKeyring)
pw = utils.get_password('system', 'user', None, {})
assert pw == 'entered pw'
@pytest.fixture
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)
@pytest.fixture
def entered_password(monkeypatch):
monkeypatch.setattr(utils, 'password_prompt', lambda prompt: 'entered pw')
def test_get_password_keyring_missing_prompts(
entered_password, keyring_missing):
assert utils.get_password('system', 'user', None, {}) == 'entered pw'
@pytest.fixture
def keyring_no_backends(monkeypatch):
"""
Simulate that keyring has no available backends. When keyring
has no backends for the system, the backend will be a
fail.Keyring, which raises RuntimeError on get_password.
"""
class FailKeyring(object):
@staticmethod
def get_password(system, username):
raise RuntimeError("fail!")
monkeypatch.setitem(sys.modules, 'keyring', FailKeyring())
def test_get_password_runtime_error_suppressed(
entered_password, keyring_no_backends, recwarn):
assert utils.get_password('system', 'user', None, {}) == 'entered pw'
assert len(recwarn) == 1
warning = recwarn.pop(UserWarning)
assert 'fail!' in str(warning)
......@@ -28,3 +28,20 @@ def example_wheel(request):
def test_version_parsing(example_wheel):
assert example_wheel.py_version == 'py2.py3'
def test_find_metadata_files():
names = [
b'package/lib/__init__.py',
b'package/lib/version.py',
b'package/METADATA.txt',
b'package/METADATA.json',
b'package/METADATA',
]
expected = [
['package', 'METADATA'],
['package', 'METADATA.json'],
['package', 'METADATA.txt'],
]
candidates = wheel.Wheel.find_candidate_metadata_files(names)
assert expected == candidates
This diff is collapsed.
......@@ -7,11 +7,13 @@ setup.py
docs/Makefile
docs/changelog.rst
docs/conf.py
docs/contributing.rst
docs/index.rst
docs/make.bat
docs/_static/.empty
tests/helpers.py
tests/test_cli.py
tests/test_main.py
tests/test_package.py
tests/test_repository.py
tests/test_upload.py
......@@ -31,7 +33,6 @@ twine.egg-info/PKG-INFO
twine.egg-info/SOURCES.txt
twine.egg-info/dependency_links.txt
twine.egg-info/entry_points.txt
twine.egg-info/pbr.json
twine.egg-info/requires.txt
twine.egg-info/top_level.txt
twine/commands/__init__.py
......
{"is_release": true, "git_version": "5c06ed2"}
\ No newline at end of file
clint
pkginfo >= 1.0
requests >= 2.5.0
requests-toolbelt >= 0.5.1
setuptools >= 0.7.0
tqdm>=4.14
pkginfo>=1.0
requests!=2.15,!=2.16,>=2.5.0
requests-toolbelt>=0.8.0
setuptools>=0.7.0
[keyring]
keyring
[with-blake2]
pyblake2
# Copyright 2013 Donald Stufft
# Copyright 2018 Donald Stufft and individual contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......@@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__all__ = (
"__title__", "__summary__", "__uri__", "__version__", "__author__",
......@@ -20,13 +19,13 @@ __all__ = (
)
__title__ = "twine"
__summary__ = "Collection of utilities for interacting with PyPI"
__uri__ = "https://github.com/pypa/twine"
__summary__ = "Collection of utilities for publishing packages on PyPI"
__uri__ = "http://twine.readthedocs.io/"
__version__ = "1.8.1"
__version__ = "1.10.0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "Apache License, Version 2.0"
__copyright__ = "Copyright 2013 Donald Stufft"
__copyright__ = "Copyright 2018 Donald Stufft and individual contributors"
......@@ -21,7 +21,13 @@ from twine.cli import dispatch
def main():
return dispatch(sys.argv[1:])
try:
return dispatch(sys.argv[1:])
except Exception as exc:
return '{0}: {1}'.format(
exc.__class__.__name__,
exc.args[0],
)
if __name__ == "__main__":
......
......@@ -18,7 +18,7 @@ import argparse
import pkg_resources
import setuptools
import clint
import tqdm
import requests
import requests_toolbelt
import pkginfo
......@@ -38,7 +38,7 @@ def list_dependencies_and_versions():
('requests', requests.__version__),
('setuptools', setuptools.__version__),
('requests-toolbelt', requests_toolbelt.__version__),
('clint', clint.__version__),
('tqdm', tqdm.__version__),
]
......
......@@ -15,7 +15,6 @@ from __future__ import absolute_import, unicode_literals, print_function
import argparse
import os.path
import sys
from twine import exceptions as exc
from twine.package import PackageFile
......@@ -37,7 +36,9 @@ def register(package, repository, username, password, comment, config_file,
print("Registering package to {0}".format(config["repository"]))
username = utils.get_username(username, config)
password = utils.get_password(password, config)
password = utils.get_password(
config["repository"], username, password, config,
)
ca_cert = utils.get_cacert(cert, config)
client_cert = utils.get_clientcert(client_cert, config)
......@@ -69,9 +70,10 @@ def main(args):
action=utils.EnvironmentDefault,
env="TWINE_REPOSITORY",
default="pypi",
help="The repository to register the package to. Can be a section in "
"the config file or a full URL to the repository (default: "
"%(default)s)",
help="The repository to register the package to. "
"Should be a section in the config file (default: "
"%(default)s). (Can also be set via %(env)s environment "
"variable)",
)
parser.add_argument(
"--repository-url",
......@@ -79,9 +81,9 @@ def main(args):
env="TWINE_REPOSITORY_URL",
default=None,
required=False,
help="The repository URL to upload the package to. This can be "
"specified with --repository because it will be used if there is "
"no configuration for the value passed to --repository."
help="The repository URL to register the package to. "
"This overrides --repository."
"(Can also be set via %(env)s environment variable.)"
)
parser.add_argument(
"-u", "--username",
......@@ -110,14 +112,19 @@ def main(args):
)
parser.add_argument(
"--cert",
action=utils.EnvironmentDefault,
env="TWINE_CERT",
default=None,
required=False,
metavar="path",
help="Path to alternate CA bundle",
help="Path to alternate CA bundle (can also be set via %(env)s "
"environment variable)",
)
parser.add_argument(
"--client-cert",
metavar="path",
help="Path to SSL client certificate, a single file containing the "
"private key and the certificate in PEM forma",
"private key and the certificate in PEM format",
)
parser.add_argument(
"package",
......@@ -128,7 +135,4 @@ def main(args):
args = parser.parse_args(args)
# Call the register function with the args from the command line
try:
register(**vars(args))
except Exception as exc:
sys.exit("{exc.__class__.__name__}: {exc}".format(exc=exc))
register(**vars(args))
......@@ -21,7 +21,7 @@ import sys
import twine.exceptions as exc
from twine.package import PackageFile
from twine.repository import Repository
from twine.repository import Repository, LEGACY_PYPI
from twine import utils
......@@ -98,8 +98,16 @@ def upload(dists, repository, sign, identity, username, password, comment,
print("Uploading distributions to {0}".format(config["repository"]))
if config["repository"].startswith(LEGACY_PYPI):
print(
"Note: you are uploading to the old upload URL. It's recommended "
"to use the new URL \"{0}\" or to leave the URL unspecified and "
"allow twine to choose.".format(utils.DEFAULT_REPOSITORY))
username = utils.get_username(username, config)
password = utils.get_password(password, config)
password = utils.get_password(
config["repository"], username, password, config,
)
ca_cert = utils.get_cacert(cert, config)
client_cert = utils.get_clientcert(client_cert, config)
......@@ -157,9 +165,10 @@ def main(args):
action=utils.EnvironmentDefault,
env="TWINE_REPOSITORY",
default="pypi",
help="The repository to register the package to. Can be a section in "
"the config file or a full URL to the repository (default: "
"%(default)s)",
help="The repository to upload the package to. "
"Should be a section in the config file (default: "
"%(default)s). (Can also be set via %(env)s environment "
"variable)",
)
parser.add_argument(