diff --git a/.travis.yml b/.travis.yml index db0dfd37..8e37d475 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,6 @@ matrix: name: Linting code smells env: TOXENV: lint-code-style - - python: 3.7 - name: Linting distribution metadata - env: - TOXENV: lint-dist-meta - python: 3.7 name: Linting type matching env: diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index f7511579..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include LICENSE -include README.rst -include AUTHORS -include .coveragerc -include mypy.ini - -recursive-include tests *.py *.whl *.gz deprecated-pypirc -recursive-include docs *.bat *.empty *.py *.rst Makefile *.txt - -prune docs/_build -prune *.yml diff --git a/README.rst b/README.rst index 3293a791..532714ca 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ .. image:: https://img.shields.io/pypi/pyversions/twine.svg :target: https://pypi.org/project/twine - + .. image:: https://img.shields.io/readthedocs/twine :target: https://twine.readthedocs.io @@ -103,31 +103,16 @@ Keyring Support --------------- Instead of typing in your password every time you upload a distribution, Twine -allows you to store your username and password securely using `keyring`_. - -To use the keyring, you must first install the keyring packages: +allows storing a username and password securely using `keyring`_. +Keyring is installed with Twine but for some systems (Linux mainly) may +require +`additional installation steps `_. -- On Windows and MacOS you just need to install ``keyring``, for example, - ``pip install --user keyring``. -- On Linux, in addition to the ``keyring`` package you also need to ensure the - ``python3-dbus`` system package is installed. For example, ``apt install - python3-dbus``. See `Keyring's installation instructions`_ for more details. +Once Twine is installed, use the ``keyring`` program to set a +username and password to use for each package index (repository) to +which you may upload. -Once keyring is installed you can use the ``keyring`` program to set your -username and password to use for each package index (repository) you want to -upload to using Twine. - -To set your username and password for Test PyPI run the following command. -``keyring`` will prompt you for your password: - -.. code-block:: console - - $ keyring set https://test.pypi.org/legacy/ your-username - # or - $ python3 -m keyring set https://test.pypi.org/legacy/ your-username - -To set your username and password for PyPI run this command, again, ``keyring`` -will prompt for the password: +For example, to set a username and password for PyPI: .. code-block:: console @@ -135,8 +120,12 @@ will prompt for the password: # or $ python3 -m keyring set https://upload.pypi.org/legacy/ your-username +And enter the password when prompted. + +For a different repository, replace the URL with the relevant repository +URL. For example, for Test PyPI, use ``https://test.pypi.org/legacy/``. -The next time you run ``twine`` it will prompt you for a username and will grab +The next time you run ``twine``, it will prompt you for a username and will grab the appropriate password from the keyring. .. Note:: If you are using Linux in a headless environment (such as on a @@ -144,20 +133,30 @@ the appropriate password from the keyring. store secrets securely. See `Using Keyring on headless systems`_. .. _`keyring`: https://pypi.org/project/keyring/ -.. _`Keyring's installation instructions`: - https://keyring.readthedocs.io/en/latest#installation-instructions .. _`Using Keyring on headless systems`: https://keyring.readthedocs.io/en/latest/#using-keyring-on-headless-linux-systems Disabling Keyring ^^^^^^^^^^^^^^^^^ -In some cases, the presence of keyring may be problematic. To disable -keyring and defer to a prompt for passwords, uninstall ``keyring`` -or if that's not an option, you can also configure keyring to be disabled. +In most cases, simply not setting a password in keyring will allow twine +to fall back to prompting for a password. In some cases, the presence of +keyring will cause unexpected or undesirable prompts from the backing +system. In these cases, it may be desirable to disable keyring altogether. +To disable keyring, simply invoke: + +.. code-block:: console + + $ keyring --disable + or + $ python -m keyring --disable + +That command will configure for the current user the "null" keyring, +effectively disabling the functionality, and allowing Twine to prompt +for passwords. -See `twine 338 `_ for a -discussion on ways to do that. +See `twine 338 `_ for +discussion and background. Options ------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 7eabb304..a95a3ffa 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,8 +3,10 @@ ========= Changelog ========= -* :feature:`520`: Remove ``no_positional`` decorator in favor of native - syntax. +* :feature:`524`: Twine now unconditionally requires the keyring library + and no longer supports uninstalling ``keyring`` as a means to disable + that functionality. Instead, use ``keyring --disable`` keyring functionality + if necessary. * :feature:`518` Add Python 3.8 to classifiers. * :bug:`332` More robust handling of server response in ``--skip-existing`` * :release:`2.0.0 <2019-09-24>` diff --git a/docs/contributing.rst b/docs/contributing.rst index 4dafa205..7c201811 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -199,12 +199,7 @@ A checklist for creating, testing, and distributing a new version. #. View your tag: ``git tag -v {number}`` #. Push your tag: ``git push upstream {number}``. -#. Delete old distributions: ``rm dist/*``. -#. Create distributions with ``python setup.py sdist bdist_wheel``. -#. Set your TestPyPI and canon PyPI credentials in your session with - ``keyring`` (docs forthcoming). -#. Upload to Test PyPI: :command:`twine upload --repository-url - https://test.pypi.org/legacy/ --skip-existing dist/*` +#. Upload to TestPyPI with ``TWINE_REPOSITORY=https://test.pypi.org/legacy/ tox -e release`` #. Verify that everything looks good, downloads ok, etc. Make needed fixes. #. Merge the last PR before the new release: @@ -218,12 +213,7 @@ A checklist for creating, testing, and distributing a new version. #. Create a new git tag with ``git tag -sam 'Release v{number}' {number}``. #. View your tag: ``git tag -v {number}`` #. Push your tag: ``git push upstream {number}``. -#. Delete old distributions: ``rm dist/*``. -#. Create distributions with ``python setup.py sdist bdist_wheel``. -#. On a Monday or Tuesday, upload to canon PyPI: :command:`twine - upload --skip-existing dist/*` - - .. note:: Will be replaced by ``tox -e release`` at some point. +#. On a Monday or Tuesday, upload to PyPI with ``tox -e release``. #. Send announcement email to `pypa-dev mailing list`_ and celebrate. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c5effd95 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=40.8", "wheel", "setuptools_scm>=1.15"] +build-backend = "setuptools.build_meta:__legacy__" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..4d8092d8 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +filterwarnings= + # workaround for https://github.com/mozilla/bleach/issues/425 + ignore:Using or importing the ABCs:DeprecationWarning:bleach + # workaround for https://github.com/pypa/setuptools/issues/479 + ignore:the imp module is deprecated::setuptools diff --git a/setup.cfg b/setup.cfg index cf83245f..0c9e0fc1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,2 @@ -[check-manifest] -ignore = - .travis.yml - tox.ini - .github - .github/* - [metadata] license_file = LICENSE diff --git a/setup.py b/setup.py index 4830ace8..62436757 100644 --- a/setup.py +++ b/setup.py @@ -73,10 +73,9 @@ "requests-toolbelt >= 0.8.0, != 0.9.0", "setuptools >= 0.7.0", "tqdm >= 4.14", + "keyring >= 15.1", + ], + setup_requires=[ + 'setuptools_scm>=1.15', ], - extras_require={ - 'keyring': [ - 'keyring', - ], - }, ) diff --git a/tests/test_utils.py b/tests/test_utils.py index eeb4b590..1f3d6b45 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,7 +11,7 @@ # 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 sys + import os.path import textwrap @@ -227,7 +227,7 @@ class MockKeyring: def get_password(system, user): return '{user}@{system} sekure pa55word'.format(**locals()) - monkeypatch.setitem(sys.modules, 'keyring', MockKeyring) + monkeypatch.setattr(utils, 'keyring', MockKeyring) pw = utils.get_password('system', 'user', None, {}) assert pw == 'user@system sekure pa55word' @@ -241,7 +241,7 @@ class MockKeyring: def get_password(system, user): return - monkeypatch.setitem(sys.modules, 'keyring', MockKeyring) + monkeypatch.setattr(utils, 'keyring', MockKeyring) pw = utils.get_password('system', 'user', None, {}) assert pw == 'entered pw' @@ -278,7 +278,7 @@ def get_password(system, user): raise RuntimeError("unexpected username") return cred.password - monkeypatch.setitem(sys.modules, 'keyring', MockKeyring) + monkeypatch.setattr(utils, 'keyring', MockKeyring) user = utils.get_username('system', None, {}) assert user == 'real_user' @@ -286,22 +286,13 @@ def get_password(system, user): assert pw == 'real_user@system sekure pa55word' -@pytest.fixture -def keyring_missing(monkeypatch): - """ - Simulate that 'import keyring' raises an ImportError - """ - monkeypatch.delitem(sys.modules, 'keyring', raising=False) - - @pytest.fixture def keyring_missing_get_credentials(monkeypatch): """ - Simulate older versions of keyring that do not have the - 'get_credentials' API. + Simulate keyring prior to 15.2 that does not have the + 'get_credential' API. """ - monkeypatch.delattr('keyring.backends.KeyringBackend', - 'get_credential', raising=False) + monkeypatch.delattr(utils.keyring, 'get_credential') @pytest.fixture @@ -319,11 +310,6 @@ def test_get_username_keyring_missing_get_credentials_prompts( assert utils.get_username('system', None, {}) == 'entered user' -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): """ @@ -335,7 +321,7 @@ class FailKeyring: @staticmethod def get_password(system, username): raise RuntimeError("fail!") - monkeypatch.setitem(sys.modules, 'keyring', FailKeyring()) + monkeypatch.setattr(utils, 'keyring', FailKeyring()) @pytest.fixture @@ -343,13 +329,13 @@ def keyring_no_backends_get_credential(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. + fail.Keyring, which raises RuntimeError on get_credential. """ class FailKeyring: @staticmethod def get_credential(system, username): raise RuntimeError("fail!") - monkeypatch.setitem(sys.modules, 'keyring', FailKeyring()) + monkeypatch.setattr(utils, 'keyring', FailKeyring()) def test_get_username_runtime_error_suppressed( diff --git a/tox.ini b/tox.ini index 7b7fa8e7..1ea48a79 100644 --- a/tox.ini +++ b/tox.ini @@ -23,12 +23,23 @@ commands = twine check dist/* [testenv:release] +# disabled for twine to cause it to upload itself +# skip_install = True +# specify Python 3 to use platform's default Python 3 basepython = python3 deps = - wheel + pep517>=0.5 + twine>=1.13 + path.py +passenv = + TWINE_PASSWORD + TWINE_REPOSITORY +setenv = + TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = - python setup.py -q bdist_wheel sdist - twine upload --skip-existing dist/* + python -c "import path; path.Path('dist').rmtree_p()" + python -m pep517.build . + python -m twine upload dist/* [testenv:lint-code-style] deps = @@ -36,14 +47,6 @@ deps = commands = flake8 twine/ tests/ -[testenv:lint-dist-meta] -deps = - check-manifest -commands = - check-manifest -v - python setup.py sdist - twine check dist/* - [testenv:lint-mypy] deps = mypy @@ -55,9 +58,7 @@ commands = [testenv:lint] deps = {[testenv:lint-code-style]deps} - {[testenv:lint-dist-meta]deps} {[testenv:lint-mypy]deps} commands = {[testenv:lint-code-style]commands} - {[testenv:lint-dist-meta]commands} {[testenv:lint-mypy]commands} diff --git a/twine/utils.py b/twine/utils.py index d0cee932..6b56498c 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -17,7 +17,6 @@ import os.path import functools import getpass -import sys import argparse import warnings import collections @@ -25,13 +24,7 @@ from urllib.parse import urlparse, urlunparse import requests - -# TODO: Unconditionally require keyring -# https://github.com/pypa/twine/issues/524 -try: - import keyring # noqa -except ImportError: - pass +import keyring from twine import exceptions @@ -219,24 +212,13 @@ def get_userpass_value( # They seem to do similar things, but this has more exception handling # Could they have a similar (maybe simpler) structure? def get_username_from_keyring(system: str) -> Optional[str]: - # TODO: Is this necessary? Could we catch a NameError instead? - if 'keyring' not in sys.modules: - return None - - try: - getter = ( - # Workaround mypy error `module has no attribute "get_credential"` - # Revealed type is '_importlib_modulespec.ModuleType*' - sys.modules['keyring'] # type: ignore - .get_credential - ) - except AttributeError: - return None - try: - creds = getter(system, None) + creds = keyring.get_credential(system, None) if creds: return creds.username + except AttributeError: + # To support keyring prior to 15.2 + pass except Exception as exc: warnings.warn(str(exc)) @@ -247,18 +229,9 @@ def password_prompt(prompt_text: str) -> str: return getpass.getpass(prompt_text) -# TODO: See TODOs in get_username_from_keyring -def get_password_from_keyring(system: str, username: str) -> Optional[str]: - if 'keyring' not in sys.modules: - return None - +def get_password_from_keyring(system, username): try: - return ( - # Workaround mypy error `module has no attribute "get_password"` - # Revealed type is '_importlib_modulespec.ModuleType*' - sys.modules['keyring'] # type: ignore - .get_password(system, username) - ) + return keyring.get_password(system, username) except Exception as exc: warnings.warn(str(exc))