././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8811786 twine-3.1.1/0000775000372000037200000000000000000000000013505 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/.codecov.yml0000664000372000037200000000001500000000000015724 0ustar00travistravis00000000000000comment: off ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/.coveragerc0000664000372000037200000000073500000000000015633 0ustar00travistravis00000000000000[run] branch = True [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain if non-runnable code isn't run if __name__ == .__main__.: # Paths to omit from consideration omit = # __main__.py exists only as a very basic wrapper around warehouse.cli # and exists only to provide setuptools and python -m a place to point # at. */twine/__main__.py ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8731787 twine-3.1.1/.github/0000775000372000037200000000000000000000000015045 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/.github/ISSUE_TEMPLATE.md0000664000372000037200000000166300000000000017560 0ustar00travistravis00000000000000## Your Environment Thank you for taking the time to report an issue. To more efficiently resolve this issue, we'd like to know some basic information about your system and setup. 1) Your operating system: 2) Version of python you are running: ``` bash python --version ``` 3) How did you install twine? Did you use your operating system's package manager or pip or something else? 4) Version of twine you have installed (include complete output of): ``` bash twine --version ``` 5) Which package repository are you targeting? If you're having issues uploading a specific package, you *must* include a copy of the following: * The package's `PKG-INFO` file * A redacted version of your `.pypirc` file (**REMOVE ALL USERNAMES & PASSWORDS BEFORE UPLOADING**) ## The Issue Please describe the issue that you are experiencing. ## Steps to Reproduce If the issue is predictable and consistently reproducible, please list the steps here. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/.gitignore0000664000372000037200000000245300000000000015501 0ustar00travistravis00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST pip-wheel-metadata/ # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json monkeytype.sqlite3 mypy/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/.travis.yml0000664000372000037200000000146600000000000015625 0ustar00travistravis00000000000000dist: xenial language: python cache: pip env: global: TOXENV: python matrix: fast_finish: true include: - python: 3.7 name: Linting code smells env: TOXENV: lint-code-style - python: 3.7 name: Linting type matching env: TOXENV: lint-mypy - python: 3.7 name: Making sure that docs build is healthy env: TOXENV: docs - python: &latest_py3 3.8 - python: 3.7 - python: 3.6 - stage: deploy if: tag IS present python: *latest_py3 env: TOXENV: release after_script: skip install: - pip install tox codecov script: - tox after_script: - codecov --env TRAVIS_OS_NAME,TOXENV notifications: irc: channels: - "irc.freenode.org#pypa-dev" use_notice: true skip_join: true ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/AUTHORS0000664000372000037200000000227400000000000014562 0ustar00travistravis00000000000000# A list of people who have contributed to twine in order of their first # contribution. # # Uses the format of ``Name (url)`` with the ``(url)`` # being optional. Donald Stufft (https://caremad.io/) Jannis Leidel Ralf Schmitt Ian Cordasco Marc Abramowitz (http://marc-abramowitz.com/) Tom Myers Rodrigue Cloutier Tyrel Souza (https://tyrelsouza.com) Adam Talsma Jens Diemer (http://jensdiemer.de/) Andrew Watts Anna Martelli Ravenscroft Sumana Harihareswara Dustin Ingram (https://di.codes) Jesse Jarzynka (https://www.jessejoe.com/) László Kiss Kollár Frances Hocutt Tathagata Dasgupta Wasim Thabraze Varun Kamath Brian Rutledge Peter Stensmyr (http://www.peterstensmyr.com) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/LICENSE0000664000372000037200000002273700000000000014525 0ustar00travistravis00000000000000 Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8811786 twine-3.1.1/PKG-INFO0000664000372000037200000004446300000000000014615 0ustar00travistravis00000000000000Metadata-Version: 1.2 Name: twine Version: 3.1.1 Summary: Collection of utilities for publishing packages on PyPI Home-page: https://twine.readthedocs.io/ Author: Donald Stufft and individual contributors Author-email: donald@stufft.io License: UNKNOWN Project-URL: Packaging tutorial, https://packaging.python.org/tutorials/distributing-packages/ Project-URL: Travis CI, https://travis-ci.org/pypa/twine/ Project-URL: Twine documentation, https://twine.readthedocs.io/en/latest/ Project-URL: Twine source, https://github.com/pypa/twine/ Description: .. image:: https://img.shields.io/pypi/v/twine.svg :target: https://pypi.org/project/twine .. 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 .. image:: https://img.shields.io/travis/com/pypa/twine :target: https://travis-ci.org/pypa/twine .. image:: https://img.shields.io/codecov/c/github/pypa/twine :target: https://codecov.io/gh/pypa/twine twine ===== .. rtd-inclusion-marker-do-not-remove Twine is `a utility`_ for `publishing`_ Python packages on `PyPI`_. It provides build system independent uploads of source and binary `distribution artifacts `_ for both new and existing `projects`_. Why Should I Use This? ---------------------- The goal of ``twine`` is to improve PyPI interaction by improving security and testability. The biggest reason to use ``twine`` is that it securely authenticates you to `PyPI`_ over HTTPS using a verified connection, regardless of the underlying Python version. Meanwhile, ``python setup.py upload`` will only work correctly and securely if your build system, Python version, and underlying operating system are configured properly. Secondly, ``twine`` encourages you to build your distribution files. ``python setup.py upload`` only allows you to upload a package as a final step after building with ``distutils`` or ``setuptools``, within the same command invocation. This means that you cannot test the exact file you're going to upload to PyPI to ensure that it works before uploading it. Finally, ``twine`` allows you to pre-sign your files and pass the ``.asc`` files into the command line invocation (``twine upload myproject-1.0.1.tar.gz myproject-1.0.1.tar.gz.asc``). This enables you to be assured that you're typing your ``gpg`` passphrase into ``gpg`` itself and not anything else, since *you* will be the one directly executing ``gpg --detach-sign -a ``. Features -------- - Verified HTTPS connections - Uploading doesn't require executing ``setup.py`` - Uploading files that have already been created, allowing testing of distributions before release - Supports uploading any packaging format (including `wheels`_) Installation ------------ .. code-block:: console $ pip install twine Using Twine ----------- 1. Create some distributions in the normal way: .. code-block:: console $ python setup.py sdist bdist_wheel 2. Upload with ``twine`` to `Test PyPI`_ and verify things look right. Twine will automatically prompt for your username and password: .. code-block:: console $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* username: ... password: ... 3. Upload to `PyPI`_: .. code-block:: console $ twine upload dist/* 4. Done! More documentation on using ``twine`` to upload packages to PyPI is in the `Python Packaging User Guide`_. Keyring Support --------------- Instead of typing in your password every time you upload a distribution, Twine 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 `_. 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. For example, to set a username and password for PyPI: .. code-block:: console $ keyring set https://upload.pypi.org/legacy/ your-username # 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 appropriate password from the keyring. .. Note:: If you are using Linux in a headless environment (such as on a server) you'll need to do some additional steps to ensure that Keyring can store secrets securely. See `Using Keyring on headless systems`_. .. _`keyring`: https://pypi.org/project/keyring/ .. _`Using Keyring on headless systems`: https://keyring.readthedocs.io/en/latest/#using-keyring-on-headless-linux-systems Disabling Keyring ^^^^^^^^^^^^^^^^^ 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 discussion and background. Options ------- ``twine upload`` ^^^^^^^^^^^^^^^^ Uploads one or more distributions to a repository. .. code-block:: console $ twine upload -h usage: twine upload [-h] [-r REPOSITORY] [--repository-url REPOSITORY_URL] [-s] [--sign-with SIGN_WITH] [-i IDENTITY] [-u USERNAME] [-p PASSWORD] [-c COMMENT] [--config-file CONFIG_FILE] [--skip-existing] [--cert path] [--client-cert path] [--verbose] [--disable-progress-bar] dist [dist ...] positional arguments: dist The distribution files to upload to the repository (package index). Usually dist/* . May additionally contain a .asc file to include an existing signature with the file upload. optional arguments: -h, --help show this help message and exit -r REPOSITORY, --repository REPOSITORY The repository (package index) to upload the package to. Should be a section in the config file (default: pypi). (Can also be set via TWINE_REPOSITORY environment variable.) --repository-url REPOSITORY_URL The repository (package index) URL to upload the package to. This overrides --repository. (Can also be set via TWINE_REPOSITORY_URL environment variable.) -s, --sign Sign files to upload using GPG. --sign-with SIGN_WITH GPG program used to sign uploads (default: gpg). -i IDENTITY, --identity IDENTITY GPG identity used to sign files. -u USERNAME, --username USERNAME The username to authenticate to the repository (package index) as. (Can also be set via TWINE_USERNAME environment variable.) -p PASSWORD, --password PASSWORD The password to authenticate to the repository (package index) with. (Can also be set via TWINE_PASSWORD environment variable.) --non-interactive Do not interactively prompt for username/password if the required credentials are missing. (Can also be set via TWINE_NON_INTERACTIVE environment variable.) -c COMMENT, --comment COMMENT The comment to include with the distribution file. --config-file CONFIG_FILE The .pypirc config file to use. --skip-existing Continue uploading files if one already exists. (Only valid when uploading to PyPI. Other implementations may not support this.) --cert path Path to alternate CA bundle (can also be set via TWINE_CERT environment variable). --client-cert path Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. --verbose Show verbose output. --disable-progress-bar Disable the progress bar. ``twine check`` ^^^^^^^^^^^^^^^ Checks whether your distribution's 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**: 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: .. code-block:: console $ twine register -h usage: twine register [-h] -r REPOSITORY [--repository-url REPOSITORY_URL] [-u USERNAME] [-p PASSWORD] [-c COMMENT] [--config-file CONFIG_FILE] [--cert path] [--client-cert path] package positional arguments: package File from which we read the package metadata. optional arguments: -h, --help show this help message and exit -r REPOSITORY, --repository REPOSITORY The repository (package index) to register the package to. Should be a section in the config file. (Can also be set via TWINE_REPOSITORY environment variable.) Initial package registration no longer necessary on pypi.org: https://packaging.python.org/guides/migrating-to-pypi- org/ --repository-url REPOSITORY_URL The repository (package index) URL to register the package to. This overrides --repository. (Can also be set via TWINE_REPOSITORY_URL environment variable.) -u USERNAME, --username USERNAME The username to authenticate to the repository (package index) as. (Can also be set via TWINE_USERNAME environment variable.) -p PASSWORD, --password PASSWORD The password to authenticate to the repository (package index) with. (Can also be set via TWINE_PASSWORD environment variable.) --non-interactive Do not interactively prompt for username/password if the required credentials are missing. (Can also be set via TWINE_NON_INTERACTIVE environment variable.) -c COMMENT, --comment COMMENT The comment to include with the distribution file. --config-file CONFIG_FILE The .pypirc config file to use. --cert path Path to alternate CA bundle (can also be set via TWINE_CERT environment variable). --client-cert path Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. Environment Variables ^^^^^^^^^^^^^^^^^^^^^ Twine also supports configuration via environment variables. Options passed on the command line will take precedence over options set via environment variables. Definition via environment variable is helpful in environments where it is not convenient to create a `.pypirc` file, such as a CI/build server, for example. * ``TWINE_USERNAME`` - the username to use for authentication to the repository. * ``TWINE_PASSWORD`` - the password to use for authentication to the repository. * ``TWINE_REPOSITORY`` - the repository configuration, either defined as a section in `.pypirc` or provided as a full URL. * ``TWINE_REPOSITORY_URL`` - the repository URL to use. * ``TWINE_CERT`` - custom CA certificate to use for repositories with self-signed or untrusted certificates. * ``TWINE_NON_INTERACTIVE`` - Do not interactively prompt for username/password if the required credentials are missing. Resources --------- * `IRC `_ (``#pypa`` - irc.freenode.net) * `GitHub repository `_ * User and developer `documentation`_ * `Python Packaging User Guide`_ Contributing ------------ See our `developer documentation`_ for how to get started, an architectural overview, and our future development plans. Code of Conduct --------------- Everyone interacting in the ``twine`` project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. .. _`a utility`: https://pypi.org/project/twine/ .. _`publishing`: https://packaging.python.org/tutorials/distributing-packages/ .. _`PyPI`: https://pypi.org .. _`Test PyPI`: https://packaging.python.org/guides/using-testpypi/ .. _`Python Packaging User Guide`: https://packaging.python.org/tutorials/distributing-packages/ .. _`documentation`: https://twine.readthedocs.io/ .. _`developer documentation`: https://twine.readthedocs.io/en/latest/contributing.html .. _`projects`: https://packaging.python.org/glossary/#term-project .. _`distributions`: https://packaging.python.org/glossary/#term-distribution-package .. _`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 Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Natural Language :: English Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: BSD Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/README.rst0000664000372000037200000003426600000000000015207 0ustar00travistravis00000000000000.. image:: https://img.shields.io/pypi/v/twine.svg :target: https://pypi.org/project/twine .. 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 .. image:: https://img.shields.io/travis/com/pypa/twine :target: https://travis-ci.org/pypa/twine .. image:: https://img.shields.io/codecov/c/github/pypa/twine :target: https://codecov.io/gh/pypa/twine twine ===== .. rtd-inclusion-marker-do-not-remove Twine is `a utility`_ for `publishing`_ Python packages on `PyPI`_. It provides build system independent uploads of source and binary `distribution artifacts `_ for both new and existing `projects`_. Why Should I Use This? ---------------------- The goal of ``twine`` is to improve PyPI interaction by improving security and testability. The biggest reason to use ``twine`` is that it securely authenticates you to `PyPI`_ over HTTPS using a verified connection, regardless of the underlying Python version. Meanwhile, ``python setup.py upload`` will only work correctly and securely if your build system, Python version, and underlying operating system are configured properly. Secondly, ``twine`` encourages you to build your distribution files. ``python setup.py upload`` only allows you to upload a package as a final step after building with ``distutils`` or ``setuptools``, within the same command invocation. This means that you cannot test the exact file you're going to upload to PyPI to ensure that it works before uploading it. Finally, ``twine`` allows you to pre-sign your files and pass the ``.asc`` files into the command line invocation (``twine upload myproject-1.0.1.tar.gz myproject-1.0.1.tar.gz.asc``). This enables you to be assured that you're typing your ``gpg`` passphrase into ``gpg`` itself and not anything else, since *you* will be the one directly executing ``gpg --detach-sign -a ``. Features -------- - Verified HTTPS connections - Uploading doesn't require executing ``setup.py`` - Uploading files that have already been created, allowing testing of distributions before release - Supports uploading any packaging format (including `wheels`_) Installation ------------ .. code-block:: console $ pip install twine Using Twine ----------- 1. Create some distributions in the normal way: .. code-block:: console $ python setup.py sdist bdist_wheel 2. Upload with ``twine`` to `Test PyPI`_ and verify things look right. Twine will automatically prompt for your username and password: .. code-block:: console $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* username: ... password: ... 3. Upload to `PyPI`_: .. code-block:: console $ twine upload dist/* 4. Done! More documentation on using ``twine`` to upload packages to PyPI is in the `Python Packaging User Guide`_. Keyring Support --------------- Instead of typing in your password every time you upload a distribution, Twine 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 `_. 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. For example, to set a username and password for PyPI: .. code-block:: console $ keyring set https://upload.pypi.org/legacy/ your-username # 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 appropriate password from the keyring. .. Note:: If you are using Linux in a headless environment (such as on a server) you'll need to do some additional steps to ensure that Keyring can store secrets securely. See `Using Keyring on headless systems`_. .. _`keyring`: https://pypi.org/project/keyring/ .. _`Using Keyring on headless systems`: https://keyring.readthedocs.io/en/latest/#using-keyring-on-headless-linux-systems Disabling Keyring ^^^^^^^^^^^^^^^^^ 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 discussion and background. Options ------- ``twine upload`` ^^^^^^^^^^^^^^^^ Uploads one or more distributions to a repository. .. code-block:: console $ twine upload -h usage: twine upload [-h] [-r REPOSITORY] [--repository-url REPOSITORY_URL] [-s] [--sign-with SIGN_WITH] [-i IDENTITY] [-u USERNAME] [-p PASSWORD] [-c COMMENT] [--config-file CONFIG_FILE] [--skip-existing] [--cert path] [--client-cert path] [--verbose] [--disable-progress-bar] dist [dist ...] positional arguments: dist The distribution files to upload to the repository (package index). Usually dist/* . May additionally contain a .asc file to include an existing signature with the file upload. optional arguments: -h, --help show this help message and exit -r REPOSITORY, --repository REPOSITORY The repository (package index) to upload the package to. Should be a section in the config file (default: pypi). (Can also be set via TWINE_REPOSITORY environment variable.) --repository-url REPOSITORY_URL The repository (package index) URL to upload the package to. This overrides --repository. (Can also be set via TWINE_REPOSITORY_URL environment variable.) -s, --sign Sign files to upload using GPG. --sign-with SIGN_WITH GPG program used to sign uploads (default: gpg). -i IDENTITY, --identity IDENTITY GPG identity used to sign files. -u USERNAME, --username USERNAME The username to authenticate to the repository (package index) as. (Can also be set via TWINE_USERNAME environment variable.) -p PASSWORD, --password PASSWORD The password to authenticate to the repository (package index) with. (Can also be set via TWINE_PASSWORD environment variable.) --non-interactive Do not interactively prompt for username/password if the required credentials are missing. (Can also be set via TWINE_NON_INTERACTIVE environment variable.) -c COMMENT, --comment COMMENT The comment to include with the distribution file. --config-file CONFIG_FILE The .pypirc config file to use. --skip-existing Continue uploading files if one already exists. (Only valid when uploading to PyPI. Other implementations may not support this.) --cert path Path to alternate CA bundle (can also be set via TWINE_CERT environment variable). --client-cert path Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. --verbose Show verbose output. --disable-progress-bar Disable the progress bar. ``twine check`` ^^^^^^^^^^^^^^^ Checks whether your distribution's 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**: 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: .. code-block:: console $ twine register -h usage: twine register [-h] -r REPOSITORY [--repository-url REPOSITORY_URL] [-u USERNAME] [-p PASSWORD] [-c COMMENT] [--config-file CONFIG_FILE] [--cert path] [--client-cert path] package positional arguments: package File from which we read the package metadata. optional arguments: -h, --help show this help message and exit -r REPOSITORY, --repository REPOSITORY The repository (package index) to register the package to. Should be a section in the config file. (Can also be set via TWINE_REPOSITORY environment variable.) Initial package registration no longer necessary on pypi.org: https://packaging.python.org/guides/migrating-to-pypi- org/ --repository-url REPOSITORY_URL The repository (package index) URL to register the package to. This overrides --repository. (Can also be set via TWINE_REPOSITORY_URL environment variable.) -u USERNAME, --username USERNAME The username to authenticate to the repository (package index) as. (Can also be set via TWINE_USERNAME environment variable.) -p PASSWORD, --password PASSWORD The password to authenticate to the repository (package index) with. (Can also be set via TWINE_PASSWORD environment variable.) --non-interactive Do not interactively prompt for username/password if the required credentials are missing. (Can also be set via TWINE_NON_INTERACTIVE environment variable.) -c COMMENT, --comment COMMENT The comment to include with the distribution file. --config-file CONFIG_FILE The .pypirc config file to use. --cert path Path to alternate CA bundle (can also be set via TWINE_CERT environment variable). --client-cert path Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. Environment Variables ^^^^^^^^^^^^^^^^^^^^^ Twine also supports configuration via environment variables. Options passed on the command line will take precedence over options set via environment variables. Definition via environment variable is helpful in environments where it is not convenient to create a `.pypirc` file, such as a CI/build server, for example. * ``TWINE_USERNAME`` - the username to use for authentication to the repository. * ``TWINE_PASSWORD`` - the password to use for authentication to the repository. * ``TWINE_REPOSITORY`` - the repository configuration, either defined as a section in `.pypirc` or provided as a full URL. * ``TWINE_REPOSITORY_URL`` - the repository URL to use. * ``TWINE_CERT`` - custom CA certificate to use for repositories with self-signed or untrusted certificates. * ``TWINE_NON_INTERACTIVE`` - Do not interactively prompt for username/password if the required credentials are missing. Resources --------- * `IRC `_ (``#pypa`` - irc.freenode.net) * `GitHub repository `_ * User and developer `documentation`_ * `Python Packaging User Guide`_ Contributing ------------ See our `developer documentation`_ for how to get started, an architectural overview, and our future development plans. Code of Conduct --------------- Everyone interacting in the ``twine`` project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. .. _`a utility`: https://pypi.org/project/twine/ .. _`publishing`: https://packaging.python.org/tutorials/distributing-packages/ .. _`PyPI`: https://pypi.org .. _`Test PyPI`: https://packaging.python.org/guides/using-testpypi/ .. _`Python Packaging User Guide`: https://packaging.python.org/tutorials/distributing-packages/ .. _`documentation`: https://twine.readthedocs.io/ .. _`developer documentation`: https://twine.readthedocs.io/en/latest/contributing.html .. _`projects`: https://packaging.python.org/glossary/#term-project .. _`distributions`: https://packaging.python.org/glossary/#term-distribution-package .. _`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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8731787 twine-3.1.1/docs/0000775000372000037200000000000000000000000014435 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/Makefile0000664000372000037200000001267000000000000016103 0ustar00travistravis00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/twine.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/twine.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/twine" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/twine" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8731787 twine-3.1.1/docs/_static/0000775000372000037200000000000000000000000016063 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/_static/.empty0000664000372000037200000000005400000000000017221 0ustar00travistravis00000000000000Empty file to force the creation of _static ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/changelog.rst0000664000372000037200000002565100000000000017127 0ustar00travistravis00000000000000:orphan: ========= Changelog ========= * :release:`3.1.1 <2019-11-27>` * :bug:`548` Restore ``--non-interactive`` as a flag not expecting an argument. * :release:`3.1.0 <2019-11-23>` * :feature:`547` Add support for specifying ``--non-interactive`` as an environment variable. * :release:`3.0.0 <2019-11-18>` * :feature:`336` When a client certificate is indicated, all password processing is disabled. * :feature:`489` Add ``--non-interactive`` flag to abort upload rather than interactively prompt if credentials are missing. * :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 major` More robust handling of server response in ``--skip-existing`` * :release:`2.0.0 <2019-09-24>` * :feature:`437` Twine now requires Python 3.6 or later. Use pip 9 or pin to "twine<2" to install twine on older Python versions. * :bug:`491 major` Require requests 2.20 or later to avoid reported security vulnerabilities in earlier releases. * :release:`1.15.0 <2019-09-17>` * :feature:`488` Improved output on ``check`` command: Prints a message when there are no distributions given to check. Improved handling of errors in a distribution's markup, avoiding messages flowing through to the next distribution's errors. * :release:`1.14.0 <2019-09-06>` * :feature:`456` Better error handling and gpg2 fallback if gpg not available. * :bug:`341 major` Fail more gracefully when encountering bad metadata * :feature:`459` Show Warehouse URL after uploading a package * :feature:`310` Now provide a more meaningful error on redirect during upload. * :release:`1.13.0 <2019-02-13>` * :bug:`452 major` Restore prompts while retaining support for suppressing prompts. * :bug:`447 major` Avoid requests-toolbelt to 0.9.0 to prevent attempting to use openssl when it isn't available. * :feature:`427` Add disable_progress_bar option to disable tqdm. * :feature:`426` Allow defining an empty username and password in .pypirc. * :bug:`441 major` Only install pyblake2 if needed. * :bug:`444 major` Use io.StringIO instead of StringIO. * :bug:`436 major` Use modern Python language features. * :support:`439` Refactor tox env and travis config. * :bug:`435 major` Specify python_requires in setup.py * :bug:`432 major` Use https URLs everywhere. * :bug:`428 major` Fix --skip-existing for Nexus Repos. * :feature:`419` Support keyring.get_credential. * :feature:`418` Support keyring.get_username_and_password. * :bug:`421 major` Remove unnecessary usage of readme_render.markdown. * :feature:`416` Add Python 3.7 to classifiers. * :bug:`412 major` Don't crash if there's no package description. * :bug:`408 major` Fix keyring support. * :release:`1.12.1 <2018-09-24>` * :bug:`404` Fix regression with upload exit code * :release:`1.12.0 <2018-09-24>` * :feature:`395 major` Add ``twine check`` command to check long description * :feature:`392 major` Drop support for Python 3.3 * :feature:`363` Empower ``--skip-existing`` for Artifactory repositories * :bug:`367 major` Avoid MD5 when Python is compiled in FIPS mode * :release:`1.11.0 <2018-03-19>` * :bug:`269 major` Avoid uploading to PyPI when given alternate repository URL, and require ``http://`` or ``https://`` in ``repository_url``. * :support:`277` Add instructions on how to use keyring. * :support:`314` Add new maintainer, release checklists. * :bug:`322 major` Raise exception if attempting upload to deprecated legacy PyPI URLs. * :feature:`320` Remove PyPI as default ``register`` package index. * :feature:`319` Support Metadata 2.1 (:pep:`566`), including Markdown for ``description`` fields. * :support:`318` `Update PyPI URLs `_. * :release:`1.10.0 <2018-03-07>` * :bug:`315 major` Degrade gracefully when keyring is unavailable * :feature:`304` Reorganize & improve user & developer documentation. * :feature:`46` Link to changelog from ``README`` * :feature:`295` Add doc building instructions * :feature:`296` Add architecture overview to docs * :feature:`303` Revise docs predicting future of ``twine`` * :bug:`298 major` Fix syntax highlighting in ``README`` * :bug:`299 major` Fix changelog formatting * :bug:`200 major` Remove obsolete registration guidance * :bug:`286 major` Fix Travis CI and test configuration * :feature:`257` Declare support for Python 3.6 * :bug:`297 major` Fix Read the Docs, tox, Travis configuration * :bug:`268 major` Print progress to ``stdout``, not ``stderr`` * :bug:`265 major` Fix ``--repository[-url]`` help text * :feature:`256` Improve progressbar * :release:`1.9.1 <2017-05-27>` * :bug:`-` Blacklist known bad versions of Requests. See also :bug:`253` * :release:`1.9.0 <2017-05-22>` * :support:`-` Twine will now resolve passwords using the `keyring `_ if available. Module can be required with the ``keyring`` extra. * :support:`-` Twine will use ``hashlib.blake2b`` on Python 3.6+ instead of using pyblake2 for Blake2 hashes 256 bit hashes. * :support:`-` Twine sends less information about the user's system in the User-Agent string. See also :bug:`229` * :support:`-` Fix ``--skip-existing`` when used to upload a package for the first time. See also :bug:`220` * :support:`-` Fix precedence of ``--repository-url`` over ``--repository``. See also :bug:`206` * :release:`1.8.1 <2016-08-09>` * :support:`-` Check if a package exists if the URL is one of: * ``https://pypi.python.org/pypi/`` * ``https://upload.pypi.org/`` * ``https://upload.pypi.io/`` This helps people with ``https://upload.pypi.io`` still in their :file:`.pypirc` file. * :release:`1.8.0 <2016-08-08>` * :feature:`201` Switch from upload.pypi.io to upload.pypi.org. * :feature:`144` Retrieve configuration from the environment as a default. * Repository URL will default to ``TWINE_REPOSITORY`` * Username will default to ``TWINE_USERNAME`` * Password will default to ``TWINE_PASSWORD`` * :feature:`166` Allow the Repository URL to be provided on the command-line (``--repository-url``) or via an environment variable (``TWINE_REPOSITORY_URL``). * :support:`-` Generate SHA256 digest for all packages by default. * :feature:`171` Generate Blake2b 256 digests for packages *if* ``pyblake2`` is installed. Users can use ``python -m pip install twine[with-blake2]`` to have ``pyblake2`` installed with Twine. * :support:`-` Stop testing on Python 2.6. 2.6 support will be "best effort" until 2.0.0 * :support:`-` Warn users if they receive a 500 error when uploading to ``*pypi.python.org`` * :release:`1.7.4 <2016-07-09>` * :bug:`-` Correct a packaging error. * :release:`1.7.3 <2016-07-08>` * :bug:`195` Fix uploads to instances of pypiserver using ``--skip-existing``. We were not properly checking the return status code on the response after attempting an upload. * :support:`-` Do not generate traffic to Legacy PyPI unless we're uploading to it or uploading to Warehouse (e.g., pypi.io). This avoids the attempt to upload a package to the index if we can find it on Legacy PyPI already. * :release:`1.7.2 <2016-07-05>` * :bug:`189`, :bug:`191` Fix issue where we were checking the existence of packages even if the user didn't specify ``--skip-existing``. * :release:`1.7.1 <2016-07-05>` * :bug:`187` Clint was not specified in the wheel metadata as a dependency. * :release:`1.7.0 <2016-07-04>` * :feature:`142` Support ``--cert`` and ``--client-cert`` command-line flags and config file options for feature parity with pip. This allows users to verify connections to servers other than PyPI (e.g., local package repositories) with different certificates. * :feature:`152` Add progress bar to uploads. * :feature:`162` Allow ``--skip-existing`` to work for 409 status codes. * :feature:`167` Implement retries when the CDN in front of PyPI gives us a 5xx error. * :feature:`177` Switch Twine to upload to pypi.io instead of pypi.python.org. * :bug:`186 major` Allow passwords to have ``%``\ s in them. * :release:`1.6.5 <2015-12-16>` * :bug:`155` Bump requests-toolbelt version to ensure we avoid ConnectionErrors * :release:`1.6.4 <2015-10-27>` * :bug:`145` Paths with hyphens in them break the Wheel regular expression. * :bug:`146` Exception while accessing the ``repository`` key (sic) when raising a redirect exception. * :release:`1.6.3 <2015-10-05>` * :bug:`137`, :bug:`140` Uploading signatures was broken due to the pull request that added large file support via ``requests-toolbelt``. This caused a 500 error on PyPI and prevented package and signature upload in twine 1.6.0 * :release:`1.6.2 <2015-09-28>` * :bug:`132` Upload signatures with packages appropriately As part of the refactor for the 1.6.0 release, we were using the wrong name to find the signature file. This also uncovered a bug where if you're using twine in a situation where ``*`` is not expanded by your shell, we might also miss uploading signatures to PyPI. Both were fixed as part of this. * :release:`1.6.1 <2015-09-18>` * :bug:`130` Fix signing support for uploads * :release:`1.6.0 <2015-09-14>` * :feature:`106` Upload wheels first to PyPI * :feature:`104` Large file support via the ``requests-toolbelt`` * :bug:`92 major` Raise an exception on redirects * :feature:`97` Allow the user to specify the location of their :file:`.pypirc` * :feature:`115` Add the ``--skip-existing`` flag to ``twine upload`` to allow users to skip releases that already exist on PyPI. * :bug:`114 major` Warnings triggered by pkginfo searching for ``PKG-INFO`` files should no longer be user visible. * :bug:`116 major` Work around problems with Windows when using ``getpass.getpass`` * :bug:`111 major` Provide more helpful messages if :file:`.pypirc` is out of date. * :feature:`8` Support registering new packages with ``twine register`` * :release:`1.5.0 <2015-03-10>` * :bug:`85 major` Display information about the version of setuptools installed * :bug:`61 major` Support deprecated pypirc file format * :feature:`29` Support commands not named "gpg" for signing * :support:`-` Add lower-limit to requests dependency * :release:`1.4.0 <2014-12-12>` * :bug:`28 major` Prevent ResourceWarning from being shown * :bug:`34 major` List registered commands in help text * :bug:`32 major` Use ``pkg_resources`` to load registered commands * :bug:`47 major` Fix issue uploading packages with ``_``\ s in the name * :bug:`26 major` Add support for uploading Windows installers * :bug:`65 major` Expand globs and check for existence of dists to upload * :feature:`13` Parse :file:`~/.pypirc` ourselves and use ``subprocess`` instead of the ``distutils.spawn`` module. * :feature:`6` Switch to a git style dispatching for the commands to enable simpler commands and programmatic invocation. * :release:`1.3.0 <2014-03-31>` * :feature:`-` Additional functionality. * :release:`1.2.2 <2013-10-03>` * :feature:`0` Basic functionality. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/conf.py0000664000372000037200000002067500000000000015746 0ustar00travistravis00000000000000# twine documentation build configuration file, created by # sphinx-quickstart on Tue Aug 13 11:51:54 2013. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # import sys # import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath(os.pardir)) import twine # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. 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. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.viewcode", "releases", ] # 'releases' (changelog) settings 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"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "twine" copyright = "2019, 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 # built documents. # # The short X.Y version. version = ".".join(twine.__version__.split(".")[:2]) # The full version, including alpha/beta/rc tags. release = twine.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "twinedoc" # -- Options for LaTeX output ------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [ ( "index", "twine.tex", "Twine Documentation", "Donald Stufft and individual contributors", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( "index", "twine", "twine Documentation", ["Donald Stufft", "Individual contributors"], 1, ), ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "twine", "twine Documentation", "Donald Stufft and individual contributors", "twine", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"https://docs.python.org/": None} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/contributing.rst0000664000372000037200000001435600000000000017707 0ustar00travistravis00000000000000Contributing ============ 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 ``twine`` and its dependencies do not interfere with other packages 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 a virtual environment that uses Python 3.6 or newer as the default Python. Example: .. code-block:: console mkvirtualenv -p /usr/bin/python3.7 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 make 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. After making docs changes, lint and build the docs locally, using ``tox``, before making a pull request. Activate your virtual environment, then, in the root directory, run: .. code-block:: console tox -e docs The HTML of the docs will be visible in :file:`twine/docs/_build/`. Testing ^^^^^^^ Tests with twine are run using `tox`_, and tested against Python versions 3.6, 3.7, and 3.8. 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 py36`` or ``tox -e py37``. Also, always run ``tox -e lint`` 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 ``tox -e docs``. 4. Ensure that your name is added to the end of the :file:`AUTHORS` file using the format ``Name (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 three purposes are to be: 1. A user-facing tool for publishing on pypi.org 2. A user-facing tool for publishing on other Python package indexes (e.g., ``devpi`` instances) 3. A useful API for other programs (e.g., ``zest.releaser``) to call for publishing on any Python package index Currently, twine has two principle functions: uploading new packages and registering new `projects`_ (``register`` is no longer supported on PyPI, and is in Twine for use with other package indexes). Its command line arguments are parsed in :file:`twine/cli.py`. 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. Where Twine gets configuration and credentials ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A user can set the repository URL, username, and/or password via command line, ``.pypirc`` files, environment variables, and ``keyring``. Adding a maintainer ------------------- A checklist for adding a new maintainer to the project. #. Add them as a Member in the GitHub repo settings. (This will also give them privileges on the `Travis CI project `_.) #. Get them Test PyPI and canon PyPI usernames and add them as a Maintainer on `our Test PyPI project `_ and `canon PyPI `_. Making a new release -------------------- A checklist for creating, testing, and distributing a new version. #. Choose a version number, e.g. "1.15.0" #. Update the changelog: #. Add missing changes to :file:`docs/changelog.rst`. #. Add a release line at the beginning referencing the release and the date of the release. #. Commit, push, ensure Travis build passes. #. Create a new git tag with ``git tag -m tag {number}``. #. Push the new tag: ``git push upstream {number}``. #. Watch the release `in Travis `_. #. Send announcement email to `pypa-dev mailing list`_ and celebrate. Future development ------------------ See our `open issues`_. In the future, ``pip`` and ``twine`` may merge into a single tool; see `ongoing discussion `_. .. _`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.org/project/tox-pipenv .. _`plugin`: https://github.com/bitprophet/releases .. _`projects`: https://packaging.python.org/glossary/#term-project .. _`open issues`: https://github.com/pypa/twine/issues ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/index.rst0000664000372000037200000000124300000000000016276 0ustar00travistravis00000000000000.. 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. Welcome to twine's documentation! ================================= .. 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 contributing changelog Python Packaging User Guide * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/make.bat0000664000372000037200000001174600000000000016053 0ustar00travistravis00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\twine.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\twine.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/docs/requirements.txt0000664000372000037200000000020400000000000017715 0ustar00travistravis00000000000000doc8>=0.8.0 readme-renderer>=17.4 releases>=1.4.0 Sphinx>=1.7.0 sphinx_rtd_theme>=0.2.4 # workaround for #492 semantic-version<2.7 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/mypy.ini0000664000372000037200000000075400000000000015212 0ustar00travistravis00000000000000[mypy] ; TODO: check_untyped_defs = True ; TODO: Make this more granular; docs recommend it as a "last resort" ; https://mypy.readthedocs.io/en/latest/existing_code.html#start-small ignore_missing_imports = True ; NOTE: Docs recommend `normal` or `silent` for an existing codebase ; https://mypy.readthedocs.io/en/latest/running_mypy.html#following-imports ; follow_imports = skip show_traceback = True html_report = mypy linecount_report = mypy lineprecision_report = mypy txt_report = mypy ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/pyproject.toml0000664000372000037200000000020300000000000016414 0ustar00travistravis00000000000000[build-system] requires = ["setuptools>=40.8", "wheel", "setuptools_scm>=1.15"] build-backend = "setuptools.build_meta:__legacy__" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/pytest.ini0000664000372000037200000000040700000000000015537 0ustar00travistravis00000000000000[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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8851788 twine-3.1.1/setup.cfg0000664000372000037200000000325200000000000015330 0ustar00travistravis00000000000000[metadata] license_file = LICENSE name = twine author = Donald Stufft and individual contributors author_email = donald@stufft.io description = Collection of utilities for publishing packages on PyPI long_description = file:README.rst url = https://twine.readthedocs.io/ project_urls = Packaging tutorial = https://packaging.python.org/tutorials/distributing-packages/ Travis CI = https://travis-ci.org/pypa/twine/ Twine documentation = https://twine.readthedocs.io/en/latest/ Twine source = https://github.com/pypa/twine/ classifiers = Intended Audience :: Developers License :: OSI Approved :: Apache Software License Natural Language :: English Operating System :: MacOS :: MacOS X Operating System :: POSIX Operating System :: POSIX :: BSD Operating System :: POSIX :: Linux Operating System :: Microsoft :: Windows Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: Implementation :: CPython [options] packages = twine twine.commands python_requires = >=3.6 install_requires = pkginfo >= 1.4.2 readme_renderer >= 21.0 requests >= 2.20 requests-toolbelt >= 0.8.0, != 0.9.0 setuptools >= 0.7.0 tqdm >= 4.14 importlib_metadata; python_version < "3.8" keyring >= 15.1 setup_requires = setuptools_scm >= 1.15 [options.entry_points] twine.registered_commands = check = twine.commands.check:main upload = twine.commands.upload:main register = twine.commands.register:main console_scripts = twine = twine.__main__:main [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/setup.py0000664000372000037200000000123400000000000015217 0ustar00travistravis00000000000000# 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. # You may obtain a copy of the License at # # https://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 setuptools import setup setup( use_scm_version=True, ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8771787 twine-3.1.1/tests/0000775000372000037200000000000000000000000014647 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8771787 twine-3.1.1/tests/alt-fixtures/0000775000372000037200000000000000000000000017276 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/alt-fixtures/twine-1.5.0-py2.py3-none-any.whl0000664000372000037200000003671000000000000024552 0ustar00travistravis00000000000000PKrjFR`{twine/__init__.py}TMo0 W<ˆ3V$EPl! K>ߏǐbrI(=>G9C{J rEgQb^Uh֝(kI'KZuz W'fV o)BM9 n;IB=Xf&Iy'-qT@LuWS1 MiVr]XT<y@tV(Eڀh Akoȑjv0,YgݫF-)[%<<xw{xZ?<6lvۛl}~dۛewg~IX~刯zd;,}Ƌ/hہMK6 rE+Q+[ʷ:P5]q:"ClG1{?;r0%WqبU9kIW!xSr ώ[ԫ3V R&t_ixNvO{x^>>.dV:'T_a}vʚqOYUAXʿBz6_ C뿊P"ůZ{d8e(œPK0d$F>_> twine/cli.pyUQ6~ϯ,}Kt7ע&T!b6FDɩ- ό'Y/ R24ynF7S3hD aV>3O4m\^ !d;5Dk\@*L{nm!7Q4Vi֘zrC *.Hx~GdWQ֠+9K6İd{ X|FZ{ ͞)$k1gꙑ*bb~Q<&>VbcXixrA0[|y He4V+˟Hr[Blb3StaB!wɁUŵm&z↙r!*JV$ycIbKem,Igҭ$}-jŅ "?hOeI *VѨGVE͔~]+/MS)Oޢ6dfF s Z4 eE~dSOo۸Jm0=r+Au?#Z) I-=f<:uI瘷~>$-6W-}0ڰw t C mSh=;cN4ǒB&Q'mO^>v-/Q2==Jx(?T뚙tl솊]4ڡRX8SESQ^Z_+YL閐eY\ݝհvޥYOm@j7?}M] t+Bӯi(X]5&L$z:'~}yY61[rmP]cSnxGe.E@d+Vior oKG_{ NXɳ&x?A| "t+W^YJ'Q~[ FPKsqEg6Itwine/utils.pyWQo6~ׯ8(Wi7%fYQȔLT5coIɒvbKw dqP"x%\˜eX2ILpG\ +0[W'WZFG_  Kر@9B 8ǘD`ya/ֆ B|0fh_SҴo)>bh$Uz9#}qz5-&535(W)yV #ÌA*`f$q+aD@∲(q_P3̷iR~/b|..f<[N' |v=]N3|z0]L?#IA E .x,c^yZC*1( MQ2T$J`JJS*ZQ| {-3s J@P"ʄe:Q̶z8RrS0]?u2y"҂)Uoj&JI58C,ݱ!, 8"rl's^b+A_D3:c@j8O5D`wqB8=*# p.}b)4CBja:c7A g;3Rx[>w +統2jJԬ^a)6M7]ݨ y1J-D1*[WۖtFd:-n|3P֮58* #DO,? w:I|#xc-23d>W $#*FJ uR_ ڂbة*;\z N{ԽB<Mn' l$kͷim쨙_PyYA;f -J&wS+ϝ?> NLanwe'<˧AgB*BI´Px?UN8;UD++^/΄58-^Fb!Vl%0\@;*NhfEY]6Ttv 5VrN{.޹Aq6qL;1r?wNv|{jMp}˭ǭrjV[PKuE; twine/wheel.pyVr6}W`fD2onTܱdR@BMZR{wAR"Ջٳ,TzgDr٥UyG#F -dVƚ՜{0V(',$9D?!NUlwL** H!,E )hDŽdZRpn)>6j895.Z9}~zlbe o/'z;YJ sc\/Qa7L xi,F̪mdɄuF,+KT 0U\xΦ{1O#0]z`b:kjv9]LfzƳrӄn` GRkZՐ\,*^+=0 f-,Ң YJ;sTFY䕫 $ W18z=ю6B:4)~"U$p`xi9P9o~}Z)ٝ ?uɖlArS=1FU"ԑARB}5a4{Ң/h[%`KW-8q展&K/'x'gv}49u\it_t68tOfP&:6Muf~| HG -iSFyևJO~KȂ~ɗ%w~oVߟ|OwiT|B%{"  fR$ -)fMz]̔|/q D?,|73 PKK?EN/UXtwine/wininst.pymTn0+akд=)iN)PieHb)_^l`;;Y27$IW2$DZGUE +a+dp ) 0z:~ + x|d-B i &' bTԄn藇Oyy}07 jxAa<_9$|E|5KXϸ {qn$<O P;SqG6JqH\7@K}¬"'걒tOg1A#,'<:/IeP尒팪4 [S{5e=ætKevayFZ*7C[ 7.,G^Teht}5dz=6.0=NI[߷8+l[ SȗNjNjm5'bXs? Z,a"9jݲ$4sHW=ԺfVS@(&Bvg>#?[ vYc'[?\ ˦7X]SZ+ROHt$s7v{ze&Fzz,scQh엏݅<pN5kpLBh;0;1H}5ixPKuE}KI:twine/commands/__init__.pyuAo0 Drvr3V8@ɐe!HDͿd@a'rh80|kg<[@m0 < 0Drn4 q8GuRD=|ȂvGoHY0g&X\-\JJ^/8 0,EjS՛/{?Z1B߉lOh Q#j(;vDKG?LHU¼pWes}sվ԰j[}dz_e&9o>LISM}Ոzw5%JCGb7g}pGh>q 4}Te$+M@'$Kub PKK$F" A!twine/commands/upload.pyko8m8iM:^-2/#8n;ÇHImz [s8{#r^7{"'/_D.ꊖ9YvVg,'m3AԆfݙߙIĈ٭h7[{RՊ .ɚ5d)92Fv\m4K$-z(`So`5ojN_v Ղ&(^Iuq~y<a5ǪdRr\ m@@’H--=U;Z`@%R jUOQN2oΖdb9|:zq$>wWŻ+X!gW/a&`Urג$5^U҂g됆-hH @[ҐѥZ[V)"t% ~ϑ4W T}@[YZr-d(B2.z~oܔ[2iڸlW3PT]%ZV|']W[eRZ) ɟSqR4wuLu+@TDdR%t, Ҭ|CnY5T]-nXEoCHjdrX^ן߃gW}hjѩ5bgvYQUCwf5NRK Lݦ"m(*՗4ŗ1 oB H;]8M۔5%x)fFlp񏯝c}45:R y@#4Ѥ-k0nsm` pŐ[V7}=h +@VUV`{R,hgG9)N;yosJ1*8Ks}N[Ź+S6"i 2YyPS(AEp~~n,9L7%F"T j]E࠽_Z Å\_>7Nb B |@_jSWb*ۡ_(lJ~ܼ:$:RQ=_^9Koi)fv؇9=4an2`x`lH%;]lxCÍa^pú/iO4SrvNn #48m7ZA@N @g((77#mw}wXUy1ѩǫc?l'Ռ^Çݱ׾'` 'u{;IkZy!HdǩNu %{Ǯꊅ;)n +tw |"#2 7I(l˒n !f ѩb 6JMGAtCW3obwɣitkj .2yj1׽/6 C6n{nD9GMAOet >/Bqv9@[` i P8,:-e uT=Q>!|p̾T9l Lf7*Pl ߱=F0zh*LͭxY 9ϡ1R6:I 9<J3e~8}/hXCsZ_wx`$ovJNF"QO(V#-qjLzDŽC~D+GcM3;:f6zVž*M4}_ac$qvh1i?3dƿV&? huwn4ǎHa%Z3 ~\46lIa(~z+cgo=,?n2o";"q@hǭ59t2#3~gS&`fƢ`yhdggnF DGK|P?I͝{Xi7Ϻ_Gu[&Zqx ?5V… QgwL \a*;7u]&=Aa|8mYO)( -cڄ9O~H`!9E%މ!Hi0'h9KF~X;, жTs= +y/j:2-Sʩ%$ <vp# YDabփq'ɴLyo߿%h)AƐ,.nսme:Vat/)cgT{.aA̟3$a:CFm|IKy뇠cyb?ba˴ď\^K8V;37a6:}7:e?X 3}l爜y^} ^e %PMkjc٧K0R I$MFF~;%5, I_DkCǐpPKۛjFїr%twine-1.5.0.dist-info/DESCRIPTION.rstWo6] $%>݂4m,a;+hȢFRqadIT@ݓ[JF/ eIPTܚڐ4"u)Y)Wx=>s=>8׵+tkmL>Qt#+WI9mS8KM]jLYg~ue)z_iZ蜮Y *ϥud">e 8+H!W 0 '-u[8HHCog&8FoHʵw&|ʂi=(*mתBFC5*R<}:iJuٕ}Nrb0v,$m񑕘Ñ!dm Z~P^>ں0 rV dPu=أ*Kvęi)ЇE)38e҉=g\^& 7 bѦq菎OctPZ wq3tp"B,CV2r?zv3l % qz-r[%òWyeBl78s29:>>ExZ8:5pen>44Wg9Wq`%oվes4O>(:[$#V϶iLoep}QWxևQ&gr'ٻ/5t5=;8w}4>N_N^N//.`[*F YC+XBwA3]nm8MY[Ϗ&nxlC ->w@5ņ"0;`} s)vknQCSfW綯BG}v3NKMXMS wi/$Wrb qWҾ)DDp/)neo{̇?G#ؒd ,n:,}@x'Wihm}R$Iޱr)T[U񾤝ƔI$"1| qc 44np2/Ch-:blHyWugYnѕ_= ፴}ytƼȯpc$ߍf 2gU?PKۛjF s0Qp&twine-1.5.0.dist-info/entry_points.txtN+I/N.,()*)KUUz񹉙yV +"ZY\ZXZ ЃC PKۛjFx6ц8#twine-1.5.0.dist-info/metadata.jsonTk0WttiA),5a`ZgG,i4ﻓ5et>ٺ{]d C6f٥ M/4l2͛  .xźR֠`ج{qӗi(eLbJ U1¤FoS⻨ t]z- @U6>cd9I#^]mC=|D?k+%~naȻN@.(3?".B~<bK ak7 ˄)#VIdt҆Nzqi:<·*^3*#*sIr($J!VUȥ9>IʱE1k)"Wvp.qlO&Z70QyQ]cd2po m@{jDp G¢U!dJѧlj]F!Ac65(c :QbKF 0Up떇Dֵ~E陥E7bŖJl)e;I?g17R'+$?Ԏ7 ~AM_~³w~ic+kqo:lپm_PKۛjF#twine-1.5.0.dist-info/top_level.txt+)KPKۛjF3o_ntwine-1.5.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RHJ,./Q0323 /, (-JLR()*M ILR(4KM̫#DPKۛjFKItwine-1.5.0.dist-info/METADATAXr۸SvgLzmgw;j]vMc[#M;L1IhY$Eى*/$/x),O߄6RU#v\R]JG5ezTQSKXYH+aK/d=T)šg`[[a"JTyXk~E66WzUŋl\ZƫSy'ӆ,QrXMKbRGqtTG@i͓\9'P^] n\J]0JENT*lŝ(T &[{[z6Z;zfjiW\l_qhXWY i}Ƴ}`*H-Lx.1Gay^fZ\g5FN׻6߼a{^ 񶴭luWPd%DJg(aYf2U4u5 SbBcW٠Q)_Y1Aa߼; BfLJX:YesnI#F Q@LE _H\F|>Vț)\ű7mS֬8f101r\xQ%Z$c (MfPJcqt||AA)xUk+K&3az^udǕ)ٸ)0\.E1wRջ7uqPtt0v+|8`hR@ `z@@`ϖNm椙s6^݉FJ1Cީ%]eݎ%e āG&8wtL9y`?"<k>JЗ;B##̉Iud hpߛcE?DG:%->pBAXn 'Q`Y9m\s T(֤5X:cq@\' Bax /1M "OQ'Lh!abKrXk…RmSQy'h@Jn GHwg}~cT(%Lg]}nmq(t ;DڈM+؊GH{ر~uqԢm3\bx fn;s]{_;0 s7o_lG|5fDK>]BƄF3 .갏 v#kx+>JtEJ8T0hɠdxz+HzFA*a Z[a<`r ~OOB&uEOKY m ⋱VAyϠU,,waϼ6K~$d ͫ$G\W㐭]n~.Ƹ 34pOߔ9#uǮHK 7<7gq =е,tHg!p?^<{iE%0e4(+eZal6Od] VC: nOo? Վ"qLkT"e/](xq.2"_vaZo94pԟ;c49dF[~8cQTwRE߁ȍ?`m8f;pT00(ґp0.ƸY; 1qOSPKۛjF.Nrtwine-1.5.0.dist-info/RECORDuKϲ8$OOn@APD./i墔IFdM7MӒ!_fUF 5"Ve!$<_f}+v4bfL?x]%Q ËAJUa) QZ\Hp9;X`iex%qtQi"HᇍvJl[;HxڎՏR,_JG2~8IpaĜN$kMT(:i֝zR7`3\8jCq :iW4 ϩI"ydUK>$lGw]?Ԙw&/XO][OSX5iw@m*:F5Md{* ) J!(bثstS`L2g[ŢMɲ$k0%tV/EYXٽ%/ީz <#+ r~n3iXzy""l"파o_"_^{O02}6kq37T=Y=nv<^ɋ5HQ4O$4,ZJ`-bD,oIU =*6)9h ,?W.P(FX| +U(=? ̢,9IY' )(wFW8$ @oAWK{w~HFAxᄅ3ۭ[m/\PKrjFR`{twine/__init__.pyPKuEHOJtwine/__main__.pyPK0d$F>_> twine/cli.pyPKsqEg6Itwine/utils.pyPKuE; atwine/wheel.pyPKK?EN/UXCtwine/wininst.pyPKuE}KI: twine/commands/__init__.pyPKK$F" A!twine/commands/upload.pyPKۛjFїr%##twine-1.5.0.dist-info/DESCRIPTION.rstPKۛjF s0Qp&)twine-1.5.0.dist-info/entry_points.txtPKۛjFx6ц8#*twine-1.5.0.dist-info/metadata.jsonPKۛjF#T-twine-1.5.0.dist-info/top_level.txtPKۛjF3o_n-twine-1.5.0.dist-info/WHEELPKۛjFKI5.twine-1.5.0.dist-info/METADATAPKۛjF.Nr6twine-1.5.0.dist-info/RECORDPK$9././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/conftest.py0000664000372000037200000001157500000000000017057 0ustar00travistravis00000000000000import contextlib import textwrap import secrets import subprocess import pathlib import functools import getpass import sys import datetime import pytest import portend import requests import jaraco.envs import munch from twine import settings @pytest.fixture() def pypirc(tmpdir): return tmpdir / ".pypirc" @pytest.fixture() def make_settings(pypirc): """Returns a factory function for settings.Settings with defaults.""" default_pypirc = """ [pypi] username:foo password:bar """ def _settings(pypirc_text=default_pypirc, **settings_kwargs): pypirc.write(textwrap.dedent(pypirc_text)) settings_kwargs.setdefault('sign_with', None) settings_kwargs.setdefault('config_file', str(pypirc)) return settings.Settings(**settings_kwargs) return _settings class DevPiEnv(jaraco.envs.ToxEnv): """ Run devpi using tox:testenv:devpi. """ name = 'devpi' username = 'foober' def create(self, root, password): super().create() self.base = root self.password = password self.port = portend.find_available_local_port() cmd = [ self.exe('devpi-init'), '--serverdir', str(root), '--root-passwd', password, ] subprocess.run(cmd, check=True) @property def url(self): return f'http://localhost:{self.port}/' @property def repo(self): return f'{self.url}/{self.username}/dev/' def ready(self): with contextlib.suppress(Exception): return requests.get(self.url) def init(self): run = functools.partial(subprocess.run, check=True) client_dir = self.base / 'client' devpi_client = [ self.exe('devpi'), '--clientdir', str(client_dir), ] run(devpi_client + ['use', self.url + 'root/pypi/']) run(devpi_client + [ 'user', '--create', self.username, f'password={self.password}']) run(devpi_client + [ 'login', self.username, '--password', self.password]) run(devpi_client + ['index', '-c', 'dev']) @pytest.fixture(scope='session') def devpi_server(request, watcher_getter, tmp_path_factory): env = DevPiEnv() password = secrets.token_urlsafe() root = tmp_path_factory.mktemp('devpi') env.create(root, password) proc = watcher_getter( name=str(env.exe('devpi-server')), arguments=['--port', str(env.port), '--serverdir', str(root)], checker=env.ready, # Needed for the correct execution order of finalizers request=request, ) env.init() username = env.username url = env.repo return munch.Munch.fromDict(locals()) dist_names = [ 'twine-1.5.0.tar.gz', 'twine-1.5.0-py2.py3-none-any.whl', 'twine-1.6.5.tar.gz', 'twine-1.6.5-py2.py3-none-any.whl', ] @pytest.fixture(params=dist_names) def uploadable_dist(request): return pathlib.Path(__file__).parent / 'fixtures' / request.param @pytest.fixture def entered_password(monkeypatch): monkeypatch.setattr(getpass, 'getpass', lambda prompt: 'entered pw') @pytest.fixture def sampleproject_dist(tmp_path_factory): checkout = tmp_path_factory.mktemp('sampleproject', numbered=False) subprocess.run([ 'git', 'clone', 'https://github.com/pypa/sampleproject', str(checkout), ]) with (checkout / 'setup.py').open('r+') as setup: orig = setup.read() sub = orig.replace( "name='sampleproject'", "name='twine-sampleproject'") assert orig != sub setup.seek(0) setup.write(sub) tag = datetime.datetime.now().strftime('%Y%m%d%H%M%S%f') subprocess.run([ sys.executable, 'setup.py', 'egg_info', '--tag-build', f'post{tag}', 'sdist', ], cwd=str(checkout), ) dist, = checkout.joinpath('dist').glob('*') return dist class PypiserverEnv(jaraco.envs.ToxEnv): """ Run pypiserver using tox:testenv:pypiserver. """ name = 'pypiserver' @property @functools.lru_cache() def port(self): return portend.find_available_local_port() @property def url(self): return f'http://localhost:{self.port}/' def ready(self): with contextlib.suppress(Exception): return requests.get(self.url) @pytest.fixture(scope='session') def pypiserver_instance(request, watcher_getter, tmp_path_factory): env = PypiserverEnv() env.create() proc = watcher_getter( name=str(env.exe('pypi-server')), arguments=[ '--port', str(env.port), # allow anonymous uploads '-P', '.', '-a', '.', tmp_path_factory.mktemp('packages'), ], checker=env.ready, # Needed for the correct execution order of finalizers request=request, ) url = env.url return munch.Munch.fromDict(locals()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8771787 twine-3.1.1/tests/fixtures/0000775000372000037200000000000000000000000016520 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/fixtures/deprecated-pypirc0000664000372000037200000000011300000000000022042 0ustar00travistravis00000000000000[server-login] username:testusername password:testpassword [pypi] foo:bar ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/fixtures/twine-1.5.0-py2.py3-none-any.whl0000664000372000037200000003671000000000000023774 0ustar00travistravis00000000000000PKrjFR`{twine/__init__.py}TMo0 W<ˆ3V$EPl! K>ߏǐbrI(=>G9C{J rEgQb^Uh֝(kI'KZuz W'fV o)BM9 n;IB=Xf&Iy'-qT@LuWS1 MiVr]XT<y@tV(Eڀh Akoȑjv0,YgݫF-)[%<<xw{xZ?<6lvۛl}~dۛewg~IX~刯zd;,}Ƌ/hہMK6 rE+Q+[ʷ:P5]q:"ClG1{?;r0%WqبU9kIW!xSr ώ[ԫ3V R&t_ixNvO{x^>>.dV:'T_a}vʚqOYUAXʿBz6_ C뿊P"ůZ{d8e(œPK0d$F>_> twine/cli.pyUQ6~ϯ,}Kt7ע&T!b6FDɩ- ό'Y/ R24ynF7S3hD aV>3O4m\^ !d;5Dk\@*L{nm!7Q4Vi֘zrC *.Hx~GdWQ֠+9K6İd{ X|FZ{ ͞)$k1gꙑ*bb~Q<&>VbcXixrA0[|y He4V+˟Hr[Blb3StaB!wɁUŵm&z↙r!*JV$ycIbKem,Igҭ$}-jŅ "?hOeI *VѨGVE͔~]+/MS)Oޢ6dfF s Z4 eE~dSOo۸Jm0=r+Au?#Z) I-=f<:uI瘷~>$-6W-}0ڰw t C mSh=;cN4ǒB&Q'mO^>v-/Q2==Jx(?T뚙tl솊]4ڡRX8SESQ^Z_+YL閐eY\ݝհvޥYOm@j7?}M] t+Bӯi(X]5&L$z:'~}yY61[rmP]cSnxGe.E@d+Vior oKG_{ NXɳ&x?A| "t+W^YJ'Q~[ FPKsqEg6Itwine/utils.pyWQo6~ׯ8(Wi7%fYQȔLT5coIɒvbKw dqP"x%\˜eX2ILpG\ +0[W'WZFG_  Kر@9B 8ǘD`ya/ֆ B|0fh_SҴo)>bh$Uz9#}qz5-&535(W)yV #ÌA*`f$q+aD@∲(q_P3̷iR~/b|..f<[N' |v=]N3|z0]L?#IA E .x,c^yZC*1( MQ2T$J`JJS*ZQ| {-3s J@P"ʄe:Q̶z8RrS0]?u2y"҂)Uoj&JI58C,ݱ!, 8"rl's^b+A_D3:c@j8O5D`wqB8=*# p.}b)4CBja:c7A g;3Rx[>w +統2jJԬ^a)6M7]ݨ y1J-D1*[WۖtFd:-n|3P֮58* #DO,? w:I|#xc-23d>W $#*FJ uR_ ڂbة*;\z N{ԽB<Mn' l$kͷim쨙_PyYA;f -J&wS+ϝ?> NLanwe'<˧AgB*BI´Px?UN8;UD++^/΄58-^Fb!Vl%0\@;*NhfEY]6Ttv 5VrN{.޹Aq6qL;1r?wNv|{jMp}˭ǭrjV[PKuE; twine/wheel.pyVr6}W`fD2onTܱdR@BMZR{wAR"Ջٳ,TzgDr٥UyG#F -dVƚ՜{0V(',$9D?!NUlwL** H!,E )hDŽdZRpn)>6j895.Z9}~zlbe o/'z;YJ sc\/Qa7L xi,F̪mdɄuF,+KT 0U\xΦ{1O#0]z`b:kjv9]LfzƳrӄn` GRkZՐ\,*^+=0 f-,Ң YJ;sTFY䕫 $ W18z=ю6B:4)~"U$p`xi9P9o~}Z)ٝ ?uɖlArS=1FU"ԑARB}5a4{Ң/h[%`KW-8q展&K/'x'gv}49u\it_t68tOfP&:6Muf~| HG -iSFyևJO~KȂ~ɗ%w~oVߟ|OwiT|B%{"  fR$ -)fMz]̔|/q D?,|73 PKK?EN/UXtwine/wininst.pymTn0+akд=)iN)PieHb)_^l`;;Y27$IW2$DZGUE +a+dp ) 0z:~ + x|d-B i &' bTԄn藇Oyy}07 jxAa<_9$|E|5KXϸ {qn$<O P;SqG6JqH\7@K}¬"'걒tOg1A#,'<:/IeP尒팪4 [S{5e=ætKevayFZ*7C[ 7.,G^Teht}5dz=6.0=NI[߷8+l[ SȗNjNjm5'bXs? Z,a"9jݲ$4sHW=ԺfVS@(&Bvg>#?[ vYc'[?\ ˦7X]SZ+ROHt$s7v{ze&Fzz,scQh엏݅<pN5kpLBh;0;1H}5ixPKuE}KI:twine/commands/__init__.pyuAo0 Drvr3V8@ɐe!HDͿd@a'rh80|kg<[@m0 < 0Drn4 q8GuRD=|ȂvGoHY0g&X\-\JJ^/8 0,EjS՛/{?Z1B߉lOh Q#j(;vDKG?LHU¼pWes}sվ԰j[}dz_e&9o>LISM}Ոzw5%JCGb7g}pGh>q 4}Te$+M@'$Kub PKK$F" A!twine/commands/upload.pyko8m8iM:^-2/#8n;ÇHImz [s8{#r^7{"'/_D.ꊖ9YvVg,'m3AԆfݙߙIĈ٭h7[{RՊ .ɚ5d)92Fv\m4K$-z(`So`5ojN_v Ղ&(^Iuq~y<a5ǪdRr\ m@@’H--=U;Z`@%R jUOQN2oΖdb9|:zq$>wWŻ+X!gW/a&`Urג$5^U҂g됆-hH @[ҐѥZ[V)"t% ~ϑ4W T}@[YZr-d(B2.z~oܔ[2iڸlW3PT]%ZV|']W[eRZ) ɟSqR4wuLu+@TDdR%t, Ҭ|CnY5T]-nXEoCHjdrX^ן߃gW}hjѩ5bgvYQUCwf5NRK Lݦ"m(*՗4ŗ1 oB H;]8M۔5%x)fFlp񏯝c}45:R y@#4Ѥ-k0nsm` pŐ[V7}=h +@VUV`{R,hgG9)N;yosJ1*8Ks}N[Ź+S6"i 2YyPS(AEp~~n,9L7%F"T j]E࠽_Z Å\_>7Nb B |@_jSWb*ۡ_(lJ~ܼ:$:RQ=_^9Koi)fv؇9=4an2`x`lH%;]lxCÍa^pú/iO4SrvNn #48m7ZA@N @g((77#mw}wXUy1ѩǫc?l'Ռ^Çݱ׾'` 'u{;IkZy!HdǩNu %{Ǯꊅ;)n +tw |"#2 7I(l˒n !f ѩb 6JMGAtCW3obwɣitkj .2yj1׽/6 C6n{nD9GMAOet >/Bqv9@[` i P8,:-e uT=Q>!|p̾T9l Lf7*Pl ߱=F0zh*LͭxY 9ϡ1R6:I 9<J3e~8}/hXCsZ_wx`$ovJNF"QO(V#-qjLzDŽC~D+GcM3;:f6zVž*M4}_ac$qvh1i?3dƿV&? huwn4ǎHa%Z3 ~\46lIa(~z+cgo=,?n2o";"q@hǭ59t2#3~gS&`fƢ`yhdggnF DGK|P?I͝{Xi7Ϻ_Gu[&Zqx ?5V… QgwL \a*;7u]&=Aa|8mYO)( -cڄ9O~H`!9E%މ!Hi0'h9KF~X;, жTs= +y/j:2-Sʩ%$ <vp# YDabփq'ɴLyo߿%h)AƐ,.nսme:Vat/)cgT{.aA̟3$a:CFm|IKy뇠cyb?ba˴ď\^K8V;37a6:}7:e?X 3}l爜y^} ^e %PMkjc٧K0R I$MFF~;%5, I_DkCǐpPKۛjFїr%twine-1.5.0.dist-info/DESCRIPTION.rstWo6] $%>݂4m,a;+hȢFRqadIT@ݓ[JF/ eIPTܚڐ4"u)Y)Wx=>s=>8׵+tkmL>Qt#+WI9mS8KM]jLYg~ue)z_iZ蜮Y *ϥud">e 8+H!W 0 '-u[8HHCog&8FoHʵw&|ʂi=(*mתBFC5*R<}:iJuٕ}Nrb0v,$m񑕘Ñ!dm Z~P^>ں0 rV dPu=أ*Kvęi)ЇE)38e҉=g\^& 7 bѦq菎OctPZ wq3tp"B,CV2r?zv3l % qz-r[%òWyeBl78s29:>>ExZ8:5pen>44Wg9Wq`%oվes4O>(:[$#V϶iLoep}QWxևQ&gr'ٻ/5t5=;8w}4>N_N^N//.`[*F YC+XBwA3]nm8MY[Ϗ&nxlC ->w@5ņ"0;`} s)vknQCSfW綯BG}v3NKMXMS wi/$Wrb qWҾ)DDp/)neo{̇?G#ؒd ,n:,}@x'Wihm}R$Iޱr)T[U񾤝ƔI$"1| qc 44np2/Ch-:blHyWugYnѕ_= ፴}ytƼȯpc$ߍf 2gU?PKۛjF s0Qp&twine-1.5.0.dist-info/entry_points.txtN+I/N.,()*)KUUz񹉙yV +"ZY\ZXZ ЃC PKۛjFx6ц8#twine-1.5.0.dist-info/metadata.jsonTk0WttiA),5a`ZgG,i4ﻓ5et>ٺ{]d C6f٥ M/4l2͛  .xźR֠`ج{qӗi(eLbJ U1¤FoS⻨ t]z- @U6>cd9I#^]mC=|D?k+%~naȻN@.(3?".B~<bK ak7 ˄)#VIdt҆Nzqi:<·*^3*#*sIr($J!VUȥ9>IʱE1k)"Wvp.qlO&Z70QyQ]cd2po m@{jDp G¢U!dJѧlj]F!Ac65(c :QbKF 0Up떇Dֵ~E陥E7bŖJl)e;I?g17R'+$?Ԏ7 ~AM_~³w~ic+kqo:lپm_PKۛjF#twine-1.5.0.dist-info/top_level.txt+)KPKۛjF3o_ntwine-1.5.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RHJ,./Q0323 /, (-JLR()*M ILR(4KM̫#DPKۛjFKItwine-1.5.0.dist-info/METADATAXr۸SvgLzmgw;j]vMc[#M;L1IhY$Eى*/$/x),O߄6RU#v\R]JG5ezTQSKXYH+aK/d=T)šg`[[a"JTyXk~E66WzUŋl\ZƫSy'ӆ,QrXMKbRGqtTG@i͓\9'P^] n\J]0JENT*lŝ(T &[{[z6Z;zfjiW\l_qhXWY i}Ƴ}`*H-Lx.1Gay^fZ\g5FN׻6߼a{^ 񶴭luWPd%DJg(aYf2U4u5 SbBcW٠Q)_Y1Aa߼; BfLJX:YesnI#F Q@LE _H\F|>Vț)\ű7mS֬8f101r\xQ%Z$c (MfPJcqt||AA)xUk+K&3az^udǕ)ٸ)0\.E1wRջ7uqPtt0v+|8`hR@ `z@@`ϖNm椙s6^݉FJ1Cީ%]eݎ%e āG&8wtL9y`?"<k>JЗ;B##̉Iud hpߛcE?DG:%->pBAXn 'Q`Y9m\s T(֤5X:cq@\' Bax /1M "OQ'Lh!abKrXk…RmSQy'h@Jn GHwg}~cT(%Lg]}nmq(t ;DڈM+؊GH{ر~uqԢm3\bx fn;s]{_;0 s7o_lG|5fDK>]BƄF3 .갏 v#kx+>JtEJ8T0hɠdxz+HzFA*a Z[a<`r ~OOB&uEOKY m ⋱VAyϠU,,waϼ6K~$d ͫ$G\W㐭]n~.Ƹ 34pOߔ9#uǮHK 7<7gq =е,tHg!p?^<{iE%0e4(+eZal6Od] VC: nOo? Վ"qLkT"e/](xq.2"_vaZo94pԟ;c49dF[~8cQTwRE߁ȍ?`m8f;pT00(ґp0.ƸY; 1qOSPKۛjF.Nrtwine-1.5.0.dist-info/RECORDuKϲ8$OOn@APD./i墔IFdM7MӒ!_fUF 5"Ve!$<_f}+v4bfL?x]%Q ËAJUa) QZ\Hp9;X`iex%qtQi"HᇍvJl[;HxڎՏR,_JG2~8IpaĜN$kMT(:i֝zR7`3\8jCq :iW4 ϩI"ydUK>$lGw]?Ԙw&/XO][OSX5iw@m*:F5Md{* ) J!(bثstS`L2g[ŢMɲ$k0%tV/EYXٽ%/ީz <#+ r~n3iXzy""l"파o_"_^{O02}6kq37T=Y=nv<^ɋ5HQ4O$4,ZJ`-bD,oIU =*6)9h ,?W.P(FX| +U(=? ̢,9IY' )(wFW8$ @oAWK{w~HFAxᄅ3ۭ[m/\PKrjFR`{twine/__init__.pyPKuEHOJtwine/__main__.pyPK0d$F>_> twine/cli.pyPKsqEg6Itwine/utils.pyPKuE; atwine/wheel.pyPKK?EN/UXCtwine/wininst.pyPKuE}KI: twine/commands/__init__.pyPKK$F" A!twine/commands/upload.pyPKۛjFїr%##twine-1.5.0.dist-info/DESCRIPTION.rstPKۛjF s0Qp&)twine-1.5.0.dist-info/entry_points.txtPKۛjFx6ц8#*twine-1.5.0.dist-info/metadata.jsonPKۛjF#T-twine-1.5.0.dist-info/top_level.txtPKۛjF3o_n-twine-1.5.0.dist-info/WHEELPKۛjFKI5.twine-1.5.0.dist-info/METADATAPKۛjF.Nr6twine-1.5.0.dist-info/RECORDPK$9././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/fixtures/twine-1.5.0.tar.gz0000664000372000037200000005142700000000000021445 0ustar00travistravis00000000000000Tdist/twine-1.5.0.tar}{7p+y, eli|7M0:3ئܿ=FƉ{ic󥣣zޱ77>ΏwUj WE ¾bĎOlקno/=5;8ۻ;{_'@(@L`:v(#C;ݾASExB!VGFD ANj9q&xNom??/DeUt#u=()wƶe߾gA,x66zNFoHq;E?O Eo4b¯oy֋:v|L CN 1v{ {::x>ebL ;0e^;[kƿ o:oi<㿵܅lmm61>G8 AsIBA1ZN ?[q0A{UDc > ˆ,k_}j Ϭgc׉Vxl7tgV盝8ؙ ԦQGVtYL #\"7M GP<}n#.fiƢNC=2O=Ъqq ,$T%zHA,|0>4KXUDbܸa}L1tQaw\-1o 9ob6<;<7 p5F`,9T0o?ͼG$p,%0P4DځN_v]9uo~T37 Cd}@s<{r끈rQ{.f-a**(@؀ ` C̆#i9 GJ/m.P_! |n; ~h5`rOs*g4Lɍ;mtE1,,`"$]l4Q@amGeI*4u[0ÿC1 RDxgP1<'!Lp`\58,HUTA~A?j&cӌ{{P@$О~'kި=='Gi!h"Ldᕋ)SK9q:5ʼIygVm3!1a6{q@RU㼚 '8DMN ' qz# M`EM%A(WehP #=%+4[߃Q{6诠C^dab6Em,(jIثKjR 2izFЛ)qɦgXb Wb'HcOkQ+OV"p!=-C-#9TV31pmY͏Pg׸ BE+Ne JxFG*Ԅ)zߩ__}#i >OC @'9s ,}y)&!.\D _#σDryOmn$F)$7|Rnx=vM㒦ckA: R|Vÿv14OF\g[d$ȷ}˧;4n2V~R(*5I5Azo"I,+5dJ鹔/s)^;[Jm(R&|JAJV`m P5d.H2#KچpX)2^ԑw;2hjMu!݇DE_+wT"B{HvDLD澔3Cb@F+e5,!m-H@C k&a2CT'B*I*],nMnhKzl >-SabpW^V!I, *]lk3vQLHu4|ƞ1+L .I C+O޴,(BR:Fyh)QO A &Gx(TeUm_Ҏi)~~ @nCeV!kP5Zv}H1Sϑx/d"бK_ZS, WV6"_43ߝ=X5x]\$ +w2厖VsY"Ѫ=Ej"Pm2QB唵JJHk@ҶK8>icI*\E+\8?>[u,mPcX_@3Sg)(2/2zTFZvP7/D|2`"(i h_h7dk_ 0)TkSLPI5%J>@Ha0* y7y0(tϦjW%{1Z^MT @E,8ؘ8> jj%&{jv釗sRI׫;J|RՇ2-A,r M2&$ Dk]]2 \ǡv| ȥ=i#F:3Es" *R(@h߆dv DR#5NI2 @Xj, PF> g8PO9UTI:#ğss.WnezhcwY۟$M$^`A"j0,5An* E_T<R6:Ds lTC y7- 8ô";a:m2/%ySlp"!(=\j翲߻[uc|l0G"{e3j(R ]iB G/ևV%Ϙ9ҖŰёƝdSöZ9JIl뢏eȨeYH'nB4ZNQK4Xǒo!#YE)o OD Ҏe=h_=o4w뿱^Sqe/9 UUgwBx?{wiIR 4?j_ hXU _Ӌ}gopӳˋz_ꙦO}jU% a ==mѻ?Q`qLt(zYn·Ը@@{k rNIP߱"3ƂcKWΪ Xqb cJ((|;1 P]g R^-Gi Jw/NO274pRֵe%elվ{.z(dY75Ty.b8\%0Z6*r>dڣGiRRȎ3ʥL;]pXp˪)kQ.0`ve;^G_^r ro'H؝$siD߰zFF0y2(q gP܈;麴 ՖO?[*z}b0ǧB$LgU, Z? YQvqSƖR]Ի52v' =l <hHj?Q'v@ t{1ڒCce_.$챸\ґԯח%a6]—tK|f*F3=u‡v ٱw&ŘY7pIf/_Z[(\$]Xe< Air㟖=ia'~E,3KKI\z[!. ,zӨˌ<rK j\tsyU/AQ `\'0bb0b,c\sbPa*Ɗ%(̔PG# M]1֬-A |NM NzPo5ݽOpÅDŽLtpĝ‶m+Y3ٷE3L(QjBqoYhl -mYl'鵠=C틯+ j˯*+_☯$jɲWQ)9̢@mx-jו,jָBO qQn):)7 B ֹ5ӹ}9T c[KRe(dB)"}<4 'sNibwNkv孎^ g,taYU)ƖvEyב;©z(=)vo$v؄E E,yT" ^ӗG C_>}{a7g4p܆av?09nY4aw/uuAK!^-ED:/~ca_f9 gT/ĪYaH(Lq9L5J@*P!Cݡ\& $ZyKkjg4ƸDž#oi9PS,P9^Ƣȫ#"|U؁~7J~X_"B`=UC%Y idq@hfj4TUˬlI;vdwXa>.^CC̤=U_$1[aPP2Ê6Җ3do mӛ7 4㨗XHq^77<mk9~ytrqlme㿷w7CxkQfM7J\J(͍Ptj̍DuytB'Ǘǧ'xwqTGg秇^:<5Fm:^](OߛDf|>=YDi$ޡs"}cc2IFhh_:kA JIޅNjBF2gY$]PAiw:5?=v">gq@PeIIR&Џ1JdׁN@)e;2ܦ#Ϭj2걥)I%A$*^aԒ^#W3\[^ӹaϴBi{#R w 9`M x F4 ;ALǒ{N0N8"v+8<+R& iB.^{>Qp!) \h^fFսUk>bR 9$Ӝ] p.qc@lD.o164`j$Drci{v$RxŌšZB>QfE#Ժn Ԡ9LlܿHï GKY$̝q<B󓂲72ƨI4bcJb9%tօ@6<TY8FDB*槤*.`88s(7v,*PǝƜ!T2w aQ1Vݬ"|*d$raK[*RS#A"/jϙNh>'eII1RM"YV FLrj绸-SF2r% 5A+|pC,YJ% 핀L`C d!Z12D!AigoJq8vncYJ.%VN1e8:rI FꍢFJ&ӍeU4dH9TphN8ң0{a2GV6&7*65:q ʲʐv]_y3'h j7`陶Ee:bTK"?clĊ ,6b;[o +a<Se ?9ڳ*_IQk]5w$ʬ(b_H${_(O-Yhj2JQ3cFF=D-51gM%^1%jr֐]TgjJHd( &)(R?*A4p@#̃3H:Uu pHK'/J).& 5^Ղf\rQpUp9}/R-jbKD˜񗉃V QLBBҌR)0X.!nBdDK[ɇS3I/մkmM!M4rՃwA*MS!tx:^>zw)n3t~m{ Bkz0z TfݗRZ>ižAhnɂ[7ܞ]I{VmsVFI]g@=/i& 7=@ C2dђ+,[TKG.(AX2̢Kc]@K$;8 j?|}R?8??8<>+qpaM܅kSx$0Y7tәH!#̰I団~|G'5k7Ǘ?Tyu|yrt{ݛsq%#wёhO9Oݱw2 RLCUc@^:@,ᚆpQ4y@֋/GAӆ'd;HIs{0o$*b7ƴ{RRt3Gcvt+)0jw&K wco5#[K95[rtݱ%%U>zXLwmg&bΠyEeZ(:'sMkIz,,P0s{7q{IBT\S1kCEr8$oyƌiCgGmzW*|g0x }L1HF{FGo< Y8cutnKрt# 8P?zv";k62/ 掫v &⠇54!0juZyt(1Hd7x%KDy[؜&;1A:cc!bj@Wʘd/ Zli" I(+zܙ1Ä${%'ck o<1bǀw`!pqPO^c'gQ\0L~;*eMucz¶ť85j?Gd짥~=<;з/, ,ĝz%w]'^ҝL%uET ֟߿Gqۛ; \{{{ͽ=]ucN]DnX'`¶8qeIb]&h@bdg(r<79Qg`QkW[ZbnYH,~-^`I harNGB/UЃzUy枿|3/FaXXIR^q@w|bQh..8x$E_/%wQ]AmE*zb0d8gM v]X d zE}屻ub&9p~PwmF9ވ!f-e*/C')7 \tе<%xnT'C$Oz1N$sg^Җ3vhp)3L"I.ast:Bw1 [ws.2 &)ʱ|: ^̨^jdT_)A^jh$Иwh짐+s>fI 9qh"/NxѡP3=7#`z鉹\6[ֻhQ~SoJA;}/e|-VJI`y[_}WF؜mmbӖC\_EG`ElT?/N_< rQ,"52UC}tx@?8><:ԧ!퉗oѳwUmW-A!Ce}P P#w=SӫEd舿Xc!(Wud7.Q}T) >$6QY, s tƋz{"9(*jJ"]6a$-+ hp%ܿ?^Hm2YMؤuÕɥkb a)1h Y$EHh[=\ S; Y`bA~g1Rr ~ W>w~&:/!tv{zǶ}7F 6\O)(uᅽTݪ^z5i|Cl<ņ>\hYD}=6[N 78tZAޡ ۅכR|$7)duq{t$ Ne@RwQݤRrߩuX(_$O,YZ4Dڼx=pM5Zs |m*1^;I o DڶőaàˆUם-?")2vG*Ýzs9}חy!*p\tj2&YG>8mȷ* ל`q7҇RS.?-=ܗiN`2*wL՘ig);Дw0J7@2PF@MCP džNԹk tR@ҶĻ?^hi-J[C5˪l(" ji߅:K2OP9[X3ޑ?{N(䂎}aN";+Rb)xn=<=ܤyF90/GQּoy{5Yckܿ{8IRxF(l~6_o%{ܤ?;ݽߣ%o'uٿT ݏz}z}oZd}Ѿ'=>҃'Z?BE@+>O8so|~ ^Uoˤh{ZbY"+wdykwX!KwXzò>ڿXܟ%>c?"aݵ8 ϰ|?K`>__E|Ѿ|>?e%~}ugA7i9 //[_OÏn=!剟; 5{ss^xR!/h_4,eM `Q]Y ֟8tȞ-L;lDF_Y@GbfdiqȹA:!&aU20r-Hw8lc++v|$Xh_lҏop??:ۡ_;8&9 DTKQ ]kW T:taLeD|NjW%ω]i"YԆߑ_^u,JEg>2@12 "d(LiK3tH M!rjq6 1#Bm:C\#q$RFR-j+ILi%)J:()!^R6ŦGnohϖLJVJ<fMU5>AWI`-jB?QKm0ne^~rH8f`R3WFTey NV@֯f1T/uro R*?Fì$D,J])bB2>cq'fȉ{w[a~Yn޷½[ؾ_A_ c2._)$Hl@ܰ2v&ݾQKG_ p6X/RZ /4EM'^UY^|ZjzdJW4'J"t#t~0;2 L"A7j~cERV*: .G^\MeJ>V~/w]PP6w(w[`zɤ-UpZd(c0S,n 31̢"SRLMmۘ-|Ue .WX&Cv2P]St&et6O_ (#f{f2G@2@g&OSQm''TT«21ŒL5}6*4IA+^eLZ(׊ڕ.&j22$8 N4Pl*8 E*6V4mmǥx!˪_<ً^1IF 1?;Yo' '9Hv&tKTObd ѫwo.fЍ|O8GAkL٘ēiSzw { ?$vϯDb5QPI*9)U13`(3ͭ~}:hW)J)Cg;C 7D Y*ZKSg:agW*U'p* ;cZ]e#%P.e(\U;)I*|ښֿYv5%dL7 Si~{%x:vuD 8_h~!>~lKLfKe jj ѕSv _yj+\8tJG^=fj4˟]O{c4O lms>Rᄕ$0B"|5ي `vG oE;`p(we*uZhuQxP%Ijx~3 U6sK~?}4w?Gim߁?kmPh0f^`qYaOuA$'>??Txfem{ߋa| ĸ>]lZe!$XbGXYKi{z#qcA?[{- "yYnt=3Ylk?ή05ÍƞN 9[%gKTܨ?4Zf t֠,GmbcqLTa<Уmx9XkNorzu2_oC L?@t=&GYhy PF Va0-WRؕyV¤*NeUY"l~.L: 6gJ}RU䓪ʖߟĤ_. 0 %$e!̦e@R KWPtEݩ{M1%Qvkʣ0psKM0@<7i oV~rH&.]cҡ9_ʿQ#JOjP p Oz z$F0hCϦ=fd5=dZ! 4Zzx.ޝ]\$},8?z{\\#S6L'^xE-ûɐ_yOAO"btׄ Խe$J z{ kCz#ʴ_mӨ\OuWspLOx]u-":Ypoc5Qtr$nQSw 9~j~t&C!]鰭NmR1xx`h&(jyb;9_Q@_Ëf)EJ`D+uRqWICP}y;4??׌.|z׮o3:"oZ“^2(`gVsu8tcØˌJ7btk,OƢ z~r v`}fm8VJxzaH!/WJ{3CnȳT|(4*xp%P;7$r2y_/QS0R6= }R)ld_zfjZɮn/(|0]U|x,)RU󋇆2DLH}$ݡ jUFzL]+|*uGr>T_*jHV4՛47F:'B,+ !A׮xˇs\õi}h6(bH^L[:$Q SB??Do7*4 p2h[p%jjڑor(\FM \1l-sԘpf8[b6O[5<@VI{14ա)seG2U 3jR2,uX XQ9]x{+| 0jŸ{oJ՛Q'rC>-ZUx:ƪlfkA ƪcXQYv>C MOtX!ɴIEtFq}Q 8@m]~rsg2Hi@33ggڗ)Ti3ShL\`CW2X*ſ 9tA~){r5¢ijSJ˟B%!(S;GwvN TS?TQQQѸڠmhLgw)b*h?3kώVc;a꾨ߵoH@K.H :.ydwnPH-V܃tA!~* j8WIXT\5~lPS5đPOơx>kuRDPX<ۖ5HJF>&FΥCӆ\b(l ˪2zL*!lRQʠ3y%QC M} Z1հjZOk}uFb%>#1bNS6Fl02eTM}+k yF9T=ufKj_!~%ZIٳ 'n6olqwVG ]c*mH^+ѥCU /Mi@`VQf9& 4jG16CԺw@W ɱf\ n3z$XZfpQ@ZZ.cN\j8rƦx/>Nu\EDK%mMS])}Q\KPXrY}qS:L ץi|ʍWJi+ {]~TFb^RI^1}3ljz%5ɼ/n6imuSKD\Ï}ߒMJNaY,Kp_:Hq*hE<(Ϧ38-PZ&˔_R}6P!GބO 97mϟhzbbBTjjb@K?ĥzcXN.K&1HK1UeKz+ɏ8zqH=Pp1#K s+Z*EE[E'`_EzL,i⒟I)˱J6&ͩb(u } xܡS4=(Pz:i]GNSݖtnR2<@AO%-USfE]!pXv?wޥ+uHOFIݭM%7]痌.kŝ|br;M;;U,x6XbBfGoR,?/<Λ}e[b.`*73Py Ρ8q FG5Ht.NFX4{{N&b,HyL,!#ARj2[f1DъiVq[g[elJ0HMd#Jd86.r TE농1(`Hʼn7ZcOT:N&u['fejw$]l-;4=~N~ C72Tm;8;v" @RKʥ.*D1f¹ Kf]C P MP&BTX")/K:;Pb*IpxVK ׏L*%`r:8HWF]ҫOK-#^^E eo3 7:~s˯K~9:~dD!yjD8][&ݸ%lGoS05N nindnZ'IDo?{SK2g.Qq|`o WĂz?'UN@׷͇I.d@GGvIa7ŵr+0aoK,mn4=BB1#6ȃ~Ξ솉B}aoJĶ3|M(O%?0~i8?⡯HtSB439:B@|@bH}B%Dx`bgKc :e? dHZQˡjoA5I&n`CHw_ӞpKjVoL۔ ML|.Q Ѓtլw!{'zGx$U ӨǼ^*$AeA `'썼kI`M_'`fQ>P.e8ܿОAuH0x_D+=MH߇[+o9E (Y~{typxpyPǷ{*0ҕ[o `U&.٫{U!&a\]9%B?eR)VX5™#s[5CJ 0C$L^5Qᓺf_rqaHJJXvE1͵MaVB1fnF22ˆL\2:=oa[kkp/LRH˩Z%t? TT_ _ ' \ 8A+64\e: *0J@[;uW|2 *z8kMn77_ﻘ:{sƿ)`lnfk`U|_e7v3k>C/w=qѕghe}Xxʕ%%ìNF7vk(] aP ߊdIb]p> =`1GKK,:+-=FD[KF,Xn2/*~$y}|y N7r8P2L $s /Gsq1 f@c #/~]/]Rt %VB,tmLpDQAPNuم }u*$5܌0xa"=Q@QINGtiji C8;FxQ4sfsFSEcoRwaMctׅ>&] ܾ-v[E0M,Fthy* x̍ h` itiQGhKF:,K4Yeٸ=S0t:QhM"GCAg~11"VN9ϰY/0 <&&2  9 "ھb[!i)SA񴕑,˝" tBM!3<;c4PO I+)_09+6m9;d, Hl{Y薍%P_< rQ:yDjdȇ Er G'N./L8:?9x{tuSqvpqӷoUiH?пm_NK1##C rH+Uf}T#+H)R0FQ 6&\I777‰#uA/03NG&/VJٚG&.<RD2N<)M& Ժz2(DH c+#qFY44w@,TB"heh W>o39> Ó ؘoTq:DF@|OA /V͆׳IKf;rr=D+剢'7{۔o:~o*]"S|9+ۅ`vMU!5>t b] :eA1^8CpAHgNÆu/htYP_} :,/'n,-j>uR 5Zs |m*c:J%7 "mȰ2B j)YwO/HRi tl*Ýzs9}п慨qөȇR,Lɷ;6]58 s>y]jeXH| wq0%V^ |H ꚦ8J0u `(0<6t8Uo2`ɤ-'O:p EF- !T:%@d)f…-TTTN@xNHA#8@U5˝Bi4|2{N//*+/]^nFd=ܤy 'Xͤ {ow]c5[n޻ؾgtgPl޸?mJ?;[|jb61Eɦgoq/;le:|cɤۃWG[Go0ؽ{mK^Ŧ8R~]%&P:dz;s vD_Mz(rӆ:c>X䉂J|kXcqւ8kV8h%/{y+ן?>OM_Ux6֟gY֟gY֟gY֟gY֟gY֟gY֟? Ut././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/fixtures/twine-1.6.5-py2.py3-none-any.whl0000664000372000037200000005356300000000000024007 0ustar00travistravis00000000000000PKPG8hb|t\v]AhM#.&}bh-ɰCe-8B):DmZ;CT+ Ru޽jԬUBA blan٧lwnn}pV'nn&./ Y$b!PQm j/jZ?QlZ4 "!'p$n +<q VK?S]@kH9NPE }+*t$FHEy.d8?q;rsEoa9hΡMflz9,.,+]G z8]k)q;bvx6φ0 rE+Q+[ʷ:P5]q:"ClG1{?;r0%WqبU9kIW!xSr ώ[ԫ3V R&t_ixNvO{x^>>.dV:'T_a}vʚqOYUAXʿBz6_ C뿊P"ůZ{d8e(œPKG.Gb twine/_installed.pyVmk0_q-UuP(lm ۺBm Z֑$/Ϳou-#;='yT",|>t kFRU2ן*Y^,*44pMeniؖVR+5RJ墜=0Ēsc56Q2w @vɌ8>OOy,VO2׫)^h/1^=&JߕvI$wbJ_(d'(x3K +JG-Pkn\# ˉ5̶;BҴhl1M]~-ՙvvJsa4g2Ǵ5 ̺TL[RaӠmrr5s\rةiF)Ʋ^xD'b^GQfo <;]ڱՄF8Z販m$PZsz$by{2ٿjX{H3jfq-C#pήp+띛v^9f~ӖPR'f$p`uA/fw¸%n\jMLQUPІGPKBM-Gbzoftwine/exceptions.pymSN0}W\ԥ i/)EC-jnrZg;  jsν>m+o) l%\gg3%+ubK~ϔQ1[' `tG1Ҟ:Ǡj2kƓTTiP%S/>3x) =ޛŢTD͢=>YWGղsdo'-lZJV-2Z{+T͜}/,[&er ̊)Z?n)l6_zuo ;V;_ΉWc~!BB^> rKYTӉG vȰ=H K+ g*fe+p+$W[|ۊ$IO( >b0,)Bʰc8# QJcгe;LzzX D gFx0 Y8T !ѲSd7d2p}j^VDj£iTAS|C:% lF;>ٜ@6Ve5E]ꮭM܅M 5!m=Wv*?ڰ`Cџ8PK&ZG[twine/package.pyXQo6~  1Gi2X֤- t]Q-%.Tߑe~yVTdNkʌO&GA-!'m r  d&g#k@OEӟ–dM抴d* AIMh0Uez# xכKEMhUpTs~zlj&\U_\]/N+e\n mKFȰS\sX]̈+JΤl*/P0TbA拈z/fh7wwyM^\_78zI.ߑח3&\#IC#!@V,CꢥQ;fRoDz9Zؚ)dT2Y &ijU+ M\(BWgڍg2CZ1VrFjuOzŒʲbK;Di,Wl'YE1oivw̗@F'f RiKV3s tW5Ԙ,kP4blUcwt`m7?Jb`xOICUiZ۹SB?m|c'stiHx#YQC:q=9N?ޫDpES]&d?Mlj'(%= Z2Ղ7Pǻd2EdVݖD'~z60RW髻PC#X :a^~ȸ Fsl1uJT_CęGg9GA )S ](i;arxsXS.Rԏ?[ ,s>F;=df{,1* YAVҿh•\# ћzWGgTqDi}}?N>`=D>+.bWm&ĉF}n[bA4T| U ,ݶJ|c#U[5Yۜ(΋$E+%?<~F6ݎ-u j#6ڗlDI*:\Z,qnFxKixC $žV#%;qHGjx{ycZ?{ti+c|CO<*ɦ0 uFr~W29VFWbPhMx]miϞ ۮauh]ގ*6Ӫ]1u&(4fBmfHKtOxMwE͌BZRI'ZMݸ}?Cߣ%TxFD)i>EUlvkm`<PKG|i twine/repository.pyVmk8_18˶)@^)b{*[>In6%[8}1yf}Vtm/0a9}R1ӑ{}c(5¨`=D:f~%[YBƶKFr5$\  {Xh>MfsMo'lJow0>2Q^(˟Hr[Bm #Ӓ|AS:Pʸ^L^ϸarTK Ҕ Ɨ[i)(ϰ~ɔG2Pp Cxn0^gs) R^0B6ƹzޗl~p:[wrr-Jcv;Vϸᅉm[Yl@ɴ{.KX Jձh9\rV|p-i*ʞw6 gy O}m6ueu P BRlVaJ)jTIc NUb^ #{pP@ ƃwFFR/`@oƀv^9.cmɮF{]Xe{G⪤.}<`=6xAmk)BX&bw !7!]>tEFhHbFFfzᦞWmm A3BC{}\p)ڛLے}hiYY}P 6d&a7-j¤K߲C-j';;CE 34"vEZ90*';0'hw '[gi6J̗22h./Yx]?}ױz_ujoBшvl3VmPKGhXRtwine/utils.pyWmo8_1P>ƪro_5qE ڦl#8 odr[ 3/|f8sodWb1kx+ `f,3YtĒ*V\pl?~%?B*} }R<'"e[B4G!9QRn\ba'ƪ )B|raJ3/- 97ƔpۥJNHM\\.^VCsAVB=mYZHl8I0X'efvLqDY mXT27P =Mg b|z>N'7Ӌ\]Û˷ӛ%OmÄjc~4RP5e@&AK%U+氖\\mh QrOǩ4ʔ|UR|>'R` -sstAl Œ@Pb)W| uOi̦~RUĨ(?ĺdJsy&N҅RR`޲R1P7v{nY'+ۅESUS6rHB[{(+bld:#}pI뙄:{H0v/5?GQɇw7W')5&c/E H1Zjbק3SZW[t%Rʣb%IHB;, rg BBSrN}GB[3Gx)0Rjl|b`[/e&JI_|,=G>R60jieE4dpU3Ɗ &kOZ1}azM~L2"q /cbuA5ƪi@u|s!Ύ̪ܐS;\GTq/rEH9!ݨ6~|BQQ;|w.4Bv1<(Y3Vgj#4?DG! S~G1),(DPlZ:Z|@peOG< ^2~H݈1L 稺> +ZdvE 9jr?s_z(+𴮉*;i #+03=qu}g9lzOf L,#_po6y}liye_[WbG+u_KMbS[Ӎg.A4&gAP#a/:} eΫ_EݚٵV_7\,284fSjuYaoiGb0vt9tX/0f2ncZϮ'pb$etMx.P`;H66:vnIwqiW{0w}/gs^xE[7̠`5#whVNw >J)\W޵0j"=icCyȇxc`0lcg2)R^^e{ 4S[W0օYww˛ƽz{/ ؜ո y~]G=F 4 ֶۿ`1BkښPKҼ[Gs twine/wheel.pyVr6}W`fD2onTܱdR@BMZR{wAR"Ջٳ,TzgDr٥UyG#F -dVƚ՜{0V(',$9D?!NUlwL** H!,E )hDŽdZRpn)>6j895.Z9}~zlbe o/'z;YJ sc\/Qa7L xi,F̪mdɄuF,+KT 0U\xΦ{1O#0]z`b:kjv9]LfzƳrӄn` GRkZՐ\,*^+=0 f-,Ң YJ;sTFY䕫 $ W18z=ю6B:4)~"U$p`xi9P9o~}Z)ٝ ?uɖlArS=1FU"ԑARB}5a4{Ң/h[%`KW-8q展&K/'x'gv}49u\it_t68tOfP&:6Muf~| HG -iSFyևJO~KȂ~ɗ%w~oVߟ|OwiT|B%{"  fR$ -)fMz]̔|/q D?%)c6m=oP?¨m^uw-Љ.o xMEumoPąQt<{9,; ‘BaʬF?a"M-ۆHqEr=/+ BDz!{Lb 7n 3KOU8> &]{*r3߅~EƓu$"1i]ް㳛*_zSۯ ? 8ٝT==!Xd?{.}P70Ж:fd1/CXl X7!V:F#nYS=n7Oo8{){F$DJȐX#mrÓߒ6et Iå}T=nMf p~~qh{l֕uIVms`"f`S#k-\TQ/PKK?EN/UXtwine/wininst.pymTn0+akд=)iN)PieHb)_^l`;;Y27$IW2$DZGUE +a+dp ) 0z:~ + x|d-B i &' bTԄn藇Oyy}07 jxAa<_9$|E|5KXϸ {qn$<O P;SqG6JqH\7@K}¬"'걒tOg1A#,'<:/IeP尒팪4 [S{5e=ætKevayFZ*7C[ 7.,G^Teht}5dz=6.0=NI[߷8+l[ SȗNjNjm5'bXs? Z,a"9jݲ$4sHW=ԺfVS@(&Bvg>#?[ vYc'[?\ ˦7X]SZ+ROHt$s7v{ze&Fzz,scQh엏݅<pN5kpLBh;0;1H}5ixPKuE}KI:twine/commands/__init__.pyuAo0 Drvr3V8@ɐe!HDͿd@a'rh80|kg<[@m0 < 0Drn4 q8GuRD=|ȂvGoHY0g&X\-\JJ^/8 0,EjS՛/{?Z1B߉lOh Q#j(;vDKG?LHU¼pWes}sվ԰j[}dz_e&9o>LISM}Ոzw5%JCGb7g}pGh>q 4}Te$+M@'$Kub PKG{c twine/commands/register.pyVnF}W h&vŅT_PHN (9%2bٛHJ[ 9gfgf nDixsGXdT.&g3xsl5J;yr+S ⢁7fC䗢'B؋jFhW>j n+Κzgil\fN0vW$eR򦤓E{) `Sa5ͶLjjRF͖zq30s i:2#$s[1|h#d*Нbk=sL=8Zym0S<_}ҭ!)ģy&!p9xd&q5lt'ŝ:1÷LWY:ƕlOgG&PPT`J6GJwuz0TK n{jgc|4xpФ\Q's}_EMc3W]? pwڄ)圐G@p "mKE-퐬vW+vi`3 -˲3a~gWVr+JF)+y!хb Ia]gQoh~U;w8.zlSnT8xa)Q'_;:Im~@ XGMwc}39;M{;7gQivPbL RyEhR8ݞ3aUO@x aHDO"z5hA܏_h^_?pſP[j*4,+J,_5NoPKG.:twine/commands/upload.pyn]_q@#i'CB^5 `ĈQSvfhY sB-e*8s#Njmۿ{ٰ{ӭVfr49_DK蚒+0k-+🿙ï\i!xBJJfD ;:͑а5\րh)8lY[6H$~$0f*^ӞlیYA3_../QX ֠;PXll R;#I֭F4\-SB%*Hh*@r~W x~u?G~ί..n_=\\8 uu~̈́lsH~R yI|$J:t T8TՁHHa'{JeUg:Ke-Q=Q'Ad*Dh "-]# Y+V$PVU˔Ṫ2:kYGV4:$Q"6(̐|iIedaߛjVe8qdH\(^VCaqL(8 ն&#;tf ɗjCiPvнՔ*4aQ|;Dm)! g#p)p|mu8OegR !xȬ^_,C2Q^i#PCO2*sD<<4^{Mբ呖T@Q虯 GcW?eyXD8+q/ paKז`hKk7:".3’ٓM8 TD g뎗54nwWA?قD N@ @9X X4&LV˄ԡgS,pW0ҩ8JlZ㶢ަ;N7t Lᜬ@YMg}o|k޸~NjYeT<4m燇[x2hvar(@>#tKB[%yWh +) 8o$ʬ9͝i+)m#KJlACΈ!\(0&s!Qj%lQK0lpOq +]xXsUuTnM*Y-j9$(Q{%*qYC` "iw׼n %|$ٍxz"g&'$NB* thds:~@(re4y k$2SQ>d۟<؆zb\™.2XUX~0B@XG{1Ad}±Aν +ꮌ^5oy=(cUqP,fcQ"@8Wd emB2OCu%:=1Ͷ籸|ܾIƍ)Aᳺ! `=:x9E?X#]k-d.]Hnt ב`WK=4t,?vܜ ѿz0^ EJI~t3D?h&-7$yN4Ǡ'n[,bPKGMDQ=%twine-1.6.5.dist-info/DESCRIPTION.rstXko_qHGkd5' IpDYS3߾!)vb,KsoLߐq4~K+ېnTMS1^&Ul\QPqp`9F&5c)UUe=yNWl- Ll鍦܊{0[W <3pԪve7GC5q&RVq'V.lkwTb4IByyU}`$AԲ UIF rv\F9-FʚN+qG\ :]-^<)KVĞYEDD$^El%•*IWA"Vфէ{#gCktk|V 70`|нCDBYmᇮk$UD=K-QJ2Em<0y ߛɲ -+"CM֐#uTnBMs+ +i'hSђ??/l6Is,Y5Oa-\@ؤՖ9qR|GCˋtq17;04}}a1]RKWهDCIM/׼'![`?ag  ܝD .~7Zdf{EZ+4,@5cXxDQ2C,8}kBqKwyDT2xo/>)F ]c᮰HB'k8Һ{1f1;(Fb1 xαiukwCx Zdux,j˷4kȻAmbͬb$0 ; }L&PtWt>Jd]ڡ Y+_ٿ?!s\Z'o?꣪bMJaPbou:(#-^5cqOX,ݏ=~ Dk@&{{X[ߵ1Q%24|JQ)fb>w`/7X\GĂVP&^]i٨*-0WCr8^IsČ솝C14aHw!]44s,馣@ykpR)Ġ?}w+!`XȎ_A0A[j p,&xŷLijQv/1',@xWwy] RI,^+S38i۔Irԧ 1IEbX4~w1ՒcEf {Bi]Kh*y”Zj*>.m-Jd_8pE*‹U7p܈@@8a73r4HVXhj6wQtXovtyW| i 9džk!k-xpCWio JtJ5COf M_6ɯPKGR\X&twine-1.6.5.dist-info/entry_points.txtN+I/N.,()*)KUUz񹉙yV +"ZY\ZIC )-OLTZPKG28Ȫ#twine-1.6.5.dist-info/metadata.jsonUkO1+}%E*%HDIDRNg~$D(wȧެeKp^ XϏQ2ug<Ss<{{55 |Q3P=~زG%"B.]UV8 ր^Jgt :_ 4;=e;}~mmpA(Eָd$ֵpkB΍RPjf,d8&u'[ɰ`dDh$qaVJ@W{&5VRJ}YQ,].I %shs##4K(Y,%`e,i$"b<3kYb˦fV=DN(v)tEՔ_JI zliLj?b<}|n6OYͥI;pht"uiV}LkRoO7i~@+QƏ O :xGsî£nQmf9Ww9:Kێ?cDiGR2pƅj6;pAA^-{?(Jh?-6h(}ݶXTDUoVN<{YOKiHחS%Zs)h|ŝS̽GEhw>ةժIxٌNy&L˷Re.TUƹ1yJ*83ju2e_LgXd{Xle+[R(?˫ӿ}:Gތ.T:MntiهF-{q6@h H vf-4Y#?Uq𣪊"(;`>}SwC߸n2|74Us&s! uw;[85٪OzVvbk[Rl?֊d{϶x%`Zz`&п6i{k|KG탗o^#F{6HvMr8 Z@^]NYyAw) .VV+ЅΪ0QT:k Lm./χ`A)JFBՎĔZi4>hJS,k<"8j D}￀?cMM {GNjTAMZ0ef.3з3Wah3.'4!#+3*Ac8%W1HȜ): >vvi Z!!c:D#bW~h])9FX +ΰUS i9;7ZDQH53!nlƍb-4TU +E3,oM,G/QjUeE;rzU;gV6'Q>kκ+jax:]顇G̨L-:%"8z9l6)B&MW~#=E6yl|Vj'\K ]#>xDsء+fn5X& (\ !ͮ ٵ*X6p+>ˆ&n}L>T>@lLޜ$Ȝ\Fͮ17G H{Yc( ĕ-DŚ*cC[z)Vʙv=#ʶ4L:=u-!:Y:ڂYo«ګϽ8yqLP%/5jx|qzxr}ܫp-d<'a}$ID1, h}4|_l\47z;ͼޫ]S&f:)$Z4fw[+j Ĭz[g{o62eO T{ 4. j+/kWۚAynDّ.x@wu|^.(8~!f$nXP*3LN8Ht ]K.]nJ/2w:RM~7xLJLFE~c\g+]LG ;ӣ #-;1tƤg`d 04zBh&|hF>}/AK=nBFm+'-GNU#yLߴx5O3!2F@䢉.C eo)@=y%Z/7G(U( ӗ~>fƁexn>+e=wK(BZk e˓NdwKnu+h #,+xM<׋an.?] #h!NI?[U7QiKE옦$REn4HjDUA.GN6X&p@vr_sME'M \W7يOo>Cz GN+0 M` (Z1>jA;7Q5yr!cA) ; c FRᬝ4xPйbǖ`o J5^.?f3jh*1vOW{%}{ $m߃PKG  twine-1.6.5.dist-info/RECORDuKϢ:$O..(" nH&J׏'oM7MoLEQ Ev" `x?yqLg0k]*|=Ms?QcEτ`P*\.*mNT(W ,mai~L cPUia²ڶHFl jo!1CaiU)zs[]{{+1&_]; c^KIq⢁vc`܆񪑖UJKyZFkj/?:hNSu3)捵 .A~Hf9|`RgڑpSq YLYJ ޙm7hjR)XȅUô}m۳ G&|Pj63(߱:_  Dd*/Oz!wA$xjՇcz]G1N=FԂ[WYVU}8w]c˞`E*0 9'H|'3=snta n뜠{wk_uWLlrΝ,u{$&5\B4/zϒݩϖRfsЍ60y{=q%GVgؾۖy2lH?$IfqgccDnԭvxO?m/ϭ3d%pqN/s P<G@N2)^}7كpzDqЏiE<2 1 7]_T]Q,%@f7,Fx~, ,y`1~/K_㋺u0t)uw s /=t><#Dk1p!Nk>߅&`wz,Y<|waq6wwva6,rx \렘$&N0vplT՗X;ۻoo%>0w,k_}gעJnG"7[;V}޳,!H>;;=|:Ϲ'q3 C 0pBo P 90n~7\禎LbV ' TbTYMExGn'.(?;0,r㘔Ls=qx3pI3`QPCY=00Z]@97a! =ҁmkh6{!B{I0f'!98>! .3x>̏ШrbP;ypd^ f=S 0+[E%N6lI[!xo,, g$^G4_VFf>z@ EE_Gkz0>|qjs6q( 'R7p^MPfK$>(Jz_AA4B6x>A=Ɠzq,h ߇:ȻA MLyq ɶiwC=JC<3,%{hp[78r T(p'5bxSNj_`)# ]-8_zkJ3wZb bE^q-iáٌ?z{ `(a;cca‹$dC 4X]Qw,y|qԨZYNލdgdVc^|x@^ggf RAMv`wE,1mɃ;H҇ p=7Ns-"nk~W. @jEI"Ϗx\s\f/0s``8b ' IR ^o?|_&!1OFo fT1彦?|dr#~E-F$@xKϹ7&Ƚ-juvmˌ IN}70p0"d,yƅz=:m8O7z&.K9C_Õϑ4 vClT'™' ܈Ȳ=r^y3t|6r~WŭYHs#T ܬh*ȐNjX} ?G#0mcQfȜ4FO7j P(RD6p,dƮ~q[г LilԓN Da4hg-&T:*@L?y{m1$a?q7UzݺCA):?T秶'RͲ \7%o }ӂQ\Cɕ@ľ*6,T$$5S+ eyw-a*=T_%5G +o[>p5C=h_Q4}%9ķ P9hh 7Dw}x)?)mK b#avn$tKjPӾ@USNN3wg6tSh]!Ӈ7G3!"эn}lFlRSh k;ڗI*Idu_ 0ÿ +u(bj' G/`Z_z8 c)|s$)@̜[~'&@6 粃bՁeP>ZQw4V )QMA|AV-Q5h!DrX?Aer?)VrY2E^B6? ?*5,fCcl"BI9 NUj4L|=T{uRHc&4#LIg%'ZWaCZ}m.LA 7 d،HnmX (JI8 h=ӑ)D˴s"o&IxeKxsd`BuDzwHb>Ee4AkϫwE-=@KUfD $7ZFWHji~lfox|h.{bڲpMDDUXh2)>𬋲7$UBdX/޾8x䚔KlЗhioțD׈-\kdwn ڰp&ۤۂH~q:HGxN:\o-!zd0 Cd^;e *vU{Q/kc̅L{N翦<,IB +Ͻ|SyQw$A*YxKJ$HCLaa'R F}>xޥE{|tG٬)l5U-IclRO7xNԦr%5J@ksHҚ|ŒTP4МV&¾q.ܿ?X,7]cXp@V4g#^kU^dkRX hummhV[mf4WMriՖ02' ;S7PMB(y7pSju; H$(d0o:5tb]nLXe]NBeFmMc[ :yIwJ.M'eB mds)q4Q'QR{:#mIh8 V( & i&HB8jKB,e82c#I&6H<@"G =uhl_<3ͽ ?7I#hOEAmq3/Z8L{w\@L-S5i^:e!"+-YiVv)>pT%DT fgUi")0̙JrS~PcdFV=?}s t\oH`h'!fٷ^ Rn8s|ALB!D%k<=2 6]E )U}JDۇ7z BɻY0gWjȊ`DC~䠵ɨMlg_VWb̨FJ6߬&~\|m؂g:ϵj,rIWb"WHо* 15dGV"zиjؖyW-WzeDz^adܱ, s3tgɤ#;#ɮ'hp-~'',닮ƱNZ=Yzw/^W,x;‡S积7/VT@0% CU5xytχurzqS$L\yxy*lwب+\gy0s} &hYؗLѧ\=ٲODbK4U'z'w0 Sy3xyO*JqY[MJ  ,]8*V/>v`{6t+h]*ZqCyM7`fQİ e'ǹRʈV>M7);Lde|ա{.Rug~nhx "Dw"yUpF%wQ>lӍC?zE% +ehڱoFF  Y<=QWrP+_/a돾d0nx;MC͗CY}YXK뢄%C%-Ɂ[v0DLT<{ zZ-e?]E4Evb#B֟0‘i]Y >s#44+lXkՒDlEKbKȕI=yE-E:*Z-9T9}^|V !e0犁>X>V tP$ _1 .!Ufz&{ q7)pG: WK v[0K?r q27K/seO&z?2pVw˝xxģAl0dF`"yĸMucHVa5uC/ʹW۲Ǔ FRF,EUi~( P> U IRQFˊqHݤ|JqY1ȝǁpkͧ&W-a7ZAZrT;]e+VxUc:V t`OV.vu|jݪ݊}\Az_"džN1 neh;߷I# }9o{wo`[o5ͽOl'ÄׄNtNm+]3hٷE2L(eQŠo!Mxh46׆ζ,_fuӂlZRPZ2[?h0nX~aZ.l䵾 [b≼ʋ€| EY[]a }dWbÎV,ʫGV)ID[EpJKjKRfnY(#!wð3[yUڵԪ]ѵn6|mlm-nJ#ws ԝAfocؼ !GPO[YC3Yi>7 bAϵ҈nU- # hnymFL9yU>CbzŲWjFSьF}kez6k h lVlSRX[ 6*D @.u4n:1-M`)c*h`˗B% l}~bKIdͦtո"~$Kepx*VEY7[ ™Z =)7ovl}xUȋx/ERX͋5xjX 9$6df\QqBi-$ ogys \$J@yXbG.cL3;f/ėHbui,OWUP$xW[@D'!{u(PLJ yoago~<^^Cɥ ujק/_ݎitmOJGGqv+ S|Cx%dn2z~۰A*i+B KZxLk!<̼RPfUr+HS:J5IB*C3Df@O:0ɛJ4(X.Hq\ Co^~_+⿶ۻu/+?3g7kֲbOƒkۛ ߸®.!o@]__u@|$ްóxqr\:9A^_}XVj["PAS;^^C7)v|iI J6C$:%ލbg2HFh=/:XAЀHi:D!Y+/.NYqr]PAykw 2!5Z~ 0n!6 "TQ҈8Q7dt΃ 6R:#,lKYWU5ҌTdA,j^am ADLa"{C8j. 3PDup;԰Ƶ$M<&#xxx(lR4:X5a>`2M)W(0~*us*~zDr 9虈i&!KOQifǃ#M o 7`Ddli.Px (Y\Qr4$b`FۆZҹ0ՊС1JbJ`8b1-;FEJ'Ns 5rCCÕXrhi 5FfIzպ_$=hᨡ487 ~a.<녨z32d 23@T*:Sx Ax|v)Խ'%CTD#g`cX\X.$ 9+aܐT10!יphZ-CN7u'&Ŕ{%n c CPf!oFID'0-~@i;LC)Gy;Y?%drVFg Lo*IVRMDc7A&9Q&$pɢ9\ K f<8ƬIS/^1$1RjmI~<"MhOTcz"SC"$*z]tlqPl?\8ULxB`hZ7Q/p$ÐĔt< ziKUJj6h@dQ9\pP)05o˲jL6jDfNѲa4?DmXZuZT 4r*8kQ@͂W,>&%3f#$ Qwɛh{Oˌdÿ)\s/YB.S0U8:BFA 5)ɍLƳ5ȍU$t}JKIBjCO I(]+)+{ɇ3j\5mnX1B]xɇ3#shjhı-Lhp _L\%՘ZY?f0K =Rƾꏔ{@ -Y~3 :>G R"|āT&@v? 7U>Ơ4]/ sA*MS#!rǘǏd}~;SA ; ]ZLq֡2OXےcC@zO \!4Je}I ֆHs{Pw8$Gs# "x߄Bz%YGeXV%L;{6&& Gq4/X1$E5iHSHEtzrS k%j,F< rp.+s'.㋣sqrf?_6d.\1T#&14̆ 4GBQM..6ǘ÷ k7G?:8><3l ݛ3qwF>ѐ`Ϡ;OsdCј9Ri )orEݴ; HzF[禗#;JzN& y2u@*'5w`c(Sb{%ŒNΡaz.[/j: EoWΦǿ(/AH=3?ub7/80J,,$)aRꢌM,.iDy?Ys$ށ(vKǎ$إVj®' n =PKP4T\t+@5ҽL&p 0*뉞%郮!@|A=v R,'m=/Q [vieNOJ«[%jAO>#֩ltD!-%B(f Y*E:%;Ս|!͵^c9M"Am3Ԍf ܲΙr+ʹ&_fcJ.ujddiCa9JExYrF`IWE0icJU3ʶO7.,u{u9Rxzge6p <&\4 ׆N>~Ds2Dn۶i1"}o|],e ʄskN2o1I"K'g'G'g?㋣ z4{jgo/-qCs&Nߟĺ-^ke8AXGјSu rv#,ԇ۞>M-q3%S=q_n$C]%# GI|0"Y鍺M\7^oäl.EN ޜ%-]vcusZa7:Ew|j.ȫ]&HA.RBᝁ0[P &; t$ѝaP5WJa!h&-WGo kiU6@^K}]\y -zGg/Vw `n7t` @B(x@}OAh 6Snv􃗼9{ҮaSSoC\-ϴJG}69ӌ҅L~%S+DR ?lVQH ԅPsMiC>g &@35;sqLLEXKAV# V~nIӽf[3ؑ7H5r0[kBMF|SԖ;@m["8Z(~U\JcbI@}롱O<#w| 湨#5="|(p"ؗ*G*w*&t:I<ÓXِԥHHOS\hT[03bif\RGzgL%) B59w#zC47 L &+C+Е~dVxT7cO=d*Uczj*:W\X/Tgn!)7@О}hyOQ)ANͽn@@) =ւ7ҖOiG;`tG  ҈n DL",Yxa8 XGۄr¬~%ӛ1n)w2CD9]royV@I/^on, uQ8 (Yi${Q־oy{5Yckܿ{عg#b'/4v3,g8G%翛{;_;{;[mhCKf9'R~wun,+sͥ2ֲs64Yf{\*?[j9ϵ,K_s7&ϥ>yΡOmM_ϗzd>GG87y'}΅><@ts<;XVz~cl ?|2/QY>|e9QMn9/|1EX|H^1/zsc?W_e>H빴恇D}Ѳ5 ʾ7ҿ@ Ceq_Xam^y,lm/T$&7I*3X<m{L50M{>)4BͬF%jD[ZP\d@ dK≾'.oF)S|K~ yյw], }#X1*:)fҐ&O>5 g:w *}ul;Ƴ cH=1bAɇsIJԯE+3jTJWS)]dn%{F-w &>tڗ♨bΖ(Pu ޠY:OTB9bxv\P_*(5wuo^&0Y,ܠVIq+uMõ,ϳUsWӐppJHm 7wN%Uc^cmNΝ˒έ8uV;mܽ$9ܳ{ {uv}+ܯRWOm=uCπ\XNjŖGqEIB_SHx1n ]]cpT**"ۅ;hd*b%&_ ;33-nv4D~?hXuH'ArR\ͯZ|?6io7=(߿x,*f d}f]oY&0RT%af92GtXN2tt,YGwAo.P|H?{{:27δ?t 0pΘќ *"Ƌ(Vd+uʳRRBi L6pUR`sن@c&.M$ C7A^MbV*ς9#7^RO)FNrepA` ^8"\oa.nϒucAǯ.ɪN)}=v(U QM6:?0Qe6:n%xc) )jҤq@>O+6UKjj]8:WyFMT pTijU3czʃN"#L 9UdzqpZ( # ',.tj8BTIddMd]d:]}&?!>ɮ釀q2tHR&4w WJ|j=3b_{Eʒf͚b>DO\A͝ݽSaniv.&B7({wtƆ lSa5SځZqjҰo*xH3K ~v~]5' }j8`kXΞƏ ՜*p#'zvatGYW y&?Teƥnwj\><;}sp>ePø6ookZ{;ko-=WYyXɺ7u~OIu/|¶l}xK,%؛٢ n įb d8%$;BgL3K:,SKKGQBǕNmPKê]T6y d;Wlgi(ʎRh\znS;L}9{#/Cw.tE&q֪}r.&^֋^%.aR=dI њwgasO}mG*Pv?! D#`@?djq{ gjAX1.Wq7ѕZ2 =By#?B/b+ïLl-nz1hhf&c)TRdtc/X}p2C]>\ԥ:s>!ghoP+8J"]!\Gų ?~!Ruq}7(`;.ha,L,l"<)L"E(z̮gNUZfƗ&^?;YH(v_:xkFKUb5+T˕Oф>&ӬCiԼ3+[>x?KU ^,ӆo N¨ WCION#`Cي;ITi\6];*X}R*,DO)!>~'-<&NooDu١+…6X*X]zztZATU.2K'jqt꾓}F HHGZu{,nj>d<2+w A@_5٭ 9嚚R/I-R(`*izZx  s5elhxkc Ư{@K9iKmu%7w6wZeo{{}oVo4dޮw6)hsSq09~Ҽ%$ᑘdtsG~oM9sa863 ɬ @6VXyC6#?-=- ]aʃb#6*UGV*~ Õkڠ uCv H;>}c<2)DWf z@X7=Vç5,8p0bZ w1ճ(+>eQT'N2kqBg[_fym`S° R+"̛WE|OQA*UuX: D~{#ot)1G_(:ףW;;~쿭.0n1s^\AVea9Ȁ:Tp>\z/Akto##j #g4f 2a@ ntrtG!a#'h>"ЈLAa(rȝEp>g3޸Z4J;&3g 7d<;{cxxW:Ēxp+Q\+ < J I0CY 3e@a[{6u[Ai <41 ]Vd!mEX(@J6b* kL) !ZWN`)Me5 [tV$lD9ZNMm}yhVQvdɕfSk8;+7*n)uTbQWDd Lad*!|xˉKktqc0;==;-pvY#Hˎv3p3dotihň CL-$X*kwkEWh`wݭݵZG;%|n>=Ϛ?:ϳ6̼ Q1112 9AR^U0 ^#]͗%k8m/ާthL=iV'đ/Ӵ.EݒwfueKhZM#f)V7]ZLhGȯiSBYKΈIV=6juÚbRw7,;asPEL;Wx@}X R1ǘ2J Z:\6s14Jjs~t؍aӡF|&߽trMOojW~m!JDR<hbUA"mZ_Enw3^&<.P_mJƉPV7 BӹO3Y"l#L8s?QE65{&3VpM6)*Oիz\֜3:BlxCbdawt>Kkr{(ܧw/Yi>4 -7+in&e`wjW" DIWlҸ+'/y}#o0.M2U~;bcY$^Lȳ*DRSKFZ҉@ 2q~5k,%P(RBIj7ʯ@]rvoo=~xh9@V-t[{kVY颲hۿ,}rT1`Q(߼@Gg[1&_ y*N]'X)ŭ*dlQ.eJ㛎hBj(6l6H0п^eԹXt:oP6 ,`.L +yn\HzàB\qLpDY]8 bKO@b]+MhQ+y\}"B((W+jkY={O9OV~9SIքԫ Uč5L̇1ZZrYAZ Ϊ6㥱MyUrKv|[Kk|0ںl->˛75UnhPHXm{-WOI8uAs"IN]ޥcםFs ~\$#݉sH]vaOTT?1@C540"p!ޛ(7\܄-XcӼ|{͸ҊP-a݈{CnAᑃ6SJ*SGJ'aS-`2%#LMAZX?ș8dB}1(Xzp>Xa܍Y7k}\ ɑb;! z`FԁM -E[aLQDg) n@lEEσ"~v h2%(.^|$/|Aj? iϭ͝[[3Qs;)W*Cve68QH*9辱Qҭ^ӓ UH8#Ş*nym;n& KOI Oߝ$zנE' l =x%a/ $/c,/ƿ̇<h#!SԲ(f5cgC"V1pudηi7§ڢUS&}\Ƭ@5ePKj&٘xI%o3h7S&h2C`)@]].OPZ9WvP0qJ<0gl/ٓƏaW4[XL?EM gyF56ׇL,S]@%Sè. UE ~G|]IM$=Q%u<->:ɦ)FD.Ũ aCo3 031o\򠳓cU.}Rh8[UIY1o[2İD X3?\CԳ),xP%.%LL_buMˣs*,|FyXjmMzF>2亘+&i9[%v$TULFRw .2S#t352U@8jz"B5~l䢶 ᳢L 3 [2m z-qC$R&U{ V\W8pHƞ[5ck V<ٞK5Zql *#) ;Vj+ 'TP!I4}\ 73Lfs4OS{<7TG>n*;NUl H[w]խr˫эj$to};:aeyAMO<#+U!,ßޟ<^tO.\\>T2<[_ʡA_͂Sz#gb X/gsci壂ҫ'9;]f2!_Cpo3 " Kw¤I7u Q ͫRxW< džBYn9zobeFWTLԨ! A %CluB/OA`45*ρ+ldI`,=v%u_fg +uUo+*E( oXk*-oUH7\lXF\Dw4l*9Il{Y5s2:ot}c y9qޝ}sJ̯[!ؒu$[*{KlgT:p5t{Dq⻔0cg&'z)RVp`H'<ɧCff.y3RS@2C.;j4$5\CRG"m2Ҭ x MK!-6H"(}P~VpBSZ2\?6qtu,7J[գ=~e)Ko8fMfLvBGj].Prg䀓%d( N% Uw_ȍ}}t""}"[bkVǾI Gq#*d}V'oxTpkR_}ьw' -PEFՆD=W&S!{^l:ZoXuˌ*@T%DŽFSK~_ENk[{k.F:/ތG'M{녅 ޚ72/Yڼvٷa<>xl~}x{pqqvmw7)n{ym}z^*<]r8\fh3~@H7ךt^l/x_?ޛnSC ;<0u5sF F(E>Ue]ܕ7Qw2i.`KdQ}-O/2`S6aS_M%"bZ:umAae0*A6V!Kl#U 2wAzX5Dw,y3Oe(B6k9*ZI-Bm>7[ hˬj$kJ26-z@/h3FácLV^<8ˬ#`/A7jj0ӵPoƸ,V-C2-/,1 ¾(v6?B/ e$ՄŲ0~Uæ1мfp\J عYRVPXݪyu.F5Aǒyg}l0w:H9Sf4>8qQa<sV_zi[-jgow}ϿOAZjxUgTy6SCbH n\{@W`[r#,*d .6+U K\I񘴻J-h:D8k z:k*xT2%MqB[ۛ_{Ż"n0X|&?>kFKֿ@ם<|er[/0AeKZ|iqZ} W% ]wU/- ^UtqnDf])粣׿?^NC7^-e+уe"9"z!t'GLK2:"/I-ț\Zé؊NhEMd}Q8mpZFѤ(1zTBKz,tV9X/5v G_.8{,9Ea==|l](^.vbEj@X .&b#H&nI#Bs.#ĥ4ӯc ToL9LƨCP `ib *D:.f@LԒ2FsH.`T=Dq=`I8*"óҟ3jۻ0)h8wͶg"J~#72WuȇfxSvG,7{Cl#f!fnPޛJQ>F#whK9DMoγyވn1@LPa`t()!snK7`fx9bڹM6,&%Z+bbĤ+;63i]rMm[U2SNxSf# 2Q2ܾb^)j)cs)2{)*^LԊYOQzfUM%<{ħ{vR2L Q)È4$rеg^4H4#B"4y XW| } C1\>&]K2 Eԡ^o}r-mˑ H./IxRJɬc#N]7P+4Ervpdۙp ⼸[Ƞ/ *$} *%{mSf;L<)ϟ̛ /b3[xreq'Y_0׎a9^Ii:XmK8O"#ElZ_ܘkN2o1I"K'=N?8zyx|qtA[h95ú[h4_۶/IfNj?2& 䁽Sv_n;5Ǯ[pv,l̐Ay`;7S7F¡-Ӯ0i3.>/4;#wcuMm`%)wt5aZU{%.RBᝁg<; *ػ|tg;[:zsXXK^{?y |עwtBkun7=G53ۿxi{ #o;X4[7;K^=[i0)=Ô;W3 Rfw0 Co[#GN07B(KX G$eUC1lLB9Z@]o 5$8,"SAbp%hC1L'n)1H"QPŔJs;MX_e+q^-3Z#w0[kBMF,vF;@m["8Z(ƼP^:xw\n}*uqACcxF!ߑ` 5PXKCA~yVt;6'tx&J%u)+e*WQ)H\CF#WΈ}OHl7% BdwE$,T*47 L &+YaH+52C[!P"$<'ʞY2 *1I}Ύ̙C#&QvHqEt24|Ә/.N(S-+{J2" ,|*tĻO[/t~h :( (DL0LYuJ%fvM\SE3ՏQ51n)w;4QI˽u'闿Z%;/ߟ{7^0doxaxEi#vfDI^[޽w{غoy}c5vYh:]xМΰXUOG[||MK%m{xTYզc,.RX ~+??ywm뿵ίz-z{p|C;^`4Fn@pLuQtiMw.ٰ,YXd=%'*"}i&s2ιLža' u|pm\H)Ǜ ̄tbqy5Mkߖ|=[wpnqV wI8/7_ +8XJ隇&)ۼokk-????????q0././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/helpers.py0000664000372000037200000000222500000000000016664 0ustar00travistravis00000000000000# Copyright 2016 Ian Cordasco # # 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 # # https://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. """Test functions useful across twine's tests.""" import contextlib import os @contextlib.contextmanager def set_env(**environ): """Set the process environment variables temporarily. >>> with set_env(PLUGINS_DIR=u'test/plugins'): ... "PLUGINS_DIR" in os.environ True >>> "PLUGINS_DIR" in os.environ False :param environ: Environment variables to set :type environ: dict[str, unicode] """ old_environ = dict(os.environ) os.environ.update(environ) try: yield finally: os.environ.clear() os.environ.update(old_environ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_auth.py0000664000372000037200000001151700000000000017226 0ustar00travistravis00000000000000import pytest from twine import ( auth, exceptions, utils, ) cred = auth.CredentialInput @pytest.fixture def config() -> utils.RepositoryConfig: return dict(repository='system') def test_get_password_keyring_overrides_prompt(monkeypatch, config): class MockKeyring: @staticmethod def get_password(system, user): return '{user}@{system} sekure pa55word'.format(**locals()) monkeypatch.setattr(auth, 'keyring', MockKeyring) pw = auth.Resolver(config, cred('user')).password assert pw == 'user@system sekure pa55word' def test_get_password_keyring_defers_to_prompt( monkeypatch, entered_password, config): class MockKeyring: @staticmethod def get_password(system, user): return monkeypatch.setattr(auth, 'keyring', MockKeyring) pw = auth.Resolver(config, cred('user')).password assert pw == 'entered pw' def test_no_password_defers_to_prompt(monkeypatch, entered_password, config): config.update(password=None) pw = auth.Resolver(config, cred('user')).password assert pw == 'entered pw' def test_empty_password_bypasses_prompt( monkeypatch, entered_password, config): config.update(password='') pw = auth.Resolver(config, cred('user')).password assert pw == '' def test_no_username_non_interactive_aborts(config): with pytest.raises(exceptions.NonInteractive): auth.Private(config, cred('user')).password def test_no_password_non_interactive_aborts(config): with pytest.raises(exceptions.NonInteractive): auth.Private(config, cred('user')).password def test_get_username_and_password_keyring_overrides_prompt( monkeypatch, config): class MockKeyring: @staticmethod def get_credential(system, user): return cred( 'real_user', 'real_user@{system} sekure pa55word'.format(**locals()) ) @staticmethod def get_password(system, user): cred = MockKeyring.get_credential(system, user) if user != cred.username: raise RuntimeError("unexpected username") return cred.password monkeypatch.setattr(auth, 'keyring', MockKeyring) res = auth.Resolver(config, cred()) assert res.username == 'real_user' assert res.password == 'real_user@system sekure pa55word' @pytest.fixture def keyring_missing_get_credentials(monkeypatch): """ Simulate keyring prior to 15.2 that does not have the 'get_credential' API. """ monkeypatch.delattr(auth.keyring, 'get_credential') @pytest.fixture def entered_username(monkeypatch): monkeypatch.setattr( auth, 'input', lambda prompt: 'entered user', raising=False) def test_get_username_keyring_missing_get_credentials_prompts( entered_username, keyring_missing_get_credentials, config): assert auth.Resolver(config, cred()).username == 'entered user' def test_get_username_keyring_missing_non_interactive_aborts( entered_username, keyring_missing_get_credentials, config): with pytest.raises(exceptions.NonInteractive): auth.Private(config, cred()).username def test_get_password_keyring_missing_non_interactive_aborts( entered_username, keyring_missing_get_credentials, config): with pytest.raises(exceptions.NonInteractive): auth.Private(config, cred('user')).password @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: @staticmethod def get_password(system, username): raise RuntimeError("fail!") monkeypatch.setattr(auth, 'keyring', FailKeyring()) @pytest.fixture 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_credential. """ class FailKeyring: @staticmethod def get_credential(system, username): raise RuntimeError("fail!") monkeypatch.setattr(auth, 'keyring', FailKeyring()) def test_get_username_runtime_error_suppressed( entered_username, keyring_no_backends_get_credential, recwarn, config): assert auth.Resolver(config, cred()).username == 'entered user' assert len(recwarn) == 1 warning = recwarn.pop(UserWarning) assert 'fail!' in str(warning) def test_get_password_runtime_error_suppressed( entered_password, keyring_no_backends, recwarn, config): assert auth.Resolver(config, cred('user')).password == 'entered pw' assert len(recwarn) == 1 warning = recwarn.pop(UserWarning) assert 'fail!' in str(warning) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_check.py0000664000372000037200000001120700000000000017336 0ustar00travistravis00000000000000# 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 # # https://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. import pretend from twine.commands import check class TestWarningStream: def setup(self): self.stream = check._WarningStream() self.stream.output = pretend.stub( write=pretend.call_recorder(lambda a: None), getvalue=lambda: "result", ) def test_write_match(self): self.stream.write(":2: (WARNING/2) Title underline too short.") assert self.stream.output.write.calls == [ pretend.call("line 2: Warning: Title underline too short.\n") ] def test_write_nomatch(self): self.stream.write("this does not match") assert self.stream.output.write.calls == [ pretend.call("this does not match") ] def test_str_representation(self): assert str(self.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() == "No files to check.\n" 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", 'description_content_type': 'text/markdown', }) output_stream = check.StringIO() warning_stream = "" monkeypatch.setattr(check, "_RENDERERS", {None: 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 dist/dist.tar.gz: PASSED\n" assert renderer.render.calls == [ pretend.call("blah", stream=warning_stream) ] def test_check_no_description(monkeypatch, capsys): package = pretend.stub(metadata_dictionary=lambda: { 'description': None, 'description_content_type': None, }) monkeypatch.setattr(check, "_find_dists", lambda a: ["dist/dist.tar.gz"]) monkeypatch.setattr( check, "PackageFile", pretend.stub(from_filename=lambda *a, **kw: package), ) # used to crash with `AttributeError` output_stream = check.StringIO() check.check("dist/*", output_stream=output_stream) assert output_stream.getvalue() == ( 'Checking dist/dist.tar.gz: PASSED, with warnings\n' ' warning: `long_description_content_type` missing. ' 'defaulting to `text/x-rst`.\n' ' warning: `long_description` missing.\n' ) 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", "description_content_type": 'text/markdown', }) output_stream = check.StringIO() warning_stream = "WARNING" monkeypatch.setattr(check, "_RENDERERS", {None: 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 dist/dist.tar.gz: FAILED\n" " `long_description` has syntax errors in markup and would not be " "rendered on PyPI.\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/*"])] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_cli.py0000664000372000037200000000204400000000000017027 0ustar00travistravis00000000000000# Copyright 2013 Donald Stufft # # 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 # # https://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. import pretend import pytest from twine import cli import twine.commands.upload def test_dispatch_to_subcommand(monkeypatch): replaced_main = pretend.call_recorder(lambda args: None) monkeypatch.setattr(twine.commands.upload, "main", replaced_main) cli.dispatch(["upload", "path/to/file"]) assert replaced_main.calls == [pretend.call(["path/to/file"])] def test_catches_enoent(): with pytest.raises(SystemExit): cli.dispatch(["non-existent-command"]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_commands.py0000664000372000037200000000237000000000000020063 0ustar00travistravis00000000000000import os import pytest from twine.commands import _find_dists, _group_wheel_files_first from twine import exceptions 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(exceptions.InvalidDistribution): _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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_integration.py0000664000372000037200000000220700000000000020604 0ustar00travistravis00000000000000from twine.cli import dispatch def test_devpi_upload(devpi_server, uploadable_dist): command = [ 'upload', '--repository-url', devpi_server.url, '--username', devpi_server.username, '--password', devpi_server.password, str(uploadable_dist), ] dispatch(command) twine_sampleproject_token = ( "pypi-AgENdGVzdC5weXBpLm9yZwIkNDgzYTFhMjEtMzEwYi00NT" "kzLTkwMzYtYzc1Zjg4NmFiMjllAAJEeyJwZXJtaXNzaW9ucyI6IH" "sicHJvamVjdHMiOiBbInR3aW5lLXNhbXBsZXByb2plY3QiXX0sIC" "J2ZXJzaW9uIjogMX0AAAYg2kBZ1tN8lj8dlmL3ScoVvr_pvQE0t" "6PKqigoYJKvw3M" ) def test_pypi_upload(sampleproject_dist): command = [ 'upload', '--repository-url', 'https://test.pypi.org/legacy/', '--username', '__token__', '--password', twine_sampleproject_token, str(sampleproject_dist), ] dispatch(command) def test_pypiserver_upload(pypiserver_instance, uploadable_dist): command = [ 'upload', '--repository-url', pypiserver_instance.url, '--username', 'any', '--password', 'any', str(uploadable_dist), ] dispatch(command) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_main.py0000664000372000037200000000157700000000000017216 0ustar00travistravis00000000000000# 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 # # https://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 from twine import exceptions import pretend def test_exception_handling(monkeypatch): replaced_dispatch = pretend.raiser( exceptions.InvalidConfiguration('foo') ) monkeypatch.setattr(dunder_main, 'dispatch', replaced_dispatch) assert dunder_main.main() == 'InvalidConfiguration: foo' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_package.py0000664000372000037200000002144300000000000017657 0ustar00travistravis00000000000000# Copyright 2015 Ian Cordasco # # 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 # # https://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 package, exceptions import pretend import pytest def test_sign_file(monkeypatch): replaced_check_call = pretend.call_recorder(lambda args: None) monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call) filename = 'tests/fixtures/deprecated-pypirc' pkg = package.PackageFile( filename=filename, comment=None, metadata=pretend.stub(name="deprecated-pypirc"), python_version=None, filetype=None ) try: pkg.sign('gpg2', None) except IOError: pass args = ('gpg2', '--detach-sign', '-a', filename) assert replaced_check_call.calls == [pretend.call(args)] def test_sign_file_with_identity(monkeypatch): replaced_check_call = pretend.call_recorder(lambda args: None) monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call) filename = 'tests/fixtures/deprecated-pypirc' pkg = package.PackageFile( filename=filename, comment=None, metadata=pretend.stub(name="deprecated-pypirc"), python_version=None, filetype=None ) try: pkg.sign('gpg', 'identity') except IOError: pass args = ('gpg', '--detach-sign', '--local-user', 'identity', '-a', filename) assert replaced_check_call.calls == [pretend.call(args)] def test_run_gpg_raises_exception_if_no_gpgs(monkeypatch): replaced_check_call = pretend.raiser(FileNotFoundError('not found')) monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call) gpg_args = ('gpg', '--detach-sign', '-a', 'pypircfile') with pytest.raises(exceptions.InvalidSigningExecutable) as err: package.PackageFile.run_gpg(gpg_args) assert 'executables not available' in err.value.args[0] def test_run_gpg_raises_exception_if_not_using_gpg(monkeypatch): replaced_check_call = pretend.raiser(FileNotFoundError('not found')) monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call) gpg_args = ('not_gpg', '--detach-sign', '-a', 'pypircfile') with pytest.raises(exceptions.InvalidSigningExecutable) as err: package.PackageFile.run_gpg(gpg_args) assert 'not_gpg executable not available' in err.value.args[0] def test_run_gpg_falls_back_to_gpg2(monkeypatch): def check_call(arg_list): if arg_list[0] == 'gpg': raise FileNotFoundError('gpg not found') replaced_check_call = pretend.call_recorder(check_call) monkeypatch.setattr(package.subprocess, 'check_call', replaced_check_call) gpg_args = ('gpg', '--detach-sign', '-a', 'pypircfile') package.PackageFile.run_gpg(gpg_args) gpg2_args = replaced_check_call.calls[1].args assert gpg2_args[0][0] == 'gpg2' def test_package_signed_name_is_correct(): filename = 'tests/fixtures/deprecated-pypirc' pkg = package.PackageFile( filename=filename, comment=None, metadata=pretend.stub(name="deprecated-pypirc"), python_version=None, filetype=None ) assert pkg.signed_basefilename == "deprecated-pypirc.asc" assert pkg.signed_filename == (filename + '.asc') @pytest.mark.parametrize('gpg_signature', [ (None), (pretend.stub()), ]) def test_metadata_dictionary(gpg_signature): meta = pretend.stub( name='whatever', version=pretend.stub(), metadata_version=pretend.stub(), summary=pretend.stub(), home_page=pretend.stub(), author=pretend.stub(), author_email=pretend.stub(), maintainer=pretend.stub(), maintainer_email=pretend.stub(), license=pretend.stub(), description=pretend.stub(), keywords=pretend.stub(), platforms=pretend.stub(), classifiers=pretend.stub(), download_url=pretend.stub(), supported_platforms=pretend.stub(), provides=pretend.stub(), requires=pretend.stub(), obsoletes=pretend.stub(), project_urls=pretend.stub(), provides_dist=pretend.stub(), obsoletes_dist=pretend.stub(), requires_dist=pretend.stub(), requires_external=pretend.stub(), requires_python=pretend.stub(), provides_extras=pretend.stub(), description_content_type=pretend.stub(), ) pkg = package.PackageFile( filename='tests/fixtures/twine-1.5.0-py2.py3-none-any.whl', comment=pretend.stub(), metadata=meta, python_version=pretend.stub(), filetype=pretend.stub(), ) pkg.gpg_signature = gpg_signature result = pkg.metadata_dictionary() # identify release assert result['name'] == pkg.safe_name assert result['version'] == meta.version # file content assert result['filetype'] == pkg.filetype assert result['pyversion'] == pkg.python_version # additional meta-data assert result['metadata_version'] == meta.metadata_version assert result['summary'] == meta.summary assert result['home_page'] == meta.home_page assert result['author'] == meta.author assert result['author_email'] == meta.author_email assert result['maintainer'] == meta.maintainer assert result['maintainer_email'] == meta.maintainer_email assert result['license'] == meta.license assert result['description'] == meta.description assert result['keywords'] == meta.keywords assert result['platform'] == meta.platforms assert result['classifiers'] == meta.classifiers assert result['download_url'] == meta.download_url assert result['supported_platform'] == meta.supported_platforms assert result['comment'] == pkg.comment # PEP 314 assert result['provides'] == meta.provides assert result['requires'] == meta.requires assert result['obsoletes'] == meta.obsoletes # Metadata 1.2 assert result['project_urls'] == meta.project_urls assert result['provides_dist'] == meta.provides_dist assert result['obsoletes_dist'] == meta.obsoletes_dist assert result['requires_dist'] == meta.requires_dist assert result['requires_external'] == meta.requires_external assert result['requires_python'] == meta.requires_python # Metadata 2.1 assert result['provides_extras'] == meta.provides_extras assert result['description_content_type'] == meta.description_content_type # GPG signature assert result.get('gpg_signature') == gpg_signature TWINE_1_5_0_WHEEL_HEXDIGEST = package.Hexdigest( '1919f967e990bee7413e2a4bc35fd5d1', 'd86b0f33f0c7df49e888b11c43b417da5520cbdbce9f20618b1494b600061e67', 'b657a4148d05bd0098c1d6d8cc4e14e766dbe93c3a5ab6723b969da27a87bac0', ) def test_hash_manager(): """Verify our HashManager works.""" filename = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl' hasher = package.HashManager(filename) hasher.hash() assert hasher.hexdigest() == TWINE_1_5_0_WHEEL_HEXDIGEST def test_fips_hash_manager(monkeypatch): """Verify the behaviour if hashlib is using FIPS mode.""" replaced_md5 = pretend.raiser(ValueError('fipsmode')) monkeypatch.setattr(package.hashlib, 'md5', replaced_md5) filename = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl' hasher = package.HashManager(filename) hasher.hash() hashes = TWINE_1_5_0_WHEEL_HEXDIGEST._replace(md5=None) assert hasher.hexdigest() == hashes def test_no_blake2_hash_manager(monkeypatch): """Verify the behaviour with missing blake2.""" monkeypatch.setattr(package, 'blake2b', None) filename = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl' hasher = package.HashManager(filename) hasher.hash() hashes = TWINE_1_5_0_WHEEL_HEXDIGEST._replace(blake2=None) assert hasher.hexdigest() == hashes def test_pkginfo_returns_no_metadata(monkeypatch): """ Fail gracefully if pkginfo can't interpret the metadata (possibly due to seeing a version number it doesn't support yet) and gives us back an 'empty' object with no metadata """ def EmptyDist(filename): return pretend.stub(name=None, version=None) monkeypatch.setattr(package, "DIST_TYPES", {"bdist_wheel": EmptyDist}) filename = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl' with pytest.raises(exceptions.InvalidDistribution) as err: package.PackageFile.from_filename(filename, comment=None) assert 'Invalid distribution metadata' in err.value.args[0] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_register.py0000664000372000037200000000174100000000000020107 0ustar00travistravis00000000000000from __future__ import unicode_literals import pytest import pretend from twine.commands import register from twine import exceptions # TODO: Copied from test_upload.py. Extract to helpers? WHEEL_FIXTURE = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl' def test_exception_for_redirect(make_settings): register_settings = make_settings(""" [pypi] repository: https://test.pypi.org/legacy username:foo password:bar """) stub_response = pretend.stub( is_redirect=True, status_code=301, headers={'location': 'https://test.pypi.org/legacy/'} ) stub_repository = pretend.stub( register=lambda package: stub_response, close=lambda: None ) register_settings.create_repository = lambda: stub_repository with pytest.raises(exceptions.RedirectDetected) as err: register.register(register_settings, WHEEL_FIXTURE) assert "https://test.pypi.org/legacy/" in err.value.args[0] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_repository.py0000664000372000037200000001532300000000000020503 0ustar00travistravis00000000000000# Copyright 2015 Ian Cordasco # # 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 # # https://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. import requests from twine import repository from twine.utils import DEFAULT_REPOSITORY, TEST_REPOSITORY import pretend import pytest from contextlib import contextmanager def test_gpg_signature_structure_is_preserved(): data = { 'gpg_signature': ('filename.asc', 'filecontent'), } tuples = repository.Repository._convert_data_to_list_of_tuples(data) assert tuples == [('gpg_signature', ('filename.asc', 'filecontent'))] def test_content_structure_is_preserved(): data = { 'content': ('filename', 'filecontent'), } tuples = repository.Repository._convert_data_to_list_of_tuples(data) assert tuples == [('content', ('filename', 'filecontent'))] def test_iterables_are_flattened(): data = { 'platform': ['UNKNOWN'], } tuples = repository.Repository._convert_data_to_list_of_tuples(data) assert tuples == [('platform', 'UNKNOWN')] data = { 'platform': ['UNKNOWN', 'ANOTHERPLATFORM'], } tuples = repository.Repository._convert_data_to_list_of_tuples(data) assert tuples == [('platform', 'UNKNOWN'), ('platform', 'ANOTHERPLATFORM')] def test_set_client_certificate(): repo = repository.Repository( repository_url=DEFAULT_REPOSITORY, username='username', password='password', ) assert repo.session.cert is None repo.set_client_certificate(('/path/to/cert', '/path/to/key')) assert repo.session.cert == ('/path/to/cert', '/path/to/key') def test_set_certificate_authority(): repo = repository.Repository( repository_url=DEFAULT_REPOSITORY, username='username', password='password', ) assert repo.session.verify is True repo.set_certificate_authority('/path/to/cert') assert repo.session.verify == '/path/to/cert' def test_make_user_agent_string(): repo = repository.Repository( repository_url=DEFAULT_REPOSITORY, username='username', password='password', ) assert 'User-Agent' in repo.session.headers user_agent = repo.session.headers['User-Agent'] assert 'twine/' in user_agent assert 'requests/' in user_agent 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=DEFAULT_REPOSITORY, 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=DEFAULT_REPOSITORY, 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 @pytest.mark.parametrize('disable_progress_bar', [ True, False ]) def test_disable_progress_bar_is_forwarded_to_tqdm(monkeypatch, tmpdir, disable_progress_bar): """Test whether the disable flag is passed to tqdm when the disable_progress_bar option is passed to the repository """ @contextmanager def progressbarstub(*args, **kwargs): assert "disable" in kwargs assert kwargs["disable"] == disable_progress_bar yield monkeypatch.setattr(repository, "ProgressBar", progressbarstub) repo = repository.Repository( repository_url=DEFAULT_REPOSITORY, username='username', password='password', disable_progress_bar=disable_progress_bar, ) repo.session = pretend.stub( post=lambda url, data, allow_redirects, headers: response_with( status_code=200) ) fakefile = tmpdir.join('fake.whl') fakefile.write('.') def dictfunc(): return {"name": "fake"} package = pretend.stub( safe_name='fake', metadata=pretend.stub(version='2.12.0'), basefilename="fake.whl", filename=str(fakefile), metadata_dictionary=dictfunc ) repo.upload(package) @pytest.mark.parametrize('package_meta,repository_url,release_urls', [ # Single package ( [('fake', '2.12.0')], DEFAULT_REPOSITORY, {'https://pypi.org/project/fake/2.12.0/'}, ), # Single package to testpypi ( [('fake', '2.12.0')], TEST_REPOSITORY, {'https://test.pypi.org/project/fake/2.12.0/'}, ), # Multiple packages (faking a wheel and an sdist) ( [('fake', '2.12.0'), ('fake', '2.12.0')], DEFAULT_REPOSITORY, {'https://pypi.org/project/fake/2.12.0/'}, ), # Multiple releases ( [('fake', '2.12.0'), ('fake', '2.12.1')], DEFAULT_REPOSITORY, { 'https://pypi.org/project/fake/2.12.0/', 'https://pypi.org/project/fake/2.12.1/', }, ), # Not pypi ( [('fake', '2.12.0')], 'http://devpi.example.com', set(), ), # No packages ( [], DEFAULT_REPOSITORY, set(), ), ]) def test_release_urls(package_meta, repository_url, release_urls): packages = [ pretend.stub( safe_name=name, metadata=pretend.stub(version=version), ) for name, version in package_meta ] repo = repository.Repository( repository_url=repository_url, username='username', password='password', ) assert repo.release_urls(packages) == release_urls ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_settings.py0000664000372000037200000000725300000000000020127 0ustar00travistravis00000000000000"""Tests for the Settings class and module.""" # Copyright 2018 Ian Stapleton Cordasco # # 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 # # https://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. import os.path import textwrap import argparse from twine import exceptions from twine import settings import pytest def test_settings_takes_no_positional_arguments(): """Verify that the Settings initialization is kw-only.""" with pytest.raises(TypeError): settings.Settings('a', 'b', 'c') def test_settings_transforms_config(tmpdir): """Verify that the settings object transforms the passed in options.""" pypirc = os.path.join(str(tmpdir), ".pypirc") with open(pypirc, "w") as fp: fp.write(textwrap.dedent(""" [pypi] repository: https://upload.pypi.org/legacy/ username:username password:password """)) s = settings.Settings(config_file=pypirc) assert (s.repository_config['repository'] == 'https://upload.pypi.org/legacy/') assert s.sign is False assert s.sign_with == 'gpg' assert s.identity is None assert s.username == 'username' assert s.password == 'password' assert s.cacert is None assert s.client_cert is None assert s.disable_progress_bar is False def test_identity_requires_sign(): """Verify that if a user passes identity, we require sign=True.""" with pytest.raises(exceptions.InvalidSigningConfiguration): settings.Settings(sign=False, identity='fakeid') def test_password_not_required_if_client_cert(entered_password): """Verify that if client_cert is provided then a password is not.""" test_client_cert = '/random/path' settings_obj = settings.Settings( username='fakeuser', client_cert=test_client_cert) assert not settings_obj.password assert settings_obj.client_cert == test_client_cert @pytest.mark.parametrize('client_cert', [None, ""]) def test_password_is_required_if_no_client_cert(client_cert, entered_password): """Verify that if client_cert is not provided then a password must be.""" settings_obj = settings.Settings( username='fakeuser', client_cert=client_cert) assert settings_obj.password == 'entered pw' def test_client_cert_is_set_and_password_not_if_both_given(entered_password): """Verify that if both password and client_cert are given they are set""" client_cert = '/random/path' settings_obj = settings.Settings( username='fakeuser', password='anything', client_cert=client_cert) assert not settings_obj.password assert settings_obj.client_cert == client_cert class TestArgumentParsing: @staticmethod def parse_args(args): parser = argparse.ArgumentParser() settings.Settings.register_argparse_arguments(parser) return parser.parse_args(args) def test_non_interactive_flag(self): args = self.parse_args(['--non-interactive']) assert args.non_interactive def test_non_interactive_environment(self, monkeypatch): monkeypatch.setenv("TWINE_NON_INTERACTIVE", "1") args = self.parse_args([]) assert args.non_interactive monkeypatch.setenv("TWINE_NON_INTERACTIVE", "0") args = self.parse_args([]) assert not args.non_interactive ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_upload.py0000664000372000037200000002255200000000000017552 0ustar00travistravis00000000000000# Copyright 2014 Ian Cordasco # # 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 # # https://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. import pretend import pytest from requests.exceptions import HTTPError from twine.commands import upload from twine import package, cli, exceptions import twine import helpers SDIST_FIXTURE = 'tests/fixtures/twine-1.5.0.tar.gz' WHEEL_FIXTURE = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl' RELEASE_URL = 'https://pypi.org/project/twine/1.5.0/' NEW_SDIST_FIXTURE = 'tests/fixtures/twine-1.6.5.tar.gz' NEW_WHEEL_FIXTURE = 'tests/fixtures/twine-1.6.5-py2.py3-none-any.whl' NEW_RELEASE_URL = 'https://pypi.org/project/twine/1.6.5/' def test_successful_upload(make_settings, capsys): upload_settings = make_settings() stub_response = pretend.stub( is_redirect=False, status_code=201, raise_for_status=lambda: None ) stub_repository = pretend.stub( upload=lambda package: stub_response, close=lambda: None, release_urls=lambda packages: {RELEASE_URL, NEW_RELEASE_URL} ) upload_settings.create_repository = lambda: stub_repository result = upload.upload(upload_settings, [ WHEEL_FIXTURE, SDIST_FIXTURE, NEW_SDIST_FIXTURE, NEW_WHEEL_FIXTURE ]) # A truthy result means the upload failed assert result is None captured = capsys.readouterr() assert captured.out.count(RELEASE_URL) == 1 assert captured.out.count(NEW_RELEASE_URL) == 1 @pytest.mark.parametrize('verbose', [False, True]) def test_exception_for_http_status(verbose, make_settings, capsys): upload_settings = make_settings() upload_settings.verbose = verbose stub_response = pretend.stub( is_redirect=False, status_code=403, text="Invalid or non-existent authentication information", raise_for_status=pretend.raiser(HTTPError) ) stub_repository = pretend.stub( upload=lambda package: stub_response, close=lambda: None, ) upload_settings.create_repository = lambda: stub_repository with pytest.raises(HTTPError): upload.upload(upload_settings, [WHEEL_FIXTURE]) captured = capsys.readouterr() assert RELEASE_URL not in captured.out if verbose: assert stub_response.text in captured.out assert '--verbose' not in captured.out else: assert stub_response.text not in captured.out assert '--verbose' in captured.out def test_get_config_old_format(make_settings, pypirc): try: make_settings(""" [server-login] username:foo password:bar """) except KeyError as err: assert all(text in err.args[0] for text in [ "'pypi'", "--repository-url", pypirc, "https://docs.python.org/", ]) def test_deprecated_repo(make_settings): with pytest.raises(exceptions.UploadToDeprecatedPyPIDetected) as err: upload_settings = make_settings(""" [pypi] repository: https://pypi.python.org/pypi/ username:foo password:bar """) upload.upload(upload_settings, [WHEEL_FIXTURE]) assert all(text in err.value.args[0] for text in [ "https://pypi.python.org/pypi/", "https://upload.pypi.org/legacy/", "https://test.pypi.org/legacy/", "https://packaging.python.org/", ]) def test_exception_for_redirect(make_settings): upload_settings = make_settings(""" [pypi] repository: https://test.pypi.org/legacy username:foo password:bar """) stub_response = pretend.stub( is_redirect=True, status_code=301, headers={'location': 'https://test.pypi.org/legacy/'} ) stub_repository = pretend.stub( upload=lambda package: stub_response, close=lambda: None ) upload_settings.create_repository = lambda: stub_repository with pytest.raises(exceptions.RedirectDetected) as err: upload.upload(upload_settings, [WHEEL_FIXTURE]) assert "https://test.pypi.org/legacy/" in err.value.args[0] def test_prints_skip_message_for_uploaded_package(make_settings, capsys): upload_settings = make_settings(skip_existing=True) stub_repository = pretend.stub( # Short-circuit the upload, so no need for a stub response package_is_uploaded=lambda package: True, release_urls=lambda packages: {}, close=lambda: None ) upload_settings.create_repository = lambda: stub_repository result = upload.upload(upload_settings, [WHEEL_FIXTURE]) # A truthy result means the upload failed assert result is None captured = capsys.readouterr() assert "Skipping twine-1.5.0-py2.py3-none-any.whl" in captured.out assert RELEASE_URL not in captured.out def test_prints_skip_message_for_response(make_settings, capsys): upload_settings = make_settings(skip_existing=True) stub_response = pretend.stub( is_redirect=False, status_code=409, ) stub_repository = pretend.stub( # Do the upload, triggering the error response package_is_uploaded=lambda package: False, release_urls=lambda packages: {}, upload=lambda package: stub_response, close=lambda: None ) upload_settings.create_repository = lambda: stub_repository result = upload.upload(upload_settings, [WHEEL_FIXTURE]) # A truthy result means the upload failed assert result is None captured = capsys.readouterr() assert "Skipping twine-1.5.0-py2.py3-none-any.whl" in captured.out assert RELEASE_URL not in captured.out @pytest.mark.parametrize('response_kwargs', [ pytest.param( dict(status_code=400, reason=( 'A file named "twine-1.5.0-py2.py3-none-any.whl" already ' 'exists for twine-1.5.0.' )), id='pypi' ), pytest.param( dict(status_code=400, reason=( 'Repository does not allow updating assets: pypi for url: ' 'http://www.foo.bar' )), id='nexus' ), pytest.param( dict(status_code=409, reason=( 'A file named "twine-1.5.0-py2.py3-none-any.whl" already ' 'exists for twine-1.5.0.' )), id='pypiserver' ), pytest.param( dict(status_code=403, text=( "Not enough permissions to overwrite artifact " "'pypi-local:twine/1.5.0/twine-1.5.0-py2.py3-none-any.whl'" "(user 'twine-deployer' needs DELETE permission)." )), id='artifactory_old' ), pytest.param( dict(status_code=403, text=( "Not enough permissions to delete/overwrite artifact " "'pypi-local:twine/1.5.0/twine-1.5.0-py2.py3-none-any.whl'" "(user 'twine-deployer' needs DELETE permission)." )), id='artifactory_new' ), ]) def test_skip_existing_skips_files_on_repository(response_kwargs): assert upload.skip_upload( response=pretend.stub(**response_kwargs), skip_existing=True, package=package.PackageFile.from_filename(WHEEL_FIXTURE, None), ) @pytest.mark.parametrize('response_kwargs', [ pytest.param( dict(status_code=400, reason='Invalid credentials'), id='wrong_reason' ), pytest.param( dict(status_code=404), id='wrong_code' ), ]) def test_skip_upload_doesnt_match(response_kwargs): assert not upload.skip_upload( response=pretend.stub(**response_kwargs), skip_existing=True, package=package.PackageFile.from_filename(WHEEL_FIXTURE, None) ) def test_skip_upload_respects_skip_existing(): assert not upload.skip_upload( response=pretend.stub(), skip_existing=False, package=package.PackageFile.from_filename(WHEEL_FIXTURE, None), ) def test_values_from_env(monkeypatch): def none_upload(*args, **settings_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_CERT": "/foo/bar.crt"} with helpers.set_env(**testenv): cli.dispatch(["upload", "path/to/file"]) upload_settings = replaced_upload.calls[0].args[0] assert "pypipassword" == upload_settings.password assert "pypiuser" == upload_settings.username assert "/foo/bar.crt" == upload_settings.cacert @pytest.mark.parametrize('repo_url', [ "https://upload.pypi.org/", "https://test.pypi.org/", "https://pypi.org/" ]) def test_check_status_code_for_wrong_repo_url(repo_url, make_settings): upload_settings = make_settings() # override defaults to use incorrect URL upload_settings.repository_config['repository'] = repo_url with pytest.raises(twine.exceptions.InvalidPyPIUploadURL): upload.upload(upload_settings, [ WHEEL_FIXTURE, SDIST_FIXTURE, NEW_SDIST_FIXTURE, NEW_WHEEL_FIXTURE ]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_utils.py0000664000372000037200000001474000000000000017426 0ustar00travistravis00000000000000# Copyright 2013 Donald Stufft # # 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 # # https://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. import os.path import textwrap import pytest import pretend from twine import exceptions, utils import helpers def test_get_config(tmpdir): pypirc = os.path.join(str(tmpdir), ".pypirc") with open(pypirc, "w") as fp: fp.write(textwrap.dedent(""" [distutils] index-servers = pypi [pypi] username = testuser password = testpassword """)) assert utils.get_config(pypirc) == { "pypi": { "repository": utils.DEFAULT_REPOSITORY, "username": "testuser", "password": "testpassword", }, } def test_get_config_no_distutils(tmpdir): """ Even if the user hasn't set PyPI has an index server in 'index-servers', default to uploading to PyPI. """ pypirc = os.path.join(str(tmpdir), ".pypirc") with open(pypirc, "w") as fp: fp.write(textwrap.dedent(""" [pypi] username = testuser password = testpassword """)) assert utils.get_config(pypirc) == { "pypi": { "repository": utils.DEFAULT_REPOSITORY, "username": "testuser", "password": "testpassword", }, "testpypi": { "repository": utils.TEST_REPOSITORY, "username": None, "password": None, }, } def test_get_config_no_section(tmpdir): pypirc = os.path.join(str(tmpdir), ".pypirc") with open(pypirc, "w") as fp: fp.write(textwrap.dedent(""" [distutils] index-servers = pypi foo [pypi] username = testuser password = testpassword """)) assert utils.get_config(pypirc) == { "pypi": { "repository": utils.DEFAULT_REPOSITORY, "username": "testuser", "password": "testpassword", }, } def test_get_config_override_pypi_url(tmpdir): pypirc = os.path.join(str(tmpdir), ".pypirc") with open(pypirc, "w") as fp: fp.write(textwrap.dedent(""" [pypi] repository = http://pypiproxy """)) assert utils.get_config(pypirc)['pypi']['repository'] == 'http://pypiproxy' def test_get_config_missing(tmpdir): pypirc = os.path.join(str(tmpdir), ".pypirc") assert utils.get_config(pypirc) == { "pypi": { "repository": utils.DEFAULT_REPOSITORY, "username": None, "password": None, }, "testpypi": { "repository": utils.TEST_REPOSITORY, "username": None, "password": None }, } def test_empty_userpass(tmpdir): """ Empty username and password may be supplied to suppress prompts. See #426. """ pypirc = os.path.join(str(tmpdir), ".pypirc") with open(pypirc, "w") as fp: fp.write(textwrap.dedent(""" [pypi] username= password= """)) config = utils.get_config(pypirc) pypi = config['pypi'] assert pypi['username'] == pypi['password'] == '' def test_get_repository_config_missing(tmpdir): pypirc = os.path.join(str(tmpdir), ".pypirc") repository_url = "https://notexisting.python.org/pypi" exp = { "repository": repository_url, "username": None, "password": None, } 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, "password": None, } assert utils.get_repository_from_config(pypirc, "pypi") == exp def test_get_config_deprecated_pypirc(): tests_dir = os.path.dirname(os.path.abspath(__file__)) deprecated_pypirc_path = os.path.join(tests_dir, 'fixtures', 'deprecated-pypirc') assert utils.get_config(deprecated_pypirc_path) == { "pypi": { "repository": utils.DEFAULT_REPOSITORY, "username": "testusername", "password": "testpassword", }, "testpypi": { "repository": utils.TEST_REPOSITORY, "username": "testusername", "password": "testpassword", }, } @pytest.mark.parametrize( ('cli_value', 'config', 'key', 'strategy', 'expected'), ( ('cli', {}, 'key', lambda: 'fallback', 'cli'), (None, {'key': 'value'}, 'key', lambda: 'fallback', 'value'), (None, {}, 'key', lambda: 'fallback', 'fallback'), ), ) def test_get_userpass_value(cli_value, config, key, strategy, expected): ret = utils.get_userpass_value(cli_value, config, key, strategy) assert ret == expected @pytest.mark.parametrize( ('env_name', 'default', 'environ', 'expected'), [ ('MY_PASSWORD', None, {}, None), ('MY_PASSWORD', None, {'MY_PASSWORD': 'foo'}, 'foo'), ('URL', 'https://example.org', {}, 'https://example.org'), ('URL', 'https://example.org', {'URL': 'https://pypi.org'}, 'https://pypi.org'), ], ) def test_default_to_environment_action(env_name, default, environ, expected): option_strings = ('-x', '--example') dest = 'example' with helpers.set_env(**environ): action = utils.EnvironmentDefault( env=env_name, default=default, option_strings=option_strings, dest=dest, ) assert action.env == env_name assert action.default == expected @pytest.mark.parametrize('repo_url', [ "https://pypi.python.org", "https://testpypi.python.org" ]) def test_check_status_code_for_deprecated_pypi_url(repo_url): response = pretend.stub( status_code=410, url=repo_url ) # value of Verbose doesn't matter for this check with pytest.raises(exceptions.UploadToDeprecatedPyPIDetected): utils.check_status_code(response, False) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tests/test_wheel.py0000664000372000037200000000252000000000000017363 0ustar00travistravis00000000000000# Copyright 2015 Ian Cordasco # # 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 # # https://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 wheel import pytest @pytest.fixture(params=[ 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl', 'tests/alt-fixtures/twine-1.5.0-py2.py3-none-any.whl' ]) def example_wheel(request): return wheel.Wheel(request.param) def test_version_parsing(example_wheel): assert example_wheel.py_version == 'py2.py3' def test_find_metadata_files(): names = [ 'package/lib/__init__.py', 'package/lib/version.py', 'package/METADATA.txt', 'package/METADATA.json', 'package/METADATA', ] expected = [ ['package', 'METADATA'], ['package', 'METADATA.json'], ['package', 'METADATA.txt'], ] candidates = wheel.Wheel.find_candidate_metadata_files(names) assert expected == candidates ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/tox.ini0000664000372000037200000000305000000000000015016 0ustar00travistravis00000000000000[tox] minversion = 2.4 envlist = lint,docs,py38,py37,py36 [testenv] deps = coverage pretend pytest jaraco.envs portend pytest-services munch commands = coverage run --source twine -m pytest {posargs:tests} coverage report -m [testenv:docs] 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 sdist 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 = pep517>=0.5 twine>=1.13 path.py passenv = TWINE_PASSWORD TWINE_REPOSITORY setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = python -c "import path; path.Path('dist').rmtree_p()" python -m pep517.build . python -m twine upload dist/* [testenv:lint-code-style] deps = flake8 commands = flake8 twine/ tests/ [testenv:lint-mypy] deps = mypy lxml commands = # TODO: Consider checking the tests after the source is fully typed mypy twine [testenv:lint] deps = {[testenv:lint-code-style]deps} {[testenv:lint-mypy]deps} commands = {[testenv:lint-code-style]commands} {[testenv:lint-mypy]commands} [testenv:devpi] deps = devpi-server devpi [testenv:pypiserver] deps = pypiserver ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8811786 twine-3.1.1/twine/0000775000372000037200000000000000000000000014633 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/__init__.py0000664000372000037200000000226500000000000016751 0ustar00travistravis00000000000000# 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. # You may obtain a copy of the License at # # https://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. __all__ = ( "__title__", "__summary__", "__uri__", "__version__", "__author__", "__email__", "__license__", "__copyright__", ) __copyright__ = "Copyright 2019 Donald Stufft and individual contributors" try: import importlib.metadata as importlib_metadata except ImportError: import importlib_metadata metadata = importlib_metadata.metadata('twine') __title__ = metadata['name'] __summary__ = metadata['summary'] __uri__ = metadata['home-page'] __version__ = metadata['version'] __author__ = metadata['author'] __email__ = metadata['author-email'] __license__ = metadata['license'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/__main__.py0000664000372000037200000000165700000000000016736 0ustar00travistravis00000000000000#!/usr/bin/env python3 # Copyright 2013 Donald Stufft # # 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 # # https://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. import sys import requests from twine import exceptions from twine.cli import dispatch def main(): try: return dispatch(sys.argv[1:]) except (exceptions.TwineException, requests.exceptions.HTTPError) as exc: return '{}: {}'.format(exc.__class__.__name__, exc.args[0]) if __name__ == "__main__": sys.exit(main()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/_installed.py0000664000372000037200000000440700000000000017330 0ustar00travistravis00000000000000# Copyright 2013 Tres Seaver # Copyright 2015 Ian Cordasco # This code was originally licensed under the Python Software Foudation # License by Tres Seaver. In order to facilitate finding the metadata of # installed packages, part of the most current implementation of the # pkginfo.Installed class is reproduced here with bug fixes from # https://bugs.launchpad.net/pkginfo/+bug/1437570. import glob import os import sys import warnings import pkginfo class Installed(pkginfo.Installed): def read(self): opj = os.path.join if self.package is not None: package = self.package.__package__ if package is None: package = self.package.__name__ egg_pattern = '%s*.egg-info' % package dist_pattern = '%s*.dist-info' % package file = getattr(self.package, '__file__', None) if file is not None: candidates = [] def _add_candidate(where): candidates.extend(glob.glob(where)) for entry in sys.path: if file.startswith(entry): _add_candidate(opj(entry, 'METADATA')) # egg? _add_candidate(opj(entry, 'EGG-INFO')) # egg? # dist-installed? _add_candidate(opj(entry, egg_pattern)) _add_candidate(opj(entry, dist_pattern)) dir, name = os.path.split(self.package.__file__) _add_candidate(opj(dir, egg_pattern)) _add_candidate(opj(dir, dist_pattern)) _add_candidate(opj(dir, '..', egg_pattern)) _add_candidate(opj(dir, '..', dist_pattern)) for candidate in candidates: if os.path.isdir(candidate): path = opj(candidate, 'PKG-INFO') if not os.path.exists(path): path = opj(candidate, 'METADATA') else: path = candidate if os.path.exists(path): with open(path) as f: return f.read() warnings.warn('No PKG-INFO or METADATA found for package: %s' % self.package_name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/auth.py0000664000372000037200000000503600000000000016152 0ustar00travistravis00000000000000import warnings import getpass import functools from typing import Optional, Callable import keyring from . import utils from . import exceptions class CredentialInput: def __init__(self, username: str = None, password: str = None): self.username = username self.password = password class Resolver: def __init__(self, config: utils.RepositoryConfig, input: CredentialInput): self.config = config self.input = input @classmethod def choose(cls, interactive): return cls if interactive else Private @property # type: ignore # https://github.com/python/mypy/issues/1362 @functools.lru_cache() def username(self) -> Optional[str]: return utils.get_userpass_value( self.input.username, self.config, key='username', prompt_strategy=self.username_from_keyring_or_prompt, ) @property # type: ignore # https://github.com/python/mypy/issues/1362 @functools.lru_cache() def password(self) -> Optional[str]: return utils.get_userpass_value( self.input.password, self.config, key='password', prompt_strategy=self.password_from_keyring_or_prompt, ) @property def system(self) -> Optional[str]: return self.config['repository'] def get_username_from_keyring(self) -> Optional[str]: try: creds = keyring.get_credential(self.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)) return None def get_password_from_keyring(self) -> Optional[str]: try: return keyring.get_password(self.system, self.username) except Exception as exc: warnings.warn(str(exc)) return None def username_from_keyring_or_prompt(self) -> str: return ( self.get_username_from_keyring() or self.prompt('username', input) ) def password_from_keyring_or_prompt(self) -> str: return ( self.get_password_from_keyring() or self.prompt('password', getpass.getpass) ) def prompt(self, what: str, how: Callable) -> str: return how(f"Enter your {what}: ") class Private(Resolver): def prompt(self, what: str, how: Optional[Callable] = None) -> str: raise exceptions.NonInteractive(f"Credential not found for {what}.") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/cli.py0000664000372000037200000000375200000000000015763 0ustar00travistravis00000000000000# Copyright 2013 Donald Stufft # # 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 # # https://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. import argparse import pkg_resources import setuptools import tqdm import requests import requests_toolbelt import pkginfo import twine from twine._installed import Installed def _registered_commands(group='twine.registered_commands'): registered_commands = pkg_resources.iter_entry_points(group=group) return {c.name: c for c in registered_commands} def list_dependencies_and_versions(): return [ ('pkginfo', Installed(pkginfo).version), ('requests', requests.__version__), ('setuptools', setuptools.__version__), ('requests-toolbelt', requests_toolbelt.__version__), ('tqdm', tqdm.__version__), ] def dep_versions(): return ', '.join( '{}: {}'.format(*dependency) for dependency in list_dependencies_and_versions() ) def dispatch(argv): registered_commands = _registered_commands() parser = argparse.ArgumentParser(prog="twine") parser.add_argument( "--version", action="version", version="%(prog)s version {} ({})".format( twine.__version__, dep_versions(), ), ) parser.add_argument( "command", choices=registered_commands.keys(), ) parser.add_argument( "args", help=argparse.SUPPRESS, nargs=argparse.REMAINDER, ) args = parser.parse_args(argv) main = registered_commands[args.command].load() return main(args.args) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8811786 twine-3.1.1/twine/commands/0000775000372000037200000000000000000000000016434 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/commands/__init__.py0000664000372000037200000000276700000000000020561 0ustar00travistravis00000000000000# Copyright 2013 Donald Stufft # # 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 # # https://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 typing import List import glob import os.path from twine import exceptions __all__: List[str] = [] def _group_wheel_files_first(files): if not any(fname for fname in files if fname.endswith(".whl")): # Return early if there's no wheel files return files files.sort(key=lambda x: -1 if x.endswith(".whl") else 0) return files def _find_dists(dists): uploads = [] for filename in dists: if os.path.exists(filename): uploads.append(filename) continue # The filename didn't exist so it may be a glob files = glob.glob(filename) # If nothing matches, files is [] if not files: raise exceptions.InvalidDistribution( "Cannot find file (or expand pattern): '%s'" % filename ) # Otherwise, files will be filenames that exist uploads.extend(files) return _group_wheel_files_first(uploads) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/commands/check.py0000664000372000037200000001156400000000000020072 0ustar00travistravis00000000000000# 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 # # https://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. import argparse import cgi import re import sys from io import StringIO import readme_renderer.rst from twine.commands import _find_dists from twine.package import PackageFile _RENDERERS = { None: readme_renderer.rst, # Default if description_content_type is None "text/plain": None, # Rendering cannot fail "text/x-rst": readme_renderer.rst, "text/markdown": None, # Rendering cannot fail } # Regular expression used to capture and reformat docutils warnings into # something that a human can understand. This is loosely borrowed from # Sphinx: https://github.com/sphinx-doc/sphinx/blob # /c35eb6fade7a3b4a6de4183d1dd4196f04a5edaf/sphinx/util/docutils.py#L199 _REPORT_RE = re.compile( r"^:(?P(?:\d+)?): " r"\((?PDEBUG|INFO|WARNING|ERROR|SEVERE)/(\d+)?\) " r"(?P.*)", re.DOTALL | re.MULTILINE, ) class _WarningStream: def __init__(self): self.output = StringIO() def write(self, text): matched = _REPORT_RE.search(text) if not matched: self.output.write(text) return self.output.write( "line {line}: {level_text}: {message}\n".format( level_text=matched.group("level").capitalize(), line=matched.group("line"), message=matched.group("message").rstrip("\r\n"), ) ) def __str__(self): return self.output.getvalue() def _check_file(filename, render_warning_stream): """Check given distribution.""" warnings = [] is_ok = True package = PackageFile.from_filename(filename, comment=None) metadata = package.metadata_dictionary() description = metadata["description"] description_content_type = metadata["description_content_type"] if description_content_type is None: warnings.append( '`long_description_content_type` missing. ' 'defaulting to `text/x-rst`.' ) description_content_type = 'text/x-rst' content_type, params = cgi.parse_header(description_content_type) renderer = _RENDERERS.get(content_type, _RENDERERS[None]) if description in {None, 'UNKNOWN\n\n\n'}: warnings.append('`long_description` missing.') elif renderer: rendering_result = renderer.render( description, stream=render_warning_stream, **params ) if rendering_result is None: is_ok = False return warnings, is_ok # TODO: Replace with textwrap.indent when Python 2 support is dropped def _indented(text, prefix): """Adds 'prefix' to all non-empty lines on 'text'.""" def prefixed_lines(): for line in text.splitlines(True): yield (prefix + line if line.strip() else line) return ''.join(prefixed_lines()) def check(dists, output_stream=sys.stdout): uploads = [i for i in _find_dists(dists) if not i.endswith(".asc")] if not uploads: # Return early, if there are no files to check. output_stream.write("No files to check.\n") return False failure = False for filename in uploads: output_stream.write("Checking %s: " % filename) render_warning_stream = _WarningStream() warnings, is_ok = _check_file(filename, render_warning_stream) # Print the status and/or error if not is_ok: failure = True output_stream.write("FAILED\n") error_text = ( "`long_description` has syntax errors in markup and " "would not be rendered on PyPI.\n" ) output_stream.write(_indented(error_text, " ")) output_stream.write(_indented(str(render_warning_stream), " ")) elif warnings: output_stream.write("PASSED, with warnings\n") else: output_stream.write("PASSED\n") # Print warnings after the status and/or error for message in warnings: output_stream.write(' warning: ' + message + '\n') return failure def main(args): parser = argparse.ArgumentParser(prog="twine check") parser.add_argument( "dists", nargs="+", metavar="dist", help="The distribution files to check, usually dist/*", ) args = parser.parse_args(args) # Call the check function with the arguments from the command line return check(args.dists) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/commands/register.py0000664000372000037200000000365700000000000020645 0ustar00travistravis00000000000000# Copyright 2015 Ian Cordasco # # 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 # # https://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. import argparse import os.path from twine.package import PackageFile from twine import exceptions from twine import settings def register(register_settings, package): repository_url = register_settings.repository_config['repository'] print(f"Registering package to {repository_url}") repository = register_settings.create_repository() if not os.path.exists(package): raise exceptions.PackageNotFound( f'"{package}" does not exist on the file system.' ) resp = repository.register( PackageFile.from_filename(package, register_settings.comment) ) repository.close() if resp.is_redirect: raise exceptions.RedirectDetected.from_args( repository_url, resp.headers["location"], ) resp.raise_for_status() def main(args): parser = argparse.ArgumentParser( prog="twine register", description="register operation is not required with PyPI.org", ) settings.Settings.register_argparse_arguments(parser) parser.add_argument( "package", metavar="package", help="File from which we read the package metadata.", ) args = parser.parse_args(args) register_settings = settings.Settings.from_argparse(args) # Call the register function with the args from the command line register(register_settings, args.package) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/commands/upload.py0000664000372000037200000001113600000000000020274 0ustar00travistravis00000000000000# Copyright 2013 Donald Stufft # # 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 # # https://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. import argparse import os.path from twine.commands import _find_dists from twine.package import PackageFile from twine import exceptions from twine import settings from twine import utils def skip_upload(response, skip_existing, package): if not skip_existing: return False status = response.status_code reason = getattr(response, 'reason', '').lower() text = getattr(response, 'text', '').lower() # NOTE(sigmavirus24): PyPI presently returns a 400 status code with the # error message in the reason attribute. Other implementations return a # 403 or 409 status code. return ( # pypiserver (https://pypi.org/project/pypiserver) status == 409 # PyPI / TestPyPI or (status == 400 and 'already exist' in reason) # Nexus Repository OSS (https://www.sonatype.com/nexus-repository-oss) or (status == 400 and 'updating asset' in reason) # Artifactory (https://jfrog.com/artifactory/) or (status == 403 and 'overwrite artifact' in text) ) def upload(upload_settings, dists): dists = _find_dists(dists) # Determine if the user has passed in pre-signed distributions signatures = {os.path.basename(d): d for d in dists if d.endswith(".asc")} uploads = [i for i in dists if not i.endswith(".asc")] upload_settings.check_repository_url() repository_url = upload_settings.repository_config['repository'] print(f"Uploading distributions to {repository_url}") repository = upload_settings.create_repository() uploaded_packages = [] for filename in uploads: package = PackageFile.from_filename(filename, upload_settings.comment) skip_message = ( " Skipping {} because it appears to already exist".format( package.basefilename) ) # Note: The skip_existing check *needs* to be first, because otherwise # we're going to generate extra HTTP requests against a hardcoded # URL for no reason. if (upload_settings.skip_existing and repository.package_is_uploaded(package)): print(skip_message) continue signed_name = package.signed_basefilename if signed_name in signatures: package.add_gpg_signature(signatures[signed_name], signed_name) elif upload_settings.sign: package.sign(upload_settings.sign_with, upload_settings.identity) resp = repository.upload(package) # Bug 92. If we get a redirect we should abort because something seems # funky. The behaviour is not well defined and redirects being issued # by PyPI should never happen in reality. This should catch malicious # redirects as well. if resp.is_redirect: raise exceptions.RedirectDetected.from_args( repository_url, resp.headers["location"], ) if skip_upload(resp, upload_settings.skip_existing, package): print(skip_message) continue utils.check_status_code(resp, upload_settings.verbose) uploaded_packages.append(package) release_urls = repository.release_urls(uploaded_packages) if release_urls: print('\nView at:') for url in release_urls: print(url) # Bug 28. Try to silence a ResourceWarning by clearing the connection # pool. repository.close() def main(args): parser = argparse.ArgumentParser(prog="twine upload") settings.Settings.register_argparse_arguments(parser) parser.add_argument( "dists", nargs="+", metavar="dist", help="The distribution files to upload to the repository " "(package index). Usually dist/* . May additionally contain " "a .asc file to include an existing signature with the " "file upload.", ) args = parser.parse_args(args) upload_settings = settings.Settings.from_argparse(args) # Call the upload function with the arguments from the command line return upload(upload_settings, args.dists) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/exceptions.py0000664000372000037200000000672200000000000017375 0ustar00travistravis00000000000000"""Module containing exceptions raised by twine.""" # Copyright 2015 Ian Stapleton Cordasco # # 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 # # https://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. class TwineException(Exception): """Base class for all exceptions raised by twine.""" pass class RedirectDetected(TwineException): """A redirect was detected that the user needs to resolve. In some cases, requests refuses to issue a new POST request after a redirect. In order to prevent a confusing user experience, we raise this exception to allow users to know the index they're uploading to is redirecting them. """ @classmethod def from_args(cls, repository_url, redirect_url): msg = "\n".join([ "{} attempted to redirect to {}." .format(repository_url, redirect_url), "If you trust these URLs, set {} as your repository URL." .format(redirect_url), "Aborting." ]) return cls(msg) class PackageNotFound(TwineException): """A package file was provided that could not be found on the file system. This is only used when attempting to register a package. """ pass class UploadToDeprecatedPyPIDetected(TwineException): """An upload attempt was detected to deprecated PyPI domains. The sites pypi.python.org and testpypi.python.org are deprecated. """ @classmethod def from_args(cls, target_url, default_url, test_url): """Return an UploadToDeprecatedPyPIDetected instance.""" return cls("You're trying to upload to the legacy PyPI site '{}'. " "Uploading to those sites is deprecated. \n " "The new sites are pypi.org and test.pypi.org. Try using " "{} (or {}) to upload your packages instead. " "These are the default URLs for Twine now. \n More at " "https://packaging.python.org/guides/migrating-to-pypi-org/" " .".format(target_url, default_url, test_url) ) class UnreachableRepositoryURLDetected(TwineException): """An upload attempt was detected to a URL without a protocol prefix. All repository URLs must have a protocol (e.g., ``https://``). """ pass class InvalidSigningConfiguration(TwineException): """Both the sign and identity parameters must be present.""" pass class InvalidSigningExecutable(TwineException): """Signing executable must be installed on system.""" pass class InvalidConfiguration(TwineException): """Raised when configuration is invalid.""" pass class InvalidDistribution(TwineException): """Raised when a distribution is invalid.""" pass class NonInteractive(TwineException): """Raised in non-interactive mode when credentials could not be found.""" pass class InvalidPyPIUploadURL(TwineException): """Repository configuration tries to use PyPI with an incorrect URL. For example, https://pypi.org instead of https://upload.pypi.org/legacy. """ pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/package.py0000664000372000037200000002174500000000000016611 0ustar00travistravis00000000000000# Copyright 2015 Ian Cordasco # # 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 # # https://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 typing import Dict, IO, Optional, Union, Sequence, Tuple import collections import hashlib import io import os import subprocess from hashlib import blake2b import pkginfo import pkg_resources from twine.wheel import Wheel from twine.wininst import WinInst from twine import exceptions DIST_TYPES = { "bdist_wheel": Wheel, "bdist_wininst": WinInst, "bdist_egg": pkginfo.BDist, "sdist": pkginfo.SDist, } DIST_EXTENSIONS = { ".whl": "bdist_wheel", ".exe": "bdist_wininst", ".egg": "bdist_egg", ".tar.bz2": "sdist", ".tar.gz": "sdist", ".zip": "sdist", } MetadataValue = Union[str, Sequence[str], Tuple[str, IO, str]] class PackageFile: def __init__( self, filename: str, comment: Optional[str], metadata: pkginfo.Distribution, python_version: Optional[str], filetype: Optional[str], ) -> None: self.filename = filename self.basefilename = os.path.basename(filename) self.comment = comment self.metadata = metadata self.python_version = python_version self.filetype = filetype self.safe_name = pkg_resources.safe_name(metadata.name) self.signed_filename = self.filename + '.asc' self.signed_basefilename = self.basefilename + '.asc' self.gpg_signature: Optional[Tuple[str, bytes]] = None hasher = HashManager(filename) hasher.hash() hexdigest = hasher.hexdigest() self.md5_digest = hexdigest.md5 self.sha2_digest = hexdigest.sha2 self.blake2_256_digest = hexdigest.blake2 @classmethod def from_filename(cls, filename: str, comment: None) -> 'PackageFile': # Extract the metadata from the package for ext, dtype in DIST_EXTENSIONS.items(): if filename.endswith(ext): meta = DIST_TYPES[dtype](filename) break else: raise exceptions.InvalidDistribution( "Unknown distribution format: '%s'" % os.path.basename(filename) ) # If pkginfo encounters a metadata version it doesn't support, it may # give us back empty metadata. At the very least, we should have a name # and version if not (meta.name and meta.version): raise exceptions.InvalidDistribution( "Invalid distribution metadata. Try upgrading twine if " "possible." ) py_version: Optional[str] if dtype == "bdist_egg": pkgd = pkg_resources.Distribution.from_filename(filename) py_version = pkgd.py_version elif dtype == "bdist_wheel": py_version = meta.py_version elif dtype == "bdist_wininst": py_version = meta.py_version else: py_version = None return cls(filename, comment, meta, py_version, dtype) def metadata_dictionary(self) -> Dict[str, MetadataValue]: meta = self.metadata data = { # identify release "name": self.safe_name, "version": meta.version, # file content "filetype": self.filetype, "pyversion": self.python_version, # additional meta-data "metadata_version": meta.metadata_version, "summary": meta.summary, "home_page": meta.home_page, "author": meta.author, "author_email": meta.author_email, "maintainer": meta.maintainer, "maintainer_email": meta.maintainer_email, "license": meta.license, "description": meta.description, "keywords": meta.keywords, "platform": meta.platforms, "classifiers": meta.classifiers, "download_url": meta.download_url, "supported_platform": meta.supported_platforms, "comment": self.comment, "md5_digest": self.md5_digest, "sha256_digest": self.sha2_digest, "blake2_256_digest": self.blake2_256_digest, # PEP 314 "provides": meta.provides, "requires": meta.requires, "obsoletes": meta.obsoletes, # Metadata 1.2 "project_urls": meta.project_urls, "provides_dist": meta.provides_dist, "obsoletes_dist": meta.obsoletes_dist, "requires_dist": meta.requires_dist, "requires_external": meta.requires_external, "requires_python": meta.requires_python, # Metadata 2.1 "provides_extras": meta.provides_extras, "description_content_type": meta.description_content_type, } if self.gpg_signature is not None: data['gpg_signature'] = self.gpg_signature return data def add_gpg_signature( self, signature_filepath: str, signature_filename: str ): if self.gpg_signature is not None: raise exceptions.InvalidDistribution( 'GPG Signature can only be added once' ) with open(signature_filepath, "rb") as gpg: self.gpg_signature = (signature_filename, gpg.read()) def sign(self, sign_with: str, identity: Optional[str]): print(f"Signing {self.basefilename}") gpg_args: Tuple[str, ...] = (sign_with, "--detach-sign") if identity: gpg_args += ("--local-user", identity) gpg_args += ("-a", self.filename) self.run_gpg(gpg_args) self.add_gpg_signature(self.signed_filename, self.signed_basefilename) @classmethod def run_gpg(cls, gpg_args): try: subprocess.check_call(gpg_args) return except FileNotFoundError: if gpg_args[0] != "gpg": raise exceptions.InvalidSigningExecutable( "{} executable not available.".format(gpg_args[0])) print("gpg executable not available. Attempting fallback to gpg2.") try: subprocess.check_call(("gpg2",) + gpg_args[1:]) except FileNotFoundError: print("gpg2 executable not available.") raise exceptions.InvalidSigningExecutable( "'gpg' or 'gpg2' executables not available. " "Try installing one of these or specifying an executable " "with the --sign-with flag." ) Hexdigest = collections.namedtuple('Hexdigest', ['md5', 'sha2', 'blake2']) class HashManager: """Manage our hashing objects for simplicity. This will also allow us to better test this logic. """ def __init__(self, filename: str) -> None: """Initialize our manager and hasher objects.""" self.filename = filename self._md5_hasher = None try: self._md5_hasher = hashlib.md5() except ValueError: # FIPs mode disables MD5 pass self._sha2_hasher = hashlib.sha256() self._blake_hasher = None if blake2b is not None: self._blake_hasher = blake2b(digest_size=256 // 8) def _md5_update(self, content: bytes) -> None: if self._md5_hasher is not None: self._md5_hasher.update(content) def _md5_hexdigest(self) -> Optional[str]: if self._md5_hasher is not None: return self._md5_hasher.hexdigest() return None def _sha2_update(self, content: bytes) -> None: if self._sha2_hasher is not None: self._sha2_hasher.update(content) def _sha2_hexdigest(self) -> Optional[str]: if self._sha2_hasher is not None: return self._sha2_hasher.hexdigest() return None def _blake_update(self, content: bytes) -> None: if self._blake_hasher is not None: self._blake_hasher.update(content) def _blake_hexdigest(self) -> Optional[str]: if self._blake_hasher is not None: return self._blake_hasher.hexdigest() return None def hash(self) -> None: """Hash the file contents.""" with open(self.filename, "rb") as fp: for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b''): self._md5_update(content) self._sha2_update(content) self._blake_update(content) def hexdigest(self) -> Hexdigest: """Return the hexdigest for the file.""" return Hexdigest( self._md5_hexdigest(), self._sha2_hexdigest(), self._blake_hexdigest(), ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/repository.py0000664000372000037200000002062500000000000017431 0ustar00travistravis00000000000000# Copyright 2015 Ian Cordasco # # 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 # # https://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 typing import Dict, List, Optional, Tuple import sys from tqdm import tqdm import requests from requests import codes from requests.adapters import HTTPAdapter from requests.models import Response from requests.packages.urllib3 import util from requests_toolbelt.multipart import ( MultipartEncoder, MultipartEncoderMonitor ) from requests_toolbelt.utils import user_agent from twine.package import PackageFile, MetadataValue import twine KEYWORDS_TO_NOT_FLATTEN = {"gpg_signature", "content"} LEGACY_PYPI = 'https://pypi.python.org/' LEGACY_TEST_PYPI = 'https://testpypi.python.org/' WAREHOUSE = 'https://upload.pypi.org/' OLD_WAREHOUSE = 'https://upload.pypi.io/' TEST_WAREHOUSE = 'https://test.pypi.org/' WAREHOUSE_WEB = 'https://pypi.org/' class ProgressBar(tqdm): def update_to(self, n): """Update the bar in the way compatible with requests-toolbelt. This is identical to tqdm.update, except ``n`` will be the current value - not the delta as tqdm expects. """ self.update(n - self.n) # will also do self.n = n class Repository: def __init__( self, repository_url: str, username: Optional[str], password: Optional[str], disable_progress_bar: bool = False, ) -> None: self.url = repository_url self.session = requests.session() # requests.Session.auth should be Union[None, Tuple[str, str], ...] # But username or password could be None # See TODO for utils.RepositoryConfig self.session.auth = ( (username or '', password or '') if username or password else None ) self.session.headers['User-Agent'] = self._make_user_agent_string() for scheme in ('http://', 'https://'): self.session.mount(scheme, self._make_adapter_with_retries()) self._releases_json_data: Dict[str, Dict] = {} self.disable_progress_bar = disable_progress_bar @staticmethod def _make_adapter_with_retries() -> HTTPAdapter: retry = util.Retry( connect=5, total=10, method_whitelist=['GET'], status_forcelist=[500, 501, 502, 503], ) return HTTPAdapter(max_retries=retry) @staticmethod def _make_user_agent_string() -> str: from twine import cli dependencies = cli.list_dependencies_and_versions() return user_agent.UserAgentBuilder( 'twine', twine.__version__, ).include_extras( dependencies ).include_implementation().build() def close(self): self.session.close() @staticmethod def _convert_data_to_list_of_tuples( data: Dict[str, MetadataValue] ) -> List[Tuple[str, MetadataValue]]: data_to_send = [] for key, value in data.items(): if (key in KEYWORDS_TO_NOT_FLATTEN or not isinstance(value, (list, tuple))): data_to_send.append((key, value)) else: for item in value: data_to_send.append((key, item)) return data_to_send def set_certificate_authority(self, cacert: Optional[str]) -> None: if cacert: self.session.verify = cacert def set_client_certificate(self, clientcert: Optional[str]) -> None: if clientcert: self.session.cert = clientcert def register(self, package): data = package.metadata_dictionary() data.update({ ":action": "submit", "protocol_version": "1", }) print(f"Registering {package.basefilename}") data_to_send = self._convert_data_to_list_of_tuples(data) encoder = MultipartEncoder(data_to_send) resp = self.session.post( self.url, data=encoder, allow_redirects=False, headers={'Content-Type': encoder.content_type}, ) # Bug 28. Try to silence a ResourceWarning by releasing the socket. resp.close() return resp def _upload(self, package: PackageFile) -> Response: data = package.metadata_dictionary() data.update({ # action ":action": "file_upload", "protocol_version": "1", }) data_to_send = self._convert_data_to_list_of_tuples(data) print(f"Uploading {package.basefilename}") with open(package.filename, "rb") as fp: data_to_send.append(( "content", (package.basefilename, fp, "application/octet-stream"), )) encoder = MultipartEncoder(data_to_send) with ProgressBar(total=encoder.len, unit='B', unit_scale=True, unit_divisor=1024, miniters=1, file=sys.stdout, disable=self.disable_progress_bar) as bar: monitor = MultipartEncoderMonitor( encoder, lambda monitor: bar.update_to(monitor.bytes_read) ) resp = self.session.post( self.url, data=monitor, allow_redirects=False, headers={'Content-Type': monitor.content_type}, ) return resp def upload( self, package: PackageFile, max_redirects: int = 5 ) -> Response: number_of_redirects = 0 while number_of_redirects < max_redirects: resp = self._upload(package) if resp.status_code == codes.OK: return resp if 500 <= resp.status_code < 600: number_of_redirects += 1 print('Received "{status_code}: {reason}" Package upload ' 'appears to have failed. Retry {retry} of 5'.format( status_code=resp.status_code, reason=resp.reason, retry=number_of_redirects, )) else: return resp return resp def package_is_uploaded( self, package: PackageFile, bypass_cache: bool = False ) -> bool: # NOTE(sigmavirus24): Not all indices are PyPI and pypi.io doesn't # have a similar interface for finding the package versions. if not self.url.startswith((LEGACY_PYPI, WAREHOUSE, OLD_WAREHOUSE)): return False safe_name = package.safe_name releases = None if not bypass_cache: releases = self._releases_json_data.get(safe_name) if releases is None: url = '{url}pypi/{package}/json'.format(package=safe_name, url=LEGACY_PYPI) headers = {'Accept': 'application/json'} response = self.session.get(url, headers=headers) if response.status_code == 200: releases = response.json()['releases'] else: releases = {} self._releases_json_data[safe_name] = releases packages = releases.get(package.metadata.version, []) for uploaded_package in packages: if uploaded_package['filename'] == package.basefilename: return True return False def release_urls(self, packages): if self.url.startswith(WAREHOUSE): url = WAREHOUSE_WEB elif self.url.startswith(TEST_WAREHOUSE): url = TEST_WAREHOUSE else: return set() return { '{}project/{}/{}/'.format( url, package.safe_name, package.metadata.version ) for package in packages } def verify_package_integrity(self, package: PackageFile): # TODO(sigmavirus24): Add a way for users to download the package and # check it's hash against what it has locally. pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/settings.py0000664000372000037200000002671700000000000017062 0ustar00travistravis00000000000000"""Module containing logic for handling settings.""" # Copyright 2018 Ian Stapleton Cordasco # # 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 # # https://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 typing import cast, Optional import argparse from twine import exceptions from twine import repository from twine import utils from twine import auth class Settings: """Object that manages the configuration for Twine. This object can only be instantiated with keyword arguments. For example, .. code-block:: python Settings(True, username='fakeusername') Will raise a :class:`TypeError`. Instead, you would want .. code-block:: python Settings(sign=True, username='fakeusername') """ def __init__( self, *, sign: bool = False, sign_with: Optional[str] = 'gpg', identity: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None, non_interactive: bool = False, comment: Optional[str] = None, config_file: str = '~/.pypirc', skip_existing: bool = False, cacert: Optional[str] = None, client_cert: Optional[str] = None, repository_name: str = 'pypi', repository_url: Optional[str] = None, verbose: bool = False, disable_progress_bar: bool = False, **ignored_kwargs ) -> None: """Initialize our settings instance. :param bool sign: Configure whether the package file should be signed. This defaults to ``False``. :param str sign_with: The name of the executable used to sign the package with. This defaults to ``gpg``. :param str identity: The GPG identity that should be used to sign the package file. :param str username: The username used to authenticate to the repository (package index). :param str password: The password used to authenticate to the repository (package index). :param bool non_interactive: Do not interactively prompt for username/password if the required credentials are missing. This defaults to ``False``. :param str comment: The comment to include with each distribution file. :param str config_file: The path to the configuration file to use. This defaults to ``~/.pypirc``. :param bool skip_existing: Specify whether twine should continue uploading files if one of them already exists. This primarily supports PyPI. Other package indexes may not be supported. This defaults to ``False``. :param str cacert: The path to the bundle of certificates used to verify the TLS connection to the package index. :param str client_cert: The path to the client certificate used to perform authentication to the index. This must be a single file that contains both the private key and the PEM-encoded certificate. :param str repository_name: The name of the repository (package index) to interact with. This should correspond to a section in the config file. :param str repository_url: The URL of the repository (package index) to interact with. This will override the settings inferred from ``repository_name``. :param bool verbose: Show verbose output. :param bool disable_progress_bar: Disable the progress bar. This defaults to ``False`` """ self.config_file = config_file self.comment = comment self.verbose = verbose self.disable_progress_bar = disable_progress_bar self.skip_existing = skip_existing self._handle_repository_options( repository_name=repository_name, repository_url=repository_url, ) self._handle_package_signing( sign=sign, sign_with=sign_with, identity=identity, ) # _handle_certificates relies on the parsed repository config self._handle_certificates(cacert, client_cert) self.auth = auth.Resolver.choose(not non_interactive)( self.repository_config, auth.CredentialInput(username, password), ) @property def username(self): return self.auth.username @property def password(self): return None if self.client_cert else self.auth.password @staticmethod def register_argparse_arguments(parser: argparse.ArgumentParser) -> None: """Register the arguments for argparse.""" parser.add_argument( "-r", "--repository", action=utils.EnvironmentDefault, env="TWINE_REPOSITORY", default="pypi", help="The repository (package index) 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( "--repository-url", action=utils.EnvironmentDefault, env="TWINE_REPOSITORY_URL", default=None, required=False, help="The repository (package index) URL to upload the package to." " This overrides --repository. " "(Can also be set via %(env)s environment variable.)" ) parser.add_argument( "-s", "--sign", action="store_true", default=False, help="Sign files to upload using GPG.", ) parser.add_argument( "--sign-with", default="gpg", help="GPG program used to sign uploads (default: %(default)s).", ) parser.add_argument( "-i", "--identity", help="GPG identity used to sign files.", ) parser.add_argument( "-u", "--username", action=utils.EnvironmentDefault, env="TWINE_USERNAME", required=False, help="The username to authenticate to the repository " "(package index) as. (Can also be set via " "%(env)s environment variable.)", ) parser.add_argument( "-p", "--password", action=utils.EnvironmentDefault, env="TWINE_PASSWORD", required=False, help="The password to authenticate to the repository " "(package index) with. (Can also be set via " "%(env)s environment variable.)", ) parser.add_argument( "--non-interactive", action=utils.EnvironmentFlag, env="TWINE_NON_INTERACTIVE", help="Do not interactively prompt for username/password if the " "required credentials are missing. (Can also be set via " "%(env)s environment variable.)" ) parser.add_argument( "-c", "--comment", help="The comment to include with the distribution file.", ) parser.add_argument( "--config-file", default="~/.pypirc", help="The .pypirc config file to use.", ) parser.add_argument( "--skip-existing", default=False, action="store_true", help="Continue uploading files if one already exists. (Only valid " "when uploading to PyPI. Other implementations may not " "support this.)", ) parser.add_argument( "--cert", action=utils.EnvironmentDefault, env="TWINE_CERT", default=None, required=False, metavar="path", 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 format.", ) parser.add_argument( "--verbose", default=False, required=False, action="store_true", help="Show verbose output." ) parser.add_argument( "--disable-progress-bar", default=False, required=False, action="store_true", help="Disable the progress bar." ) @classmethod def from_argparse(cls, args: argparse.Namespace) -> "Settings": """Generate the Settings from parsed arguments.""" settings = vars(args) settings['repository_name'] = settings.pop('repository') settings['cacert'] = settings.pop('cert') return cls(**settings) def _handle_package_signing( self, sign: bool, sign_with: Optional[str], identity: Optional[str] ) -> None: if not sign and identity: raise exceptions.InvalidSigningConfiguration( "sign must be given along with identity" ) self.sign = sign self.sign_with = sign_with self.identity = identity def _handle_repository_options( self, repository_name: str, repository_url: Optional[str] ) -> None: self.repository_config = utils.get_repository_from_config( self.config_file, repository_name, repository_url, ) self.repository_config['repository'] = utils.normalize_repository_url( cast(str, self.repository_config['repository']), ) def _handle_certificates( self, cacert: Optional[str], client_cert: Optional[str] ) -> None: self.cacert = utils.get_cacert(cacert, self.repository_config) self.client_cert = utils.get_clientcert( client_cert, self.repository_config, ) def check_repository_url(self) -> None: """Verify we are not using legacy PyPI. :raises: :class:`~twine.exceptions.UploadToDeprecatedPyPIDetected` """ repository_url = cast(str, self.repository_config['repository']) if repository_url.startswith((repository.LEGACY_PYPI, repository.LEGACY_TEST_PYPI)): raise exceptions.UploadToDeprecatedPyPIDetected.from_args( repository_url, utils.DEFAULT_REPOSITORY, utils.TEST_REPOSITORY ) def create_repository(self) -> repository.Repository: """Create a new repository for uploading.""" repo = repository.Repository( cast(str, self.repository_config['repository']), self.username, self.password, self.disable_progress_bar, ) repo.set_certificate_authority(self.cacert) repo.set_client_certificate(self.client_cert) return repo ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/utils.py0000664000372000037200000002133600000000000016352 0ustar00travistravis00000000000000# Copyright 2013 Donald Stufft # # 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 # # https://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 typing import Callable, DefaultDict, Dict, Optional import os import os.path import functools import argparse import collections import configparser from urllib.parse import urlparse, urlunparse import requests from twine import exceptions # Shim for input to allow testing. input_func = input DEFAULT_REPOSITORY = "https://upload.pypi.org/legacy/" TEST_REPOSITORY = "https://test.pypi.org/legacy/" # TODO: In general, it seems to be assumed that the values retrieved from # instances of this type aren't None, except for username and password. # Type annotations would be cleaner if this were Dict[str, str], but that # requires reworking the username/password handling, probably starting with # get_userpass_value. RepositoryConfig = Dict[str, Optional[str]] def get_config(path: str = "~/.pypirc") -> Dict[str, RepositoryConfig]: # even if the config file does not exist, set up the parser # variable to reduce the number of if/else statements parser = configparser.RawConfigParser() # this list will only be used if index-servers # is not defined in the config file index_servers = ["pypi", "testpypi"] # default configuration for each repository defaults: RepositoryConfig = {"username": None, "password": None} # Expand user strings in the path path = os.path.expanduser(path) # Parse the rc file if os.path.isfile(path): parser.read(path) # Get a list of index_servers from the config file # format: https://docs.python.org/3/distutils/packageindex.html#pypirc if parser.has_option("distutils", "index-servers"): index_servers = parser.get("distutils", "index-servers").split() for key in ["username", "password"]: if parser.has_option("server-login", key): defaults[key] = parser.get("server-login", key) config: DefaultDict[str, RepositoryConfig] = \ collections.defaultdict(lambda: defaults.copy()) # don't require users to manually configure URLs for these repositories config["pypi"]["repository"] = DEFAULT_REPOSITORY if "testpypi" in index_servers: config["testpypi"]["repository"] = TEST_REPOSITORY # optional configuration values for individual repositories for repository in index_servers: for key in [ "username", "repository", "password", "ca_cert", "client_cert", ]: if parser.has_option(repository, key): config[repository][key] = parser.get(repository, key) # convert the defaultdict to a regular dict at this point # to prevent surprising behavior later on return dict(config) def get_repository_from_config( config_file: str, repository: str, repository_url: Optional[str] = None ) -> RepositoryConfig: # Get our config from, if provided, command-line values for the # repository name and URL, or the .pypirc file if repository_url and "://" in repository_url: # prefer CLI `repository_url` over `repository` or .pypirc return { "repository": repository_url, "username": None, "password": None, } if repository_url and "://" not in repository_url: raise exceptions.UnreachableRepositoryURLDetected( "Repository URL {} has no protocol. Please add " "'https://'. \n".format(repository_url)) try: return get_config(config_file)[repository] except KeyError: msg = ( "Missing '{repo}' section from the configuration file\n" "or not a complete URL in --repository-url.\n" "Maybe you have a out-dated '{cfg}' format?\n" "more info: " "https://docs.python.org/distutils/packageindex.html#pypirc\n" ).format( repo=repository, cfg=config_file ) raise exceptions.InvalidConfiguration(msg) _HOSTNAMES = {"pypi.python.org", "testpypi.python.org", "upload.pypi.org", "test.pypi.org"} def normalize_repository_url(url: str) -> str: parsed = urlparse(url) if parsed.netloc in _HOSTNAMES: return urlunparse(("https",) + parsed[1:]) return urlunparse(parsed) def check_status_code(response: requests.Response, verbose: bool) -> None: """Generate a helpful message based on the response from the repository. Raise a custom exception for recognized errors. Otherwise, print the response content (based on the verbose option) before re-raising the HTTPError. """ if response.status_code == 410 and "pypi.python.org" in response.url: raise exceptions.UploadToDeprecatedPyPIDetected( f"It appears you're uploading to pypi.python.org (or " f"testpypi.python.org). You've received a 410 error response. " f"Uploading to those sites is deprecated. The new sites are " f"pypi.org and test.pypi.org. Try using {DEFAULT_REPOSITORY} (or " f"{TEST_REPOSITORY}) to upload your packages instead. These are " f"the default URLs for Twine now. More at " f"https://packaging.python.org/guides/migrating-to-pypi-org/.") elif response.status_code == 405 and "pypi.org" in response.url: raise exceptions.InvalidPyPIUploadURL( f"It appears you're trying to upload to pypi.org but have an " f"invalid URL. You probably want one of these two URLs: " f"{DEFAULT_REPOSITORY} or {TEST_REPOSITORY}. Check your " f"--repository-url value.") try: response.raise_for_status() except requests.HTTPError as err: if response.text: if verbose: print('Content received from server:\n{}'.format( response.text)) else: print('NOTE: Try --verbose to see response content.') raise err def get_userpass_value( cli_value: Optional[str], config: RepositoryConfig, key: str, prompt_strategy: Optional[Callable] = None ) -> Optional[str]: """Gets the username / password from config. Uses the following rules: 1. If it is specified on the cli (`cli_value`), use that. 2. If `config[key]` is specified, use that. 3. If `prompt_strategy`, prompt using `prompt_strategy`. 4. Otherwise return None :param cli_value: The value supplied from the command line or `None`. :type cli_value: unicode or `None` :param config: Config dictionary :type config: dict :param key: Key to find the config value. :type key: unicode :prompt_strategy: Argumentless function to return fallback value. :type prompt_strategy: function :returns: The value for the username / password :rtype: unicode """ if cli_value is not None: return cli_value elif config.get(key) is not None: return config[key] elif prompt_strategy: return prompt_strategy() else: return None get_cacert = functools.partial( get_userpass_value, key='ca_cert', ) get_clientcert = functools.partial( get_userpass_value, key='client_cert', ) class EnvironmentDefault(argparse.Action): """Get values from environment variable.""" def __init__( self, env: str, required: bool = True, default: Optional[str] = None, **kwargs ) -> None: default = os.environ.get(env, default) self.env = env if default: required = False super().__init__(default=default, required=required, **kwargs) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) class EnvironmentFlag(argparse.Action): """Set boolean flag from environment variable.""" def __init__( self, env: str, **kwargs ) -> None: default = self.bool_from_env(os.environ.get(env)) self.env = env super().__init__(default=default, nargs=0, **kwargs) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, True) @staticmethod def bool_from_env(val): """ Allow '0' and 'false' and 'no' to be False """ falsey = {'0', 'false', 'no'} return val and val.lower() not in falsey ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/wheel.py0000664000372000037200000000554300000000000016320 0ustar00travistravis00000000000000# Copyright 2013 Donald Stufft # # 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 # # https://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. import os import re import zipfile from io import StringIO from pkginfo import distribution from pkginfo.distribution import Distribution from twine import exceptions # Monkeypatch Metadata 2.0 support distribution.HEADER_ATTRS_2_0 = distribution.HEADER_ATTRS_1_2 distribution.HEADER_ATTRS.update({"2.0": distribution.HEADER_ATTRS_2_0}) wheel_file_re = re.compile( r"""^(?P(?P.+?)(-(?P\d.+?))?) ((-(?P\d.*?))?-(?P.+?)-(?P.+?)-(?P.+?) \.whl|\.dist-info)$""", re.VERBOSE) class Wheel(Distribution): def __init__(self, filename, metadata_version=None): self.filename = filename self.basefilename = os.path.basename(self.filename) self.metadata_version = metadata_version self.extractMetadata() @property def py_version(self): wheel_info = wheel_file_re.match(self.basefilename) return wheel_info.group("pyver") @staticmethod def find_candidate_metadata_files(names): """Filter files that may be METADATA files.""" tuples = [x.split('/') for x in names if 'METADATA' in x] return [x[1] for x in sorted([(len(x), x) for x in tuples])] def read(self): fqn = os.path.abspath(os.path.normpath(self.filename)) if not os.path.exists(fqn): raise exceptions.InvalidDistribution( 'No such file: %s' % fqn ) if fqn.endswith('.whl'): archive = zipfile.ZipFile(fqn) names = archive.namelist() def read_file(name): return archive.read(name) else: raise exceptions.InvalidDistribution( 'Not a known archive format: %s' % fqn ) try: for path in self.find_candidate_metadata_files(names): candidate = '/'.join(path) data = read_file(candidate) if b'Metadata-Version' in data: return data finally: archive.close() raise exceptions.InvalidDistribution( 'No METADATA in archive: %s' % fqn ) def parse(self, data): super().parse(data) fp = StringIO(distribution.must_decode(data)) msg = distribution.parse(fp) self.description = msg.get_payload() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873239.0 twine-3.1.1/twine/wininst.py0000664000372000037200000000323200000000000016700 0ustar00travistravis00000000000000import os import re import zipfile from pkginfo.distribution import Distribution from twine import exceptions wininst_file_re = re.compile(r".*py(?P\d+\.\d+)\.exe$") class WinInst(Distribution): def __init__(self, filename, metadata_version=None): self.filename = filename self.metadata_version = metadata_version self.extractMetadata() @property def py_version(self): m = wininst_file_re.match(self.filename) if m is None: return "any" else: return m.group("pyver") def read(self): fqn = os.path.abspath(os.path.normpath(self.filename)) if not os.path.exists(fqn): raise exceptions.InvalidDistribution( 'No such file: %s' % fqn ) if fqn.endswith('.exe'): archive = zipfile.ZipFile(fqn) names = archive.namelist() def read_file(name): return archive.read(name) else: raise exceptions.InvalidDistribution( 'Not a known archive format: %s' % fqn ) try: tuples = [x.split('/') for x in names if x.endswith(".egg-info") or x.endswith("PKG-INFO")] schwarz = sorted([(len(x), x) for x in tuples]) for path in [x[1] for x in schwarz]: candidate = '/'.join(path) data = read_file(candidate) if b'Metadata-Version' in data: return data finally: archive.close() raise exceptions.InvalidDistribution( 'No PKG-INFO/.egg-info in archive: %s' % fqn ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574873267.8811786 twine-3.1.1/twine.egg-info/0000775000372000037200000000000000000000000016325 5ustar00travistravis00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873267.0 twine-3.1.1/twine.egg-info/PKG-INFO0000664000372000037200000004446300000000000017435 0ustar00travistravis00000000000000Metadata-Version: 1.2 Name: twine Version: 3.1.1 Summary: Collection of utilities for publishing packages on PyPI Home-page: https://twine.readthedocs.io/ Author: Donald Stufft and individual contributors Author-email: donald@stufft.io License: UNKNOWN Project-URL: Packaging tutorial, https://packaging.python.org/tutorials/distributing-packages/ Project-URL: Travis CI, https://travis-ci.org/pypa/twine/ Project-URL: Twine documentation, https://twine.readthedocs.io/en/latest/ Project-URL: Twine source, https://github.com/pypa/twine/ Description: .. image:: https://img.shields.io/pypi/v/twine.svg :target: https://pypi.org/project/twine .. 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 .. image:: https://img.shields.io/travis/com/pypa/twine :target: https://travis-ci.org/pypa/twine .. image:: https://img.shields.io/codecov/c/github/pypa/twine :target: https://codecov.io/gh/pypa/twine twine ===== .. rtd-inclusion-marker-do-not-remove Twine is `a utility`_ for `publishing`_ Python packages on `PyPI`_. It provides build system independent uploads of source and binary `distribution artifacts `_ for both new and existing `projects`_. Why Should I Use This? ---------------------- The goal of ``twine`` is to improve PyPI interaction by improving security and testability. The biggest reason to use ``twine`` is that it securely authenticates you to `PyPI`_ over HTTPS using a verified connection, regardless of the underlying Python version. Meanwhile, ``python setup.py upload`` will only work correctly and securely if your build system, Python version, and underlying operating system are configured properly. Secondly, ``twine`` encourages you to build your distribution files. ``python setup.py upload`` only allows you to upload a package as a final step after building with ``distutils`` or ``setuptools``, within the same command invocation. This means that you cannot test the exact file you're going to upload to PyPI to ensure that it works before uploading it. Finally, ``twine`` allows you to pre-sign your files and pass the ``.asc`` files into the command line invocation (``twine upload myproject-1.0.1.tar.gz myproject-1.0.1.tar.gz.asc``). This enables you to be assured that you're typing your ``gpg`` passphrase into ``gpg`` itself and not anything else, since *you* will be the one directly executing ``gpg --detach-sign -a ``. Features -------- - Verified HTTPS connections - Uploading doesn't require executing ``setup.py`` - Uploading files that have already been created, allowing testing of distributions before release - Supports uploading any packaging format (including `wheels`_) Installation ------------ .. code-block:: console $ pip install twine Using Twine ----------- 1. Create some distributions in the normal way: .. code-block:: console $ python setup.py sdist bdist_wheel 2. Upload with ``twine`` to `Test PyPI`_ and verify things look right. Twine will automatically prompt for your username and password: .. code-block:: console $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* username: ... password: ... 3. Upload to `PyPI`_: .. code-block:: console $ twine upload dist/* 4. Done! More documentation on using ``twine`` to upload packages to PyPI is in the `Python Packaging User Guide`_. Keyring Support --------------- Instead of typing in your password every time you upload a distribution, Twine 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 `_. 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. For example, to set a username and password for PyPI: .. code-block:: console $ keyring set https://upload.pypi.org/legacy/ your-username # 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 appropriate password from the keyring. .. Note:: If you are using Linux in a headless environment (such as on a server) you'll need to do some additional steps to ensure that Keyring can store secrets securely. See `Using Keyring on headless systems`_. .. _`keyring`: https://pypi.org/project/keyring/ .. _`Using Keyring on headless systems`: https://keyring.readthedocs.io/en/latest/#using-keyring-on-headless-linux-systems Disabling Keyring ^^^^^^^^^^^^^^^^^ 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 discussion and background. Options ------- ``twine upload`` ^^^^^^^^^^^^^^^^ Uploads one or more distributions to a repository. .. code-block:: console $ twine upload -h usage: twine upload [-h] [-r REPOSITORY] [--repository-url REPOSITORY_URL] [-s] [--sign-with SIGN_WITH] [-i IDENTITY] [-u USERNAME] [-p PASSWORD] [-c COMMENT] [--config-file CONFIG_FILE] [--skip-existing] [--cert path] [--client-cert path] [--verbose] [--disable-progress-bar] dist [dist ...] positional arguments: dist The distribution files to upload to the repository (package index). Usually dist/* . May additionally contain a .asc file to include an existing signature with the file upload. optional arguments: -h, --help show this help message and exit -r REPOSITORY, --repository REPOSITORY The repository (package index) to upload the package to. Should be a section in the config file (default: pypi). (Can also be set via TWINE_REPOSITORY environment variable.) --repository-url REPOSITORY_URL The repository (package index) URL to upload the package to. This overrides --repository. (Can also be set via TWINE_REPOSITORY_URL environment variable.) -s, --sign Sign files to upload using GPG. --sign-with SIGN_WITH GPG program used to sign uploads (default: gpg). -i IDENTITY, --identity IDENTITY GPG identity used to sign files. -u USERNAME, --username USERNAME The username to authenticate to the repository (package index) as. (Can also be set via TWINE_USERNAME environment variable.) -p PASSWORD, --password PASSWORD The password to authenticate to the repository (package index) with. (Can also be set via TWINE_PASSWORD environment variable.) --non-interactive Do not interactively prompt for username/password if the required credentials are missing. (Can also be set via TWINE_NON_INTERACTIVE environment variable.) -c COMMENT, --comment COMMENT The comment to include with the distribution file. --config-file CONFIG_FILE The .pypirc config file to use. --skip-existing Continue uploading files if one already exists. (Only valid when uploading to PyPI. Other implementations may not support this.) --cert path Path to alternate CA bundle (can also be set via TWINE_CERT environment variable). --client-cert path Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. --verbose Show verbose output. --disable-progress-bar Disable the progress bar. ``twine check`` ^^^^^^^^^^^^^^^ Checks whether your distribution's 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**: 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: .. code-block:: console $ twine register -h usage: twine register [-h] -r REPOSITORY [--repository-url REPOSITORY_URL] [-u USERNAME] [-p PASSWORD] [-c COMMENT] [--config-file CONFIG_FILE] [--cert path] [--client-cert path] package positional arguments: package File from which we read the package metadata. optional arguments: -h, --help show this help message and exit -r REPOSITORY, --repository REPOSITORY The repository (package index) to register the package to. Should be a section in the config file. (Can also be set via TWINE_REPOSITORY environment variable.) Initial package registration no longer necessary on pypi.org: https://packaging.python.org/guides/migrating-to-pypi- org/ --repository-url REPOSITORY_URL The repository (package index) URL to register the package to. This overrides --repository. (Can also be set via TWINE_REPOSITORY_URL environment variable.) -u USERNAME, --username USERNAME The username to authenticate to the repository (package index) as. (Can also be set via TWINE_USERNAME environment variable.) -p PASSWORD, --password PASSWORD The password to authenticate to the repository (package index) with. (Can also be set via TWINE_PASSWORD environment variable.) --non-interactive Do not interactively prompt for username/password if the required credentials are missing. (Can also be set via TWINE_NON_INTERACTIVE environment variable.) -c COMMENT, --comment COMMENT The comment to include with the distribution file. --config-file CONFIG_FILE The .pypirc config file to use. --cert path Path to alternate CA bundle (can also be set via TWINE_CERT environment variable). --client-cert path Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. Environment Variables ^^^^^^^^^^^^^^^^^^^^^ Twine also supports configuration via environment variables. Options passed on the command line will take precedence over options set via environment variables. Definition via environment variable is helpful in environments where it is not convenient to create a `.pypirc` file, such as a CI/build server, for example. * ``TWINE_USERNAME`` - the username to use for authentication to the repository. * ``TWINE_PASSWORD`` - the password to use for authentication to the repository. * ``TWINE_REPOSITORY`` - the repository configuration, either defined as a section in `.pypirc` or provided as a full URL. * ``TWINE_REPOSITORY_URL`` - the repository URL to use. * ``TWINE_CERT`` - custom CA certificate to use for repositories with self-signed or untrusted certificates. * ``TWINE_NON_INTERACTIVE`` - Do not interactively prompt for username/password if the required credentials are missing. Resources --------- * `IRC `_ (``#pypa`` - irc.freenode.net) * `GitHub repository `_ * User and developer `documentation`_ * `Python Packaging User Guide`_ Contributing ------------ See our `developer documentation`_ for how to get started, an architectural overview, and our future development plans. Code of Conduct --------------- Everyone interacting in the ``twine`` project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_. .. _`a utility`: https://pypi.org/project/twine/ .. _`publishing`: https://packaging.python.org/tutorials/distributing-packages/ .. _`PyPI`: https://pypi.org .. _`Test PyPI`: https://packaging.python.org/guides/using-testpypi/ .. _`Python Packaging User Guide`: https://packaging.python.org/tutorials/distributing-packages/ .. _`documentation`: https://twine.readthedocs.io/ .. _`developer documentation`: https://twine.readthedocs.io/en/latest/contributing.html .. _`projects`: https://packaging.python.org/glossary/#term-project .. _`distributions`: https://packaging.python.org/glossary/#term-distribution-package .. _`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 Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Natural Language :: English Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: BSD Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873267.0 twine-3.1.1/twine.egg-info/SOURCES.txt0000664000372000037200000000250400000000000020212 0ustar00travistravis00000000000000.codecov.yml .coveragerc .gitignore .travis.yml AUTHORS LICENSE README.rst mypy.ini pyproject.toml pytest.ini setup.cfg setup.py tox.ini .github/ISSUE_TEMPLATE.md docs/Makefile docs/changelog.rst docs/conf.py docs/contributing.rst docs/index.rst docs/make.bat docs/requirements.txt docs/_static/.empty tests/conftest.py tests/helpers.py tests/test_auth.py tests/test_check.py tests/test_cli.py tests/test_commands.py tests/test_integration.py tests/test_main.py tests/test_package.py tests/test_register.py tests/test_repository.py tests/test_settings.py tests/test_upload.py tests/test_utils.py tests/test_wheel.py tests/alt-fixtures/twine-1.5.0-py2.py3-none-any.whl tests/fixtures/deprecated-pypirc tests/fixtures/twine-1.5.0-py2.py3-none-any.whl tests/fixtures/twine-1.5.0.tar.gz tests/fixtures/twine-1.6.5-py2.py3-none-any.whl tests/fixtures/twine-1.6.5.tar.gz twine/__init__.py twine/__main__.py twine/_installed.py twine/auth.py twine/cli.py twine/exceptions.py twine/package.py twine/repository.py twine/settings.py twine/utils.py twine/wheel.py twine/wininst.py 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/requires.txt twine.egg-info/top_level.txt twine/commands/__init__.py twine/commands/check.py twine/commands/register.py twine/commands/upload.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873267.0 twine-3.1.1/twine.egg-info/dependency_links.txt0000664000372000037200000000000100000000000022373 0ustar00travistravis00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873267.0 twine-3.1.1/twine.egg-info/entry_points.txt0000664000372000037200000000027200000000000021624 0ustar00travistravis00000000000000[console_scripts] twine = twine.__main__:main [twine.registered_commands] check = twine.commands.check:main register = twine.commands.register:main upload = twine.commands.upload:main ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873267.0 twine-3.1.1/twine.egg-info/requires.txt0000664000372000037200000000025600000000000020730 0ustar00travistravis00000000000000pkginfo>=1.4.2 readme_renderer>=21.0 requests>=2.20 requests-toolbelt!=0.9.0,>=0.8.0 setuptools>=0.7.0 tqdm>=4.14 keyring>=15.1 [:python_version < "3.8"] importlib_metadata ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1574873267.0 twine-3.1.1/twine.egg-info/top_level.txt0000664000372000037200000000000600000000000021053 0ustar00travistravis00000000000000twine