# 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: ```bash uv sync uv run pytest uv build ``` Danach liegen normalerweise zwei Dateien in `dist/`: ```text dist/utils-0.1.0.tar.gz dist/utils-0.1.0-py3-none-any.whl ``` Vor einem erneuten Build kann `dist/` entfernt werden: ```bash 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: ```text https:///api/packages//pypi ``` Installations-Endpunkt: ```text https:///api/packages//pypi/simple ``` Für den eigenen Server und den Benutzer `mathias` lautet das beispielsweise: ```text 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: ```bash uv tool install twine ``` `~/.pypirc` anlegen oder ergänzen: ```ini [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: ```bash 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: ```toml version = "0.1.1" ``` Danach erneut bauen und veröffentlichen: ```bash 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: ```bash 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: ```bash pip install \ --index-url https://:@gitea.schneiderserver.site/api/packages/mathias/pypi/simple \ utils ``` Dasselbe funktioniert mit `uv`: ```bash uv pip install \ --index-url https://:@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: ```bash pip install --index-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: ```text pyproject.toml: version = "0.1.0" Git-Tag: v0.1.0 ``` Beispiel: ```bash 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`: ```yaml 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: ```text 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: ```bash 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 - [Gitea PyPI Package Registry](https://docs.gitea.com/usage/packages/pypi/) - [Gitea Package Registry Übersicht](https://docs.gitea.com/usage/packages/overview/)