Create a Python Library Installable with pip
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