twine
Twine is a utility for publishing Python packages to PyPI and other repositories. It provides build system independent uploads of source and binary distribution artifacts, enhancing security and testability compared to older methods. The current version is 6.2.0, released on September 4, 2025, and it is actively maintained with frequent updates.
Warnings
- breaking The `python setup.py upload` command is deprecated and insecure. Twine is the official, secure, and recommended tool for uploading packages to PyPI.
- breaking Since Twine 5.0.0, only `__token__` is supported as a username when uploading to PyPI and TestPyPI. Supplying any other username will cause the upload to fail.
- breaking Twine 6.0.0 changed the default username behavior for PyPI/TestPyPI. It now defaults to `__token__` and no longer overrides a username configured via environment variables or command line if that username is *not* `__token__`. Workflows that implicitly relied on Twine overriding a non-`__token__` username will now fail.
- deprecated The `--skip-existing` flag is no longer supported for non-PyPI upload targets (e.g., custom package indexes). It also no longer submits `md5_digest` field as it's deprecated on PyPI.
- gotcha `twine check dist/*` may fail if your `README` file is not named exactly `README` and you are using the dynamic `readme` option in `pyproject.toml`.
- gotcha Using `twine upload dist/*` can inadvertently upload older or incorrect distribution files if your `dist/` directory contains multiple versions or unwanted artifacts.
Install
-
pip install twine
Imports
- twine (CLI)
twine upload dist/*
Quickstart
# 1. Ensure your distribution files (wheels, sdists) are built:
# (Use `python -m build` as a modern alternative to `python setup.py sdist bdist_wheel`)
python -m build
# 2. (Optional but recommended) Check your package metadata and README rendering:
twine check dist/*
# 3. (Optional but recommended) Upload to TestPyPI first to verify:
# Set TWINE_USERNAME and TWINE_PASSWORD environment variables with your TestPyPI API token
# For example, in bash:
# export TWINE_USERNAME="__token__"
# export TWINE_PASSWORD="pypi-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
# Or, you will be prompted for credentials.
# Replace 'your_test_pypi_api_token' with your actual TestPyPI API token
import os
os.environ['TWINE_USERNAME'] = os.environ.get('TEST_PYPI_USERNAME', '__token__')
os.environ['TWINE_PASSWORD'] = os.environ.get('TEST_PYPI_PASSWORD', 'pypi-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
print("Uploading to TestPyPI...")
# This command will be run via subprocess in a real application
# For quickstart, it implies running directly in shell:
# !twine upload --repository testpypi dist/*
# In a Python context, you'd use subprocess.run()
# 4. Upload to PyPI (after successful TestPyPI verification):
# Set TWINE_USERNAME and TWINE_PASSWORD environment variables with your PyPI API token
# For example, in bash:
# export TWINE_USERNAME="__token__"
# export TWINE_PASSWORD="pypi-YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
# Or, you will be prompted for credentials.
# Replace 'your_pypi_api_token' with your actual PyPI API token
os.environ['TWINE_USERNAME'] = os.environ.get('PYPI_USERNAME', '__token__')
os.environ['TWINE_PASSWORD'] = os.environ.get('PYPI_PASSWORD', 'pypi-YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY')
print("Uploading to PyPI...")
# !twine upload dist/*