Create a Python Library Installable with pip

Author
By Darío Rivera
Posted On in Python

In this post, you'll learn how to scaffold a modern Python library using uv and pip. You will also understand the process of generating Python builds.

Requirements

For this tutorial, you will require the following packages on your operating system:

- Python 3
- uv (see uv installation)
- A test PyPI account (see account register)

When creating a test PyPI (Python Package Index) account, you might also need to verify your email and create and activate 2FA. It is highly recommended to start submitting packages to test PyPI (test.pypi.org) instead of PyPI (pypi.org). In this tutorial, you'll learn how to submit your package to both indexes.

Initialize the project

We'll create a library with the following structure. Ensure you replace the YOUR_USERNAME to prevent any potential collisions with existing packages.

my-python-library/
├── src/
│   └── mylibrary_YOUR_USERNAME/
│       ├── __init__.py
│       └── greeter.py
├── tests/
├── README.md
└── pyproject.toml

Let's create first the root directory and browse into it.

mkdir my-python-library && cd my-python-library

Since uv will crete the pyproject.toml for us, let's create everthing else.

mkdir -p src/mylibrary_YOUR_USERNAME tests/
touch src/mylibrary_YOUR_USERNAME/__init__.py src/mylibrary_YOUR_USERNAME/greeter.py
touch README.md LICENSE

After having created those folders and empty files, the following command will create a couple of files for a new project with uv:

uv init

Your pyproject.toml should look like this:

[project]
name = "testlib"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []

Configure the package metadata

Let's take care now of the boilerplate previously created. Change the name attribute, which is the distribution name of your package. It should be a value not already taken in the PyPI (Python Package Index). For the sake of this example, we'll use mylibrary_YOUR_USERNAME. Adjust version and description to your requirements. You can also add the following block to specify authors.

authors = [
  { name="Example Author", email="author@example.com" },
]

You should also specify clasifiers to include at least which version(s) of Python your package works on.

classifiers = [
    "Programming Language :: Python :: 3",
    "Operating System :: OS Independent",
]

Since we've created a license file, this also needs to be hooked up as follows.

license = "MIT"
license-files = ["LICEN[CS]E*"]

Finally, relate extra links as follows:

[project.urls]
Homepage = "https://github.com/pypa/sampleproject"
Issues = "https://github.com/pypa/sampleproject/issues"

Add your library code

Add the following contents to src/mylibrary_YOUR_USERNAME/greeter.py as a simple sample function.

def greet(name: str) -> str:
    return f"Hello {name}!"

Install development dependencies

For this and subsequent steps you must have activated a virtual environment. This can be done by executing the following commands:

uv venv
source .venv/bin/activate

Then, you should be able to run the following command to install the development dependencies we need to build and publish the package.

uv add --group dev build twine

Build the package

To generate the distribution archives let's run the following command:

python3 -m build

You should see some files under dist/, and an output similar to the following:

* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - hatchling >= 1.26
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
  - hatchling >= 1.26
* Getting build dependencies for wheel...
* Building wheel...
Successfully built mylibrary_YOUR_USERNAME-0.1.0.tar.gz and mylibrary_YOUR_USERNAME-0.1.0-py3-none-any.whl

The tar.gz file is a source distribution whereas the .whl file is a built distribution.

Recent versions of pip prioritize installing built distributions (wheels). However, if none are available, pip will fall back to installing from a source distribution. For this reason, it is recommended to always upload a source distribution and also provide built distributions for the platforms your project supports. In this example, the package is compatible with Python on any platform, so a single built distribution is sufficient.

Uploading the distribution archives

The first step for submitting a package is to create a PyPI API token if you don't already have one.

- https://pypi.org/manage/account/#api-tokens (PyPI)
- https://test.pypi.org/manage/account/#api-tokens (test PyPI)

We'll use Twine to submit the package. It's highly recommended to experiment with test PyPI before submitting your real production package. If you feel brave, here's the command for publishing on PyPI.

uv run --group dev python -m twine upload --repository pypi dist/*

Otherwise, the following will submit the package to the test PyPI so you can experiment before submitting it to prod.

uv run --group dev python -m twine upload --repository testpypi dist/*

You'll be required to paste an authentication token. Then, you should see a final output similar to the following:

Uploading distributions to https://test.pypi.org/legacy/
WARNING  This environment is not supported for trusted publishing                                                                                                                              
Enter your API token: 
Uploading mylibrary_YOUR_USERNAME-0.1.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.3/5.3 kB • 00:00 • ?
Uploading mylibrary_YOUR_USERNAME-0.1.0.tar.gz
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 22.2/22.2 kB • 00:00 • 27.9 MB/s

View at:
https://test.pypi.org/project/mylibrary-YOUR_USERNAME/0.1.0/

Installing your package

For the sake of the example in this tutorial, we can use the following command to install the library in another project. This is the most common way to install a library.

pip install mylibrary_YOUR_USERNAME

There are a couple of different scenarios you might consider, but if this is just enough, feel free to jump to usage.

As a project dependency

The following command use pip to install the newly uploaded library.

pip install PYTHON_LIB

For projects using uv, here's the equivalent command:

uv add PYTHON_LIB

For adding the library from the test PyPI, the following tag needs to be added:

--index-url https://test.pypi.org/simple/

As a global CLI tool

Installing a library globally allows any exposed command to be executed from any place in the terminal. This is most common when installing CLI tools like GCP CLI, AWS CLI, coding assistants, etc. To install a library globally with pip we can execute the following command:

pip install PYTHON_LIB

If uv is available here's the equivalent:

uv tool install PYTHON_LIB

For adding the library from the test PyPI, the following tag needs to be added as well:

--index-url https://test.pypi.org/simple/

Using your package

After installing the package, modules can be imported under the library name as follows:

from mylibrary_YOUR_USERNAME.greeter import greet

printf(f"Salutation: {greet('Steave')}")

Exposing functions in __init__.py

Exposing functions in __init__.py helps to create a clean and stable public API surface. It also helps to hide any details or functions you don't want to expose in the package.

Add the following code to src/mylibrary_YOUR_USERNAME/__init__.py.

from .greeter import greet

Will allow consumers to import the greet function as follows:

from mylibrary_YOUR_USERNAME import greet

Acerca de Darío Rivera

Author

Application Architect at Elentra Corp . Quality developer and passionate learner with 10+ years of experience in web technologies. Creator of EasyHttp , an standard way to consume HTTP Clients.

LinkedIn Twitter Instagram

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.