# Drone standard — Type A: PyPI package # # Canonical pipeline for internal Python libraries that ship a wheel/sdist to # the private index (dagpipe, doc-forge, hexa, mail-intake, mongo-ops, # omniread, openapi-first, py-jwt). # # Two pipelines ship together: # build-and-publish-pypi — tag-triggered build + quality gate + publish. # backfill-pypi-from-tags — custom-triggered replay of every git tag that is # missing from the index (disaster recovery). # # Secrets (Drone): PIP_REPO_URL, PIP_USERNAME, PIP_PASSWORD. # Copy to /.drone.yml unchanged; the package name and version are read # from pyproject.toml, so no per-repo edits are required. --- kind: pipeline type: docker name: build-and-publish-pypi platform: os: linux arch: arm64 workspace: path: /drone/src steps: - name: check-version image: curlimages/curl:latest environment: PIP_REPO_URL: from_secret: PIP_REPO_URL PIP_USERNAME: from_secret: PIP_USERNAME PIP_PASSWORD: from_secret: PIP_PASSWORD commands: - PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | head -1 | cut -d'"' -f2) - VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2) - echo "Checking if $PACKAGE_NAME==$VERSION exists on $PIP_REPO_URL ..." - | if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then echo "$PACKAGE_NAME==$VERSION already exists; skipping build." exit 78 else echo "New version detected: $PACKAGE_NAME==$VERSION" fi - name: quality-gate image: python:3.13-slim environment: PIP_REPO_URL: from_secret: PIP_REPO_URL PIP_USERNAME: from_secret: PIP_USERNAME PIP_PASSWORD: from_secret: PIP_PASSWORD commands: - pip install --upgrade pip build - | AUTH_URL="https://${PIP_USERNAME}:${PIP_PASSWORD}@$(echo "${PIP_REPO_URL#*://}" | sed 's:/*$::')/simple" pip install --index-url "$AUTH_URL" --extra-index-url https://pypi.org/simple/ -U ".[dev]" "pydoclint>=0.9.1" - echo "Running quality gate..." - python -m black --check . - python -m ruff check . - python -m mypy - pydoclint . - python -m pytest - name: build-package image: python:3.13-slim commands: - pip install --upgrade pip build - echo "Building Python package..." - python -m build - ls -l dist - name: upload-to-private-pypi image: python:3.13-slim environment: PIP_REPO_URL: from_secret: PIP_REPO_URL PIP_USERNAME: from_secret: PIP_USERNAME PIP_PASSWORD: from_secret: PIP_PASSWORD commands: - pip install --upgrade twine - echo "Uploading to private PyPI at $PIP_REPO_URL ..." - | twine upload \ --repository-url "$PIP_REPO_URL" \ -u "$PIP_USERNAME" \ -p "$PIP_PASSWORD" \ dist/* trigger: event: - tag --- kind: pipeline type: docker name: backfill-pypi-from-tags platform: os: linux arch: arm64 workspace: path: /drone/src steps: - name: fetch-tags image: alpine/git commands: - git fetch --tags --force - name: build-and-upload-missing image: python:3.13-slim environment: PIP_REPO_URL: from_secret: PIP_REPO_URL PIP_USERNAME: from_secret: PIP_USERNAME PIP_PASSWORD: from_secret: PIP_PASSWORD commands: - apt-get update - apt-get install -y git curl ca-certificates - pip install --upgrade pip build twine - | set -e PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | cut -d'"' -f2) echo "Package: $PACKAGE_NAME" for TAG in $(git tag --sort=version:refname); do VERSION="$TAG" echo "Version: $VERSION" if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" \ "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then echo "Exists, skipping" continue fi git checkout --force "$TAG" echo "Building $VERSION" rm -rf dist python -m build echo "Uploading $VERSION" twine upload \ --repository-url "$PIP_REPO_URL" \ -u "$PIP_USERNAME" \ -p "$PIP_PASSWORD" \ dist/* done trigger: event: - custom