Packaging and Publishing a Python Library

· 6 min read · Updated March 7, 2026 · intermediate
packaging pypi pip setuptools publishing

You’ve written a useful Python module and now you want to share it with the world. Packaging and publishing your code lets anyone install it with a single pip install command. This tutorial walks you through the entire process, from structuring your project to uploading it to PyPI.

Why Package Your Code?

When you write Python code that others might want to use, packaging it properly gives you several benefits. First, users can install it with pip install your-package-name instead of manually downloading files. Second, version management becomes automatic—users can pin specific versions or use version specifiers. Third, dependencies are handled automatically during installation.

If you’ve been sharing code by sending Python files or using GitHub repositories, proper packaging is a significant upgrade to your workflow.

Setting Up Your Project Structure

A well-structured project follows a specific layout that Python packaging tools understand. Let’s create a real package called greeter that demonstrates useful functionality.

greeter/
├── pyproject.toml
├── src/
│   └── greeter/
│       ├── __init__.py
│       └── greeter.py
├── LICENSE
└── README.md

The src layout is the modern standard. Your package code goes inside src/<package_name>/. This separates your source code from configuration files and makes it clear what’s being distributed.

Create the directories and files:

mkdir -p greeter/src/greeter
touch greeter/src/greeter/__init__.py

Creating pyproject.toml

The pyproject.toml file is the modern way to configure Python packaging. It tells build tools how to package your project and defines metadata like name, version, and dependencies.

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "greeter"
version = "0.1.0"
description = "A simple package that generates greetings"
readme = "README.md"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "you@example.com"}
]
requires-python = ">=3.8"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

[project.urls]
Homepage = "https://github.com/yourusername/greeter"
Repository = "https://github.com/yourusername/greeter"

[project.optional-dependencies]
dev = ["pytest", "black", "mypy"]

The [build-system] section tells pip which build backend to use. We’re using setuptools, which is the most common choice and handles almost everything well.

The [project] section contains your package metadata. The name must be unique on PyPI—check if your desired name is available before publishing.

The [project.urls] section links to your homepage and repository. These appear on your PyPI project page and help users find more information.

Writing Your Package Code

Now let’s add actual code to our package. Open src/greeter/__init__.py:

"""greeter - A simple package that generates greetings."""

from greeter.greeter import greet, greet_person

__version__ = "0.1.0"

And create src/greeter/greeter.py:

"""Core greeting functions."""


def greet(name: str = "World") -> str:
    """Return a greeting for the given name.
    
    Args:
        name: The name to greet. Defaults to "World".
    
    Returns:
        A greeting string.
    """
    return f"Hello, {name}!"


def greet_person(first_name: str, last_name: str = "") -> str:
    """Return a formal greeting for a person.
    
    Args:
        first_name: The person's first name.
        last_name: The person's last name (optional).
    
    Returns:
        A formal greeting string.
    """
    if last_name:
        return f"Hello, {first_name} {last_name}!"
    return f"Hello, {first_name}!"

This is a simple but complete package. Notice we’re using type hints, which helps users of your package understand what arguments functions accept.

Adding a README and License

Create a README.md in your project root with installation and usage instructions. For example:

# greeter

A simple Python package that generates greetings.

## Installation

pip install greeter

## Usage

from greeter import greet

print(greet("Alice"))  # Hello, Alice!
print(greet())         # Hello, World!

For the license, create a LICENSE file with the MIT license text (or your chosen license). The MIT license is a common choice for open source packages because it’s permissive and easy to understand.

Building Your Package

Before uploading, you need to build your package into distribution formats. You’ll use the build module for this.

First, install the build tools:

pip install build twine

Now build your package:

python -m build

This command creates two files in a new dist/ directory:

  • A wheel file (.whl) - a pre-built package format
  • A source distribution (.tar.gz) - the original source code

The output shows what was created:

✨  Building wheel for greeter (0.1.0)
✨  Building sdist for greeter (0.1.0)
✨  Built dist/greeter-0.1.0-py3-none-any.whl
✨  Built dist/greeter-0.1.0.tar.gz

These are the files you’ll upload to PyPI.

Testing with TestPyPI

Before uploading to the real PyPI, test your package on TestPyPI. This is a separate index where you can upload and install packages without affecting the main Python package ecosystem.

Create or edit ~/.pypirc to store your credentials:

[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = your_username
password = your_password

[testpypi]
username = your_username
password = your_password

Upload to TestPyPI using twine:

twine upload --repository testpypi dist/*

You’ll see output like:

Uploading greeter-0.1.0-py3-none-any.whl
Uploading greeter-0.1.0.tar.gz

Now test installing from TestPyPI:

pip install --index-url https://test.pypi.org/simple/ greeter

If everything worked, you can import and use your package:

from greeter import greet
print(greet("Test"))
# Hello, Test!

Publishing to PyPI

Once you’ve verified your package works, you’re ready to publish to the real PyPI. The process is identical to TestPyPI, just with different credentials.

twine upload dist/*

Your package is now live on PyPI. Anyone can install it with:

pip install greeter

Visit https://pypi.org/project/greeter (or your package name) to see your project page.

Common Pitfalls

Several issues frequently trip up first-time package publishers. Here are the most important ones to avoid.

Name conflicts. Your package name must be unique on PyPI. If you try to upload a package with a name that already exists, you’ll get an error. Check availability before naming your package.

Missing dependencies. List all required packages in your pyproject.toml under dependencies. Don’t assume users will have certain packages installed—they won’t.

Incorrect file paths. The package structure matters. If your code is in src/greeter/, make sure your configuration reflects that. Using the src/ layout avoids many path-related issues.

Version number conflicts. You cannot reuse a version number on PyPI. After uploading version 0.1.0, your next upload must be 0.1.1 or higher. Increment the version before each upload.

Building before testing. Always test with TestPyPI first. It’s embarrassing to discover a broken package after publishing to the real PyPI.

Adding Executable Scripts

You can create command-line tools that users can run after installing your package by adding a [project.scripts] section to your pyproject.toml:

[project.scripts]
greeter-cli = "greeter.cli:main"

This creates a greeter-cli command that calls the main function from greeter.cli. You’d need to create that module with a proper CLI entry point.

Next Steps

Your package is now published and installable by anyone. To improve it further, consider adding comprehensive tests with pytest, adding type hints throughout your code, and setting up continuous integration on GitHub. You might also want to publish your documentation on ReadTheDocs so users can understand how to use your package.

The previous tutorial covered multiprocessing with asyncio. The next tutorial will continue building your Python skills with practical intermediate topics.