DEV Community

viraj-sh
viraj-sh

Posted on AI-assisted

Develop and Publish Python Packages to PyPI

Prerequisites

Steps

1. Initialize the project

Run this in the directory where you want the project:

uv init --package --name project_name --build-backend uv
Enter fullscreen mode Exit fullscreen mode

This creates a src/project_name package and a pyproject.toml.

2. Write your code

Start coding inside ./src/project_name — that's where the package source lives. All your modules go here.

3. Update the project metadata

Edit pyproject.toml:

  • Update description
  • Add a license
  • Update the author name and email
  • Add your dependencies

Also update README.md and LICENSE.

4. (Optional) Add entry points for a CLI app or MCP server

If your app is a CLI or an MCP server, you can expose a command that gets installed on the user's system. Add an entry.py under the relevant subpackage.

CLI entry point — src/package_name/cli/entry.py:

# src/package_name/cli/entry.py
import sys


def run():
    try:
        from package_name.cli.main import cli
    except ModuleNotFoundError as e:
        if e.name in {"typer", "keyring"}:
            sys.exit(
                "Missing CLI deps. Install: uv tool install 'package_name[cli]'"
            )
        raise
    cli()
Enter fullscreen mode Exit fullscreen mode

MCP server entry point — src/package_name/mcp_server/entry.py:

# src/package_name/mcp_server/entry.py
import sys


def run():
    try:
        from package_name.mcp_server.server import main
    except ModuleNotFoundError as e:
        if e.name == "fastmcp":
            sys.exit(
                "Missing MCP deps. Install: uv tool install 'package_name[mcp]'"
            )
        raise
    main()
Enter fullscreen mode Exit fullscreen mode

Then register the commands in pyproject.toml:

[project.scripts]
command_name = "package_name.cli.entry:run"
mcp_server_name = "package_name.mcp_server.entry:run"
Enter fullscreen mode Exit fullscreen mode

Note: the [cli] and [mcp] extras used above must be defined under [project.optional-dependencies] in pyproject.toml.

5. Update .gitignore and test locally

Add any files that should not be committed (virtual environments, dist/, secrets, etc.) to .gitignore, then test the application locally.

6. Build the package

uv build
Enter fullscreen mode Exit fullscreen mode

This creates a .tar.gz (source distribution) and a .whl (wheel) in ./dist. Extract the .tar.gz and check its contents to make sure no unnecessary files are included.

7. Test the build locally

Before publishing, install the wheel to confirm it works.

As a library — install it into a virtual environment:

uv pip install ./dist/package_name-x.x.x-py3-none-any.whl
Enter fullscreen mode Exit fullscreen mode

As a CLI or MCP server — uv tool install is meant for packages that expose a command (a CLI entry point or an MCP server entry point). Install the wheel with the relevant extra:

uv tool install "package_name[cli] @ ./dist/package_name-x.x.x-py3-none-any.whl"
# or
uv tool install "package_name[mcp] @ ./dist/package_name-x.x.x-py3-none-any.whl"
Enter fullscreen mode Exit fullscreen mode

8. Publish to PyPI

Log in at https://pypi.org/manage/account/ and create an API token.

Use the token to publish:

export UV_PUBLISH_TOKEN=pypi-...
uv publish
Enter fullscreen mode Exit fullscreen mode

The package is now live under your account, and anyone can install it with:

pip install package_name
Enter fullscreen mode Exit fullscreen mode

Note: a new token can be scoped to your whole account only until the project exists. After the first upload, create a token scoped to that project and use it for future releases.

9. Publish updates

After making code changes:

Bump the version in pyproject.toml:

uv version --bump patch   # or minor / major
Enter fullscreen mode Exit fullscreen mode

Clean old builds:

rm -rf dist
Enter fullscreen mode Exit fullscreen mode

Build:

uv build
Enter fullscreen mode Exit fullscreen mode

Publish (with UV_PUBLISH_TOKEN set):

uv publish
Enter fullscreen mode Exit fullscreen mode

You can also pass the token directly with uv publish --token pypi-....

Note: PyPI does not allow re-uploading a version that already exists, so always bump the version before publishing.

Top comments (0)