name: "Build PYPI" on: workflow_call: inputs: version: description: 'Version' type: string required: true ref: description: 'Ref' type: string required: true upload_to_pypi: description: "Upload to PyPi" type: boolean required: false default: false release-id: description: "Attach Artifact to Release" type: string required: false release_type: description: "Release type (release or nightly)" type: string required: false default: "release" outputs: pipy-artifact-url: description: "PyPI Artifact URL" value: ${{ jobs.pypi.outputs.pipy-artifact-url }} pipy-artifact-digests-sha256: description: "PyPI Artifact SHA256" value: ${{ jobs.pypi.outputs.pipy-artifact-digests-sha256 }} workflow_dispatch: inputs: version: description: 'Version' type: string required: true ref: description: 'Ref' type: string required: true upload_to_pypi: description: "Upload to PyPi" type: boolean required: false default: false release-id: description: "Attach Artifact to Release" type: string required: false release_type: description: "Release type (release or nightly)" type: choice required: false default: "release" options: - "release" - "nightly" env: PYTHON_VERSION_FILE: "pyproject.toml" FRONTEND_MONOREPO_DIR: "web" NODE: "22" POETRY_VERSION: 2.1.4 jobs: pypi: name: "PyPI" runs-on: ubuntu-latest outputs: pipy-artifact-url: ${{ steps.pypi-package-details.outputs.pipy-artifact-url }} pipy-artifact-digests-sha256: ${{ steps.pypi-package-details.outputs.pipy-artifact-digests-sha256 }} steps: - uses: hmarr/debug-action@v3.0.0 - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} - name: Install toml run: | set -euo pipefail wget -q -O- "https://github.com/gnprice/toml-cli/releases/download/v0.2.3/toml-0.2.3-x86_64-linux.tar.gz" | tar -xz -C . mv toml-0.2.3-x86_64-linux/toml toml chmod +x toml - name: Manage version env: PROVIDED_VERSION: ${{ inputs.version }} run: | set -euo pipefail version=$(sed "s/^v//g" <<< ${PROVIDED_VERSION}) ./toml set '${{ env.PYTHON_VERSION_FILE }}' project.version "$version" > pyproject.toml.new mv -f pyproject.toml.new pyproject.toml - name: Setup frontend environment uses: ./.github/actions/setup-frontend-environment with: node-version: "${{ env.NODE }}" directory: "${{ env.FRONTEND_MONOREPO_DIR }}" - name: "Build" working-directory: "${{ env.FRONTEND_MONOREPO_DIR }}" run: yarn build - name: "Generate version files" continue-on-error: true working-directory: "${{ env.FRONTEND_MONOREPO_DIR }}" run: yarn version:libs - name: "Install poetry" run: pipx install "poetry==${{ env.POETRY_VERSION }}" - name: "Set up Python" id: setup_python uses: actions/setup-python@v6 with: python-version: '3.13' cache: 'poetry' - name: Install Python dependencies run: poetry install --with build - name: Collect static run: poetry run python label_studio/manage.py collectstatic - name: Package env: VERSION_OVERRIDE: ${{ inputs.version }} run: poetry build - name: Attach artifacts to release if: inputs.release-id uses: actions/github-script@v8 with: github-token: ${{ secrets.GIT_PAT }} script: | const { repo, owner } = context.repo; const fs = require('fs'); const release_id = '${{ inputs.release-id }}'; for (let file of await fs.readdirSync('./dist/')) { console.log('uploadReleaseAsset', file); await github.rest.repos.uploadReleaseAsset({ owner, repo, release_id: release_id, name: file, data: await fs.readFileSync(`./dist/${file}`) }); } - name: Check SDK version is not git sha if: inputs.release_type == 'release' run: grep 'label-studio-sdk (==[0-9]\+\.[0-9]\+\.[0-9]\+)' pyproject.toml - name: Check Build run: poetry run twine check dist/* - name: Upload to PYPI if: inputs.upload_to_pypi env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ inputs.upload_to_pypi && secrets.PYPI_APIKEY || secrets.PYPI_APIKEY_TEST }} TWINE_REPOSITORY_URL: ${{ inputs.upload_to_pypi && 'https://upload.pypi.org/legacy/' || 'https://test.pypi.org/legacy/' }} run: poetry run twine upload dist/* - name: Get PyPI package details id: pypi-package-details if: inputs.upload_to_pypi && inputs.release_type == 'release' uses: actions/github-script@v8 env: VERSION: ${{ inputs.version }} with: github-token: ${{ secrets.GIT_PAT }} script: | const version = process.env.VERSION const MAX_ATTEMPTS = 10 let currentAttempt = 0 const intervalId = setInterval(async () => { currentAttempt++ console.log(`Attempt ${currentAttempt}`) const {data: pypiPackage} = await github.request('https://pypi.org/pypi/label-studio/json') if ('releases' in pypiPackage && version in pypiPackage.releases) { const release = pypiPackage.releases[version] const artifact = release.find(e => e.packagetype === 'sdist') console.log(artifact); core.setOutput("pipy-artifact-url", artifact.url); core.setOutput("pipy-artifact-digests-sha256", artifact.digests.sha256); clearInterval(intervalId) } else if (currentAttempt >= MAX_ATTEMPTS) { clearInterval(intervalId) throw Error('Max attempts exceeded') } }, 60 * 1000 ) - name: Check file size env: DIST_DIR: "./dist" THRESHOLD: "100" run: | result=true for file in $(find "${DIST_DIR}" -maxdepth 1 -type f); do size=$(du -m "$file" | cut -f1) echo "${file}(${size}Mb)" if [ $size -ge $THRESHOLD ]; then echo "::error::${file}(${size}Mb) exceeds size threshold(${THRESHOLD}Mb)" result=false fi done if [ "${result}" != "true" ]; then exit 1 fi - name: Upload to artifact if: always() && inputs.release_type == 'release' uses: actions/upload-artifact@v6 with: name: Dist path: dist/