- Python 3.9+
- A Test PyPI API token (for test uploads) or PyPI API token (for production), from the account settings on test.pypi.org or pypi.org.
- With API tokens, the username is always
__token__and the password is the token string (starts withpypi-).
From the repository root:
python -m pip install --upgrade pip build twine
python -m buildThis creates dist/*.whl and dist/*.tar.gz.
Use TWINE_USERNAME and TWINE_PASSWORD (same pattern as export TWINE_PASSWORD=... for non-interactive uploads):
export TWINE_USERNAME=__token__
export TWINE_PASSWORD=pypi-your-testpypi-api-token-here
twine upload --repository-url https://test.pypi.org/legacy/ dist/*Replace pypi-your-testpypi-api-token-here with your real Test PyPI token. Do not commit the token or paste it into the repo.
One-shot (single line, shell):
TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-your-token-here twine upload --repository-url https://test.pypi.org/legacy/ dist/*export TWINE_USERNAME=__token__
export TWINE_PASSWORD=pypi-your-pypi-api-token-here
twine upload dist/*- Version: Each upload must use a new version in
pyproject.toml/setup.pyif that version was already published. - GitHub Actions: The workflow uses the
TEST_PYPI_API_TOKEN/ PyPI secrets instead of localexport; behavior is equivalent to token-based upload.