<- VOLVER AL ARCHIVO
EN

Create and upload a package in forgejo

IDEAS / CÓDIGO / PROCESO *

Create and upload a package

From the relevant repo:

Important!!

To upload these packages you must have the HTTPS certificate installed.

If your computer does not have this certificate installed, ask the administrator to install it.

Activate/create a virtual environment:

python3 -m venv .venv
source .venv/bin/activate

Install the required tools:

python -m pip install --upgrade pip
python -m pip install build twine

Make sure pyproject.toml has a name and version

Before building the package, delete previous artifacts

rm -rf dist build

Build the package

python -m build

To check:

ls -lh dist/

You should see something like:

dist/ ├── example_package-0.1.0-py3-none-any.whl └── example_package-0.1.0.tar.gz

Configure the variables used to upload the package

The package can belong to a user or an organization. To publish under an organization, your account needs permission to write packages there.

export FORGEJO_URL="https://forgejo.example.com"
export FORGEJO_USER="YOUR_USER"
export OWNER="YOUR_OWNER"
export TOKEN="YOUR_TOKEN"

Replace these example values with your own server, user, package owner and token. If you are publishing under your own account, set OWNER="$FORGEJO_USER".

To create the token:

Create a token for your own account with permission to read and write packages. If your Forgejo instance uses a shared publishing account, ask its administrator how to obtain access.

Profile → Settings → Applications → New access token

Give the token a descriptive name and the permissions needed to read and write packages.

Write this token down; once you leave this menu it cannot be viewed again.

Publish the package

python -m twine upload \
  --repository-url "$FORGEJO_URL/api/packages/$OWNER/pypi" \
  -u "$FORGEJO_USER" \
  -p "$TOKEN" \
  dist/*

If everything went well, you should see something like:

Uploading distributions to https://forgejo.example.com/api/packages/YOUR_OWNER/pypi
Uploading example_package-0.1.0-py3-none-any.whl
100% ...
Uploading example_package-0.1.0.tar.gz
100% ...

If it fails because of the TLS certificate and it is already installed on the machine, run the following command:

export TWINE_CERT="/etc/ssl/certs/ca-certificates.crt"

This package can now be installed as a dependency of other packages with a stable version. If you make changes to this repo that break other repos, it won't matter, because this package is stable and contains the information from when it was uploaded.

Clear the environment variables

unset FORGEJO_URL FORGEJO_USER OWNER TOKEN

Where to view the package

The package will belong to and be shown in the owner's profile.

How to install the project locally with uv

uv can create and manage the virtual environment automatically from the pyproject.toml file. It is not necessary to create the .venv manually with python -m venv.

Install without optional dependencies

To create the .venv and install the normal project dependencies:

uv sync

uv creates the .venv automatically if it does not exist.

If the repository contains an up-to-date uv.lock and you do not want uv to modify it, use:

uv sync --locked

The virtual environment does not need to be activated when commands are executed through uv:

uv run python script.py

If you prefer to activate it manually:

source .venv/bin/activate

Define optional dependencies

Dependencies that are not required for every user can be separated into an optional group in pyproject.toml. For example, the packages hosted in Forgejo can be grouped under an extra called private:

[project]
dependencies = [
    "numpy>=1.26,<3",
    "taichi>=1.7,<1.8",
    "trimesh>=4.8,<5",
    "PyYAML>=6,<7",
]

[project.optional-dependencies]
private = [
    "example-package==0.1.0",
    "example-tools==0.1.0",
]

[tool.uv.sources]
example-package = { index = "forgejo" }
example-tools = { index = "forgejo" }

[[tool.uv.index]]
name = "forgejo"
url = "https://forgejo.example.com/api/packages/YOUR_OWNER/pypi/simple"
explicit = true

With this configuration, a normal installation does not install the private extra:

uv sync

Install a specific optional dependency group

To install the normal dependencies together with the optional private dependencies, configure the credentials for the private index and enable the extra:

export UV_INDEX_FORGEJO_USERNAME="YOUR_USER"
export UV_INDEX_FORGEJO_PASSWORD="YOUR_TOKEN"
export UV_SYSTEM_CERTS="true"

uv sync --extra private

The name after --extra must match the name declared in [project.optional-dependencies].

For example:

[project.optional-dependencies]
private = [
    "example-package==0.1.0",
    "example-tools==0.1.0",
]

is installed with:

uv sync --extra private

Install all optional dependencies

If the project defines more than one optional dependency group and all of them are required:

uv sync --all-extras

For private optional dependencies, the Forgejo credentials must also be available before running this command.

Important note about uv.lock and private optional dependencies

Optional dependencies are not installed by uv sync unless their extra is selected, but they are still part of dependency resolution when uv needs to create or update uv.lock.

For this reason, if the optional dependencies come from the private Forgejo index, credentials may still be required when the lock file is generated or updated, even when the extra is not going to be installed.

For normal users cloning a repository, it is recommended to commit an up-to-date uv.lock and install with:

uv sync --locked

To install the private optional dependencies as well:

export UV_INDEX_FORGEJO_USERNAME="YOUR_USER"
export UV_INDEX_FORGEJO_PASSWORD="YOUR_TOKEN"
export UV_SYSTEM_CERTS="true"

uv sync --extra private --locked

After installation, commands can be executed without activating the environment:

uv run python script.py
uv run pytest

How to use these packages as dependencies

The most elegant option would be to specify these packages as dependencies in pyproject, but it is important to indicate that they belong to a different index from the one holding the rest of the public packages:

dependencies = [
    "numpy>=1.26,<3",
    "taichi>=1.7,<1.8",
    "trimesh>=4.8,<5",
    "PyYAML>=6,<7",

    "example-package==0.1.0",
    "example-tools==0.1.0",
]

[tool.uv.sources]
example-package = { index = "forgejo" }
example-tools = { index = "forgejo" }

[[tool.uv.index]]
name = "forgejo"
url = "https://forgejo.example.com/api/packages/YOUR_OWNER/pypi/simple"
explicit = true

The URL changes depending on the package owner. Use the same owner in the publishing URL and in the package index so the setup stays easy to trace.

How to specify it in the workflow

- name: Install uv
       shell: bash
       run: |
         set -euo pipefail

         curl -LsSf https://astral.sh/uv/install.sh \
           | env UV_UNMANAGED_INSTALL="/usr/local/bin" sh

         uv --version

     - name: Install dependencies
       env:
         UV_INDEX_FORGEJO_USERNAME: ${{ secrets.PACKAGE_READ_USERNAME }}
         UV_INDEX_FORGEJO_PASSWORD: ${{ secrets.PACKAGE_READ_TOKEN }}
         UV_SYSTEM_CERTS: "true"
       run: |
         uv sync    

To manage these packages you must install the uv tool and then provide, via Forgejo secrets (variables whose value is private and are set in the Forgejo web interface), the access credentials for these packages so they can be installed with

uv sync