diff --git a/python/Gitea PyPI Package Registry.md b/python/Gitea PyPI Package Registry.md new file mode 100644 index 0000000..53ea974 --- /dev/null +++ b/python/Gitea PyPI Package Registry.md @@ -0,0 +1,262 @@ +# 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/)