Before you upload your package to the live PyPI index, it is a critical best practice to first upload it to TestPyPI. This is a separate, isolated testing environment that mirrors the real PyPI. Using it allows you to verify that your package builds correctly, that your pyproject.toml metadata is complete and valid, and that the installation process works as intended, all without polluting the official package index with test releases or risking a broken release for your users. Once you are confident in the TestPyPI release, you can then upload the very same distribution files to the official PyPI.

Generating Distribution Archives

The first step is to build your package into distribution archives. Modern Python packaging uses build as the recommended tool to create both a source distribution (sdist) and a wheel distribution (wheel). The source distribution is a fallback for environments where a wheel cannot be used, while the wheel is a pre-built archive that allows for much faster installations. Creating both ensures maximum compatibility across different user environments and operating systems.

# Install the build package if you haven't already
python -m pip install --upgrade build

# Navigate to your package directory (where pyproject.toml is)
cd /path/to/your/package

# Run the build module
python -m build

This command will create a dist/ directory containing two files: a .tar.gz source archive and a .whl wheel file. The names will be derived from the name and version fields in your pyproject.toml.

Uploading to TestPyPI with twine

twine is the officially recommended tool for uploading packages to PyPI. It securely authenticates and uploads your pre-built distribution files. You must first register an account on TestPyPI. Do not use your main PyPI password; create a separate one for testing.

# Install twine
python -m pip install --upgrade twine

# Upload all files in the dist/ directory to TestPyPI
python -m twine upload --repository testpypi dist/*

You will be prompted for your username and password. For security, it’s highly advisable to use an API token instead of a password. You can create a token on your TestPyPI account settings. When using a token, set your username to __token__ and the password/prompt to the token value itself (including the pypi- prefix).

# Using an API token (safer and recommended)
python -m twine upload --repository testpypi -u __token__ -p pypi-your-very-long-api-token-here dist/*

After a successful upload, twine will provide the URLs for your package on TestPyPI. You should immediately test the installation from this index to catch any issues.

# Install your package from TestPyPI in a fresh virtual environment
python -m pip install --index-url https://test.pypi.org/simple/ --no-deps your-package-name

The --no-deps flag is crucial here; TestPyPI does not have the vast majority of packages that the real index does. If your package has dependencies, this command will fail because they cannot be found. This flag lets you test if your specific package can be installed correctly. You would then manually install its dependencies from PyPI proper.

Uploading to PyPI

Once you have thoroughly tested your package from TestPyPI, the process for uploading to the real PyPI is identical, except you omit the --repository testpypi flag. You must have an account on PyPI and will again want to use an API token for authentication.

# Upload to the real PyPI
python -m twine upload -u __token__ -p pypi-your-real-pypi-api-token-here dist/*

Crucial Pitfall: PyPI and TestPyPI are separate indices. A package name on TestPyPI does not reserve that name on PyPI. Furthermore, once a version number is uploaded to PyPI, it is immutable and cannot be ever reused or overwritten. If you make a critical mistake in a release (e.g., version 1.0.0 contains a major bug), you cannot re-upload a fixed version as 1.0.0. You must release a new version (e.g., 1.0.1). This immutability guarantees that installations are reproducible and cannot be secretly changed by a maintainer.

Best Practices and Security

Always use API tokens instead of passwords. Tokens are scoped to a single project and can be revoked easily if compromised, whereas a leaked password gives access to all your projects. Never hardcode tokens or passwords into your scripts. For automation, use environment variables or secure credential stores.

# Using environment variables for security (in your terminal)
export TWINE_USERNAME=__token__
export TWINE_PASSWORD=pypi-your-long-token

# Then simply run (twine will read the environment variables)
python -m twine upload dist/*

Finally, consider automating this process within a CI/CD pipeline (like GitHub Actions). This minimizes human error, allows for consistent testing before release, and keeps your API tokens secure within the platform’s secret storage, never touching your local machine.