Skip to content

#311: Creating Packages With uv

As I introduced project management with uv in this blog, we skipped the code part of creating our own packages. Let us end the year with closing this gap and see what it takes to make a package for PyPI.org

Create an account at PyPI.org

Before we can use PyPI.org to distribute our packages, we need to create an account. Go to https://pypi.org/account/register/ and fill out the form. After submitting the information, make sure to verify the email address, otherwise you may run into problems along the way.

When everything is ready, create a token to access the account from the command line. For that we need a name and then must copy the generated token – otherwise we lose it and need to create another token.

In the file ~/.pypirc (on Windows this will be c:\Users\USERNAME\.pypirc) we paste the configuration setting from the token page that looks like this:

1
2
3
[pypi]
username = __token__
password = pypi-XXXXXXXXXXXXXXXXXXXXXXXX

Currently, uv does not support this file, but other publishing tools like Twine do.

Create a new project

To create a package, we need to have some code that we can put into that package. For this we create a new little project that flips a coin and answers with yes or no. We can initialise our project with this command:

uv init coinflip

If we want to run our project as a command line tool, we need to follow the structure for CLI tools as described in the Python Packaging User Guide. For that we need to create a src folder that contains a folder with the name of our command and the Python code inside a cli.py file:

1
2
3
4
5
cd coinflip
mkdir src
mkdir src\coinflip
New-Item src\coinflip\__init__.py -type file
mv main.py src\coinflip\cli.py

This should give us this structure for our project:

coinflip:
|   .python-version
|   pyproject.toml
|   README.md
|   uv.lock
\---src
    +---coinflip
        |   cli.py
        |   __init__.py

Fill the files

We now can put our content into the files. In our README.md file we put this documentation:

    # coinflip-yesno

    A tiny command line utility that prints `yes` or `no`.

    ## Install

    ``` bash
    pip install coinflip-yesno
    ```

    ## Usage

    ``` bash
    coinflip
    ```

In pyproject.toml we can put this configuration:

[project]
name = "coinflip-yesno"
version = "0.1.0"
description = "A minimal CLI tool that flips a coin and answers yes or no"
authors = [{name = "Johnny Graber"}]
readme = "README.md"
requires-python = ">=3.9"

[project.scripts]
coinflip = "coinflip.cli:main"

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

Our logic for the coinflip goes into src\coinflip\cli.py:

1
2
3
4
import random

def main():
    print(random.choice(["yes", "no"]))

The files __init__.py, uv.lock, and .python-version stay as they are.

Build and publish the package

We can build our package with this command:

uv build

When everything worked without an error, we can find our package inside the dist folder. From there we can publish our package to PyPI:

uv publish

We need to use __token__ as the username and our token as the password. I hope uv will soon support the ~/.pypirc file, but until then we need to put in the username and password when prompted or through environment variables. If the upload succeeds, we can find our package on PyPI:

Our package is now accessible through PiPY.

Install our package

We can now install our package like any other Python package using pip or uv:

uv pip install coinflip-yesno==0.1.0

If everything worked with the CLI specification, we could run our script directly from the command line:

1
2
3
4
5
6
7
8
> coinflip
no
> coinflip
no
> coinflip
yes
> coinflip
no

Conclusion

It only takes a bit of preparation and a few extra tools, but otherwise creating a Python package and distributing it through PyPI.org is not much different from creating a local Python script. While our package is as minimal as it can get, it has all the important parts in it. From this base we can build on top and create a much more useful package. I hope this helps you with your own packages.