Files
it-know-how/python/Gitea PyPI Package Registry.md

7.1 KiB

Python-Paket auf Gitea veröffentlichen

Gitea kann als private oder öffentliche PyPI-Registry verwendet werden. Damit lassen sich eigene Python-Pakete bauen, versionieren und in anderen Projekten mit pip oder uv installieren.

Diese Anleitung verwendet utils als Beispielpaket und den Gitea-Server gitea.schneiderserver.site.

Voraussetzungen

  • ein Python-Projekt mit pyproject.toml
  • ein Gitea-Repository
  • uv für Environment, Tests und Build
  • twine für das Veröffentlichen
  • ein privater Gitea Personal Access Token

Verwandt: uv und pytest.

Paket lokal bauen

Im Projektverzeichnis:

uv sync
uv run pytest
uv build

Danach liegen normalerweise zwei Dateien in dist/:

dist/utils-0.1.0.tar.gz
dist/utils-0.1.0-py3-none-any.whl

Vor einem erneuten Build kann dist/ entfernt werden:

rm -rf dist
uv build

Gitea-Endpunkte

Der Owner ist der Gitea-Benutzer oder die Organisation, unter der das Paket veröffentlicht wird. Er ist nicht zwingend der Repository-Name.

Publish-Endpunkt:

https://<gitea-host>/api/packages/<owner>/pypi

Installations-Endpunkt:

https://<gitea-host>/api/packages/<owner>/pypi/simple

Für den eigenen Server und den Benutzer mathias lautet das beispielsweise:

https://gitea.schneiderserver.site/api/packages/mathias/pypi
https://gitea.schneiderserver.site/api/packages/mathias/pypi/simple

Token

YOUR_GITEA_TOKEN steht für einen privaten Personal Access Token aus Gitea. Es handelt sich nicht um einen öffentlichen Token.

Für das Veröffentlichen wird mindestens eine Berechtigung zum Schreiben von Packages benötigt, typischerweise write:package. Für die Installation privater Packages wird read:package benötigt. Die genauen Namen können je nach Gitea-Version leicht abweichen.

Den Token niemals:

  • in Git committen
  • in eine README schreiben
  • im Chat oder in Logs veröffentlichen
  • dauerhaft direkt in die Shell-History schreiben

Für CI/CD sollte der Token als Gitea-Action-Secret hinterlegt werden, zum Beispiel als GITEA_TOKEN.

Manuell mit Twine veröffentlichen

Twine installieren:

uv tool install twine

~/.pypirc anlegen oder ergänzen:

[distutils]
index-servers =
    gitea

[gitea]
repository = https://gitea.schneiderserver.site/api/packages/mathias/pypi
username = mathias
password = YOUR_PRIVATE_GITEA_TOKEN

Anschließend veröffentlichen:

python -m twine upload --repository gitea dist/*

Gitea akzeptiert denselben Paketnamen mit derselben Versionsnummer nicht zweimal. Für eine neue Veröffentlichung muss daher zuerst die Version in pyproject.toml erhöht werden:

version = "0.1.1"

Danach erneut bauen und veröffentlichen:

rm -rf dist
uv build
python -m twine upload --repository gitea dist/*

In einem anderen Projekt installieren

Öffentliches Paket

Wenn das Package und die Registry anonym lesbar sind:

pip install \
  --index-url https://gitea.schneiderserver.site/api/packages/mathias/pypi/simple \
  utils

Privates Paket

Für ein privates Package ist ein Token mit read:package erforderlich:

pip install \
  --index-url https://<username>:<read-token>@gitea.schneiderserver.site/api/packages/mathias/pypi/simple \
  utils

Dasselbe funktioniert mit uv:

uv pip install \
  --index-url https://<username>:<read-token>@gitea.schneiderserver.site/api/packages/mathias/pypi/simple \
  utils

Tokens besser nicht direkt in Befehle schreiben, weil sie in der Shell-History oder Prozessliste sichtbar werden können. Für lokale Nutzung besser einen Credential-Helper oder eine geschützte Konfigurationsdatei verwenden; in CI ausschließlich Secrets verwenden.

--index-url und --extra-index-url

Für private interne Packages ist --index-url vorzuziehen:

pip install --index-url <gitea-pypi-simple-url> utils

--extra-index-url kann zu Dependency-Confusion-Problemen führen, weil ein gleichnamiges öffentliches Paket von PyPI aufgelöst werden könnte. Falls zusätzlich öffentliche PyPI-Abhängigkeiten gebraucht werden, sollte die Paketquellen-Konfiguration bewusst und projektbezogen eingerichtet werden.

Git-Tags und Releases

Git-Tag und Paketversion sollten übereinstimmen:

pyproject.toml: version = "0.1.0"
Git-Tag:        v0.1.0

Beispiel:

git add pyproject.toml
git commit -m "Release version 0.1.0"
git tag v0.1.0
git push origin main
git push origin v0.1.0

Das Tagging ist nicht zwingend für den manuellen Upload, macht Releases aber nachvollziehbar und kann später als Auslöser für Gitea Actions dienen.

Automatisierung mit Gitea Actions

Ein Workflow kann bei jedem v*-Tag automatisch testen, bauen und veröffentlichen. Die Zugangsdaten gehören als Repository-Secrets in Gitea und nicht in die Workflow-Datei.

Beispiel für .gitea/workflows/publish.yml:

name: Publish package

on:
  push:
    tags:
      - "v*"

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.14"

      - name: Install uv
        run: pip install uv

      - name: Install dependencies
        run: uv sync

      - name: Run tests
        run: uv run pytest

      - name: Build package
        run: uv build

      - name: Publish to Gitea
        env:
          TWINE_USERNAME: ${{ secrets.GITEA_USERNAME }}
          TWINE_PASSWORD: ${{ secrets.GITEA_TOKEN }}
          TWINE_REPOSITORY_URL: https://gitea.schneiderserver.site/api/packages/mathias/pypi
        run: uv run --with twine twine upload dist/*

Dazu in Gitea die Secrets GITEA_USERNAME und GITEA_TOKEN konfigurieren.

Fehlersuche: 404 Not Found

Bei einem Fehler wie:

HTTPError: 404 Not Found from https://gitea.schneiderserver.site/api/packages/mathias/pypi

ist der Token wahrscheinlich nicht das Hauptproblem. Ungültige Zugangsdaten führen meistens zu 401 Unauthorized oder 403 Forbidden.

Folgende Punkte prüfen:

  1. Ist mathias exakt der Gitea-Benutzername oder Organisationsname?
  2. Wird der Repository-Owner statt des Repository-Namens verwendet?
  3. Ist die Package Registry in der Gitea-Instanz aktiviert?
  4. Wird der Gitea-Server über einen Reverse Proxy mit einem URL-Präfix betrieben?
  5. Wird der Publish-Endpunkt ohne /simple verwendet?
  6. Ist der Token noch gültig und besitzt er write:package?

Der Gitea-API-Endpunkt kann unabhängig davon erreichbar sein:

curl https://gitea.schneiderserver.site/api/v1/version

Der Publish-Endpunkt antwortet auf einen normalen GET-Aufruf möglicherweise mit 405 Method Not Allowed, weil er nur Upload-Anfragen akzeptiert. Das ist ein Hinweis darauf, dass der Endpunkt existiert; ein 404 deutet eher auf einen falschen Owner, einen falschen Pfad oder eine deaktivierte Registry hin.

Offizielle Dokumentation