Skip to content

Publishing Your First Python Package to PyPI

This tutorial guides you through publishing a Python package to the Python Package Index (PyPIThe Python Package Index, the public repository where Python packages are published and downloaded from. "pip install requests" fetches requests from PyPI. Learn more → ) using uv. You’ll learn the essential steps of building and uploading a package that others can install with pip or uv.

Since this is your first package, we’ll start by uploading to TestPyPI, a “separate instance of the Python Package Index that allows you to try distribution tools and processes without affecting the real index.”

Prerequisites

Setting Up Your Project

Create a new project with a name that is not currently taken on Test PyPI. You might try something like your name followed by a random number, e.g. timhopper2456543. Browse the project’s URL at https://test.pypi.org/project/<PACKAGE_NAME>/ first; if it returns a project page rather than a 404, the name is taken and uv publish will fail later.

$ uv init <PACKAGE_NAME>
Initialized project `<PACKAGE_NAME>` at `/path/to/<PACKAGE_NAME>`
$ cd <PACKAGE_NAME>

Notice the pyproject.tomlThe standard configuration file for Python projects. Declares the project name, version, dependencies, build system, and tool settings in one place. Learn more → uv created. It declares the package name, version, and requires-python. TestPyPI uses those fields to register your release.

Building Distributions

Build your package distributions from the project root:

$ uv build
Building source distribution...
running egg_info
... (setuptools build output) ...
Successfully built dist/<PACKAGE_NAME>-0.1.0.tar.gz
Successfully built dist/<PACKAGE_NAME>-0.1.0-py3-none-any.whl

Between Building source distribution... and the final Successfully built lines, setuptools prints several dozen lines of build progress. The exact lines vary by platform and project structure; what matters is that both Successfully built lines appear at the end.

If you see × Failed to build ...does not appear to be a Python project, as neither pyproject.toml nor setup.py are present, you ran uv build from outside the project. Run cd <PACKAGE_NAME> first.

This creates two files in the dist/ directory:

  • A wheelA prebuilt Python package file (.whl) that installs without compiling anything. The standard distribution format for Python packages. Learn more → file (.whl) - Built distribution
  • A source distribution (.tar.gz)

Notice the new dist/ directory next to pyproject.toml. Open it and confirm both files are present before continuing to the publish step. If either file is missing, the build stopped partway through; re-run uv build and check the output for an error line.

Notice both filenames carry 0.1.0 from pyproject.toml. TestPyPI rejects re-uploads of an existing version, so any future publish will need a version bump in pyproject.toml followed by another uv build.

Creating a PyPI API Token

  1. Log into PyPI
  2. Go to Account SettingsAPI tokens
  3. Create a token with “Entire Account” scope
  4. Save the token securely - you won’t see it again

Publishing to TestPyPI

Use your saved token to publish:

$ uv publish --token pypi-xxxx-xxxx-xxxx-xxxx --publish-url https://test.pypi.org/legacy/
Publishing 2 files to https://test.pypi.org/legacy/
Uploading <PACKAGE_NAME>-0.1.0-py3-none-any.whl (1.3KiB)
Uploading <PACKAGE_NAME>-0.1.0.tar.gz (930B)

Visit https://test.pypi.org/project/<PACKAGE_NAME>/ to confirm the upload landed. A project page with version 0.1.0 should appear within a few seconds. If the page still shows a 404 after a minute, the upload may have failed silently; check the terminal output for a non-zero exit code.

If you see 403 Forbidden, either the token is wrong or another TestPyPI user already owns the project name. Pick a different name, regenerate the project with uv init, and rebuild before retrying. If you see 400 File already exists, you previously uploaded version 0.1.0; bump the version field in pyproject.toml, re-run uv build, and try again.

Verifying Installation

Test that your package installs correctly:

If your package name contains hyphens (for example my-package), the Python import uses underscores instead (my_package). Adjust the -c argument in the command below accordingly.

uv run executes the command inside the project’s virtual environmentAn isolated folder where Python installs packages for one project, keeping them separate from other projects and your system Python. Learn more → , ensuring all dependenciesAn external package your project needs, listed in pyproject.toml so tools can install it automatically. are installed first.

$ uv run --index https://test.pypi.org/simple --with <PACKAGE_NAME> --no-project -- python -c "import <PACKAGE_NAME>"

The command prints nothing on success. python -c "import ..." exits cleanly when the import works, so silence here means the upload landed and the package installs.

Notice the --no-project flag. It tells uv to ignore the local pyproject.toml so the package is fetched from TestPyPI into a fresh environment instead of resolved from your working directory.

If you see No solution found when resolving dependencies or a 404 on the package URL, TestPyPI may not have indexed the upload yet. Wait a minute and try again.

Publish to PyPI

When you’re ready to publish to PyPI, you will remove the --publish_url from the publish command and use a token created at pypi.org.

Learn More:

Last updated on