Blog

How to publish a Python package to a Gitlab package registry using uv

18 Dec, 2024
Xebia Background Header Wave

In many organizations, there is a need for internal packages. Installing these package is often difficult. To make this easier, you can use an internal package registry. Gitlab provides this feature by default. We’ll show how to deploy to this index using uv, a fast Python package manager.

Why uv?

We can recommend uv for the following reasons:

  • It’s extremely fast with resolving dependencies and installing them. This makes development and builds faster, which is awesome.
  • It manages virtual environments for you.
  • It can manage Python versions for you. This removes the need for Conda, pyenv or manual installation of Python.
  • It’s built on standards such as the pyproject.toml specification.

For more benefits, you can check out the uv repository.

Let’s install it!

Install uv

You can install uv in a couple different ways. If you already have Python you can use pipx. pipx can install programs separately from other python environments: pipx install uv.

If you want to install it as a standalone, you can use curl:

curl -LsSf https://astral.sh/uv/install.sh | sh

You can find more instructions in the uv repository.

Create a new Gitlab repository

Note: If you have an existing repository, you can skip this step. Your project still needs to be set up with uv though.

  1. On Gitlab, create a new repository using the + button and walk through the steps.

how to publish a python package to a gitlab package registry using uv create repository

  1. Clone the repository to your computer: Click the blue “code” button and walk through the steps. You may need to set up authentication first.
  2. Once cloned, cd into the project directory and run uv init --package . This will set up the project. We use the --package flag because we want to distribute our code as a package!
  3. Create a git commit: git add . and git commit -m "Init package"
  4. Finally, push it to our repository: git push.

Our package is now ready to be published. But it’s not published just yet. Let’s create a pipeline to publish our package!

Create your Gitlab CI pipeline

In your project directory, create a file called .gitlab-ci.yml. In this file, we’re telling Gitlab what our CI pipeline will look like. We will be using git tags to determine our version number.

Copy the following contents into the file:

# We can define multiple stages here, but for this example we have only one. What happens in the publish stage is defined later.
stages:  
  - publish  

variables:  
  PYTHON_VERSION: "3.13"  
  UV_VERSION: "0.5.5"  
  # With the following variables, we tell uv how to publish our package.
  # Gitlab has built in usernames and passwords for publish packages. This means you don't need to change them!
  UV_PUBLISH_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi"  
  UV_PUBLISH_USERNAME: "gitlab-ci-token"  
  UV_PUBLISH_PASSWORD: "${CI_JOB_TOKEN}"  

# Before we do anything else, we need to install uv
before_script:  
  - pip install uv==${UV_VERSION}  # pipx is not needed on Gitlab, because we use a fresh environment every run

# This stage publishes to our Gitlab package registry
publish:  
  stage: publish  
  image: python:${PYTHON_VERSION}
  script:  
    # Set the version in pyproject.toml to the current git tag  
    - VERSION=$(git describe --exact-match --tags)  
    - uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version $VERSION  
    # Build and publish the package  
    - uv build  
    - uv publish dist/*.whl  
  only:  # Only run this stage when a tag is created/pushed
    - tags

Explanation of the CI pipeline definition

Variables

In the variables, we define variables that can be set in one place and can be reused. This helps when expanding the pipeline, so it’s good to set it up from the start.

Publish stage

This publish stage publishes our package to the package registry. This involves a number of different steps:

  1. Get the version number from the latest git tag.
  2. Change the version number in pyproject.toml to the latest git tag. At the time of writing, uv does not (yet) support dynamic versioning. For more information, see this issue.
  3. Build our project. This will create a dist directory containing our built package.
  4. Publish the package to the registry we’ve defined in the variables. This will only publish the whl file found in the dist directory. The whl is much faster to install because it does not need to be built from scratch (like a .tar.gz).

Commit, tag and push your CI pipeline

First, commit our new pipeline:

git add .gitlab-ci.yml
git commit -m "Add pipeline to publish our package"

Then create a git tag, specifying your version number. Note that it cannot already exist, or it will give an error.

git tag 0.0.1

Now push the commit and tag to our Gitlab repository:

git push --tags

Our pipeline will automatically trigger on tags. This means it should already be running! To view the progress, go to your repository on Gitlab and choose Build > Pipelines in the sidebar:

how to publish a python package to a gitlab package registry using uv pipelines

If everything went well, you should see “Passed” in green. If there was an error, investigate it by clicking on “Failed” and carefully reading the logs. Then apply the appropriate fixes.

how to publish a python package to a gitlab package registry using uv pipeline passed how to publish a python package to a gitlab package registry using uv pipeline failed

When succesful, we can now view our published package under Deploy > Package Registry:

how to publish a python package to a gitlab package registry using uv package registry

Conclusion

In this blogpost we have:

  • Create a Python package using uv
  • Created a Gitlab CI pipeline
  • Succesfully published a package to an internal package registry.

Now users can start installing our package!

Sources

Many thanks to the following sources:


Photo by Kindel Media on Pexels

Timo Uelen
Timo Uelen is a Machine Learning Engineer at Xebia.
Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts