From 3840f9ae419e7b2629dfaf402afd8a7d3fce63fd Mon Sep 17 00:00:00 2001 From: Leandro Echevarria Date: Tue, 12 Aug 2025 11:38:42 -0300 Subject: [PATCH 1/4] feat | LAY-885 Added ground files for our publishing CICD --- .github/workflows/publish-sdk.yaml | 60 +++++++++++++++++++++ .github/workflows/release-tag.yaml | 61 +++++++++++++++++++++ .github/workflows/test-publish-sdk.yaml | 40 ++++++++++++++ Makefile | 49 +++++++++++++++++ pyproject.toml | 2 +- requirements-dev.lock | 11 +++- requirements.lock | 2 +- scripts/publish.sh | 24 +++++++++ scripts/push-release-tag.sh | 72 +++++++++++++++++++++++++ scripts/template-version.sh | 28 ++++++++++ scripts/validate-release-tag.sh | 62 +++++++++++++++++++++ src/_version.py | 4 ++ 12 files changed, 412 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/publish-sdk.yaml create mode 100644 .github/workflows/release-tag.yaml create mode 100644 .github/workflows/test-publish-sdk.yaml create mode 100644 Makefile create mode 100644 scripts/publish.sh create mode 100644 scripts/push-release-tag.sh create mode 100644 scripts/template-version.sh create mode 100644 scripts/validate-release-tag.sh create mode 100644 src/_version.py diff --git a/.github/workflows/publish-sdk.yaml b/.github/workflows/publish-sdk.yaml new file mode 100644 index 0000000..d0be54f --- /dev/null +++ b/.github/workflows/publish-sdk.yaml @@ -0,0 +1,60 @@ +# This workflow is used to publish the Python SDK to the actual PyPI. +# It is triggered by a tag push, and will only publish if the tag is valid. +# The tag must match the format sdk-v*.*.* + +name: Publish Python SDK + +on: + push: + tags: + - "sdk-v*.*.*" # Trigger on version tags like sdk-v0.1.0 etc. + +jobs: + validate: + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.set_release_tag.outputs.release_tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for checking branch + - name: Set release tag + id: set_release_tag + # ensure the tag is valid (matches code, is on main, etc) + run: | + RELEASE_TAG=${GITHUB_REF#refs/tags/} + echo "Using tag: $RELEASE_TAG" + ./scripts/validate-release-tag.sh "$RELEASE_TAG" + echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV + echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT + + build-and-publish: + needs: validate + runs-on: ubuntu-latest + + env: + PYPI_USERNAME: __token__ + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + RELEASE_TAG: ${{ needs.validate.outputs.release_tag }} + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Build, Verify and Publish + working-directory: py + run: | + make install-build-deps + make build + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: sdk-dist + path: dist/ + retention-days: 5 + - name: Publish to PyPI + working-directory: py + run: | + make publish-to-pypi diff --git a/.github/workflows/release-tag.yaml b/.github/workflows/release-tag.yaml new file mode 100644 index 0000000..aecf7eb --- /dev/null +++ b/.github/workflows/release-tag.yaml @@ -0,0 +1,61 @@ +# This workflow creates and pushes a release tag using the push-release-tag.sh script. +# It can be triggered manually and will prompt for confirmation before creating the tag. + +name: Create Release Tag + +on: + workflow_dispatch: + inputs: + dry_run: + description: "Run in dry-run mode (show what would be done without actually creating/pushing the tag)" + required: false + type: boolean + default: true + confirm_release: + description: "Type 'YES' to confirm you want to create and push the release tag" + required: true + type: string + +jobs: + create-release-tag: + runs-on: ubuntu-latest + + permissions: + contents: write # Required to create and push tags + + steps: + - name: Validate confirmation + if: github.event.inputs.confirm_release != 'YES' && github.event.inputs.dry_run != 'true' + run: | + echo "Error: You must type 'YES' in the confirm_release input to proceed with creating a release tag." + echo "Received: '${{ github.event.inputs.confirm_release }}'" + exit 1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history and tags + + - name: Make scripts executable + run: | + chmod +x scripts/push-release-tag.sh + chmod +x scripts/get_version.sh + + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Run push-release-tag script (dry-run) + if: github.event.inputs.dry_run == 'true' + run: | + echo "Running in dry-run mode..." + make push-release-tag DRY_RUN=--dry-run + + - name: Run push-release-tag script + if: github.event.inputs.dry_run != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Creating and pushing release tag..." + # Override the interactive confirmation since we already confirmed via workflow input + echo "YES" | make push-release-tag diff --git a/.github/workflows/test-publish-sdk.yaml b/.github/workflows/test-publish-sdk.yaml new file mode 100644 index 0000000..a982359 --- /dev/null +++ b/.github/workflows/test-publish-sdk.yaml @@ -0,0 +1,40 @@ +# This workflow is used to publish the Python SDK to TestPyPI. Do not need to upgrade the +# version number to use this workflow. +# Only upgrade the version number when you are ready to publish to PyPi +# The script will automatically add an "rc" suffix to the version number for test.pypi.org releases. + +name: Publish Python SDK to TestPyPI + +on: + workflow_dispatch: + inputs: + ref: + description: "Publish the given Git ref to test.pypi.org (branch, tag, or commit SHA)" + required: true + type: string + default: "main" + +jobs: + build-and-publish-test: + runs-on: ubuntu-latest + + env: + PYPI_USERNAME: __token__ + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install build dependencies + working-directory: py + run: | + make install-build-deps + - name: Verify, build and publish to TestPyPI + working-directory: py + run: | + make publish-to-testpypi diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ea6227d --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +install-build-deps: + # Install build dependencies using rye + rye sync --all-features + +build: install-build-deps clean _template-version + python -m build + # Restore the original version file after the build + git checkout src/_version.py + +test-wheel: build + # Test the built wheel + rye run pytest --wheel + +clean: + rm -rf build dist + +_publish: + ./scripts/publish.sh + +_template-version: + @bash scripts/template-version.sh + +_check-git-clean: + @if [ -n "$$(git status --porcelain)" ]; then \ + echo "Error: Git working directory is not clean. Won't run publish."; \ + exit 1; \ + fi + +_verify-build-publish: _check-git-clean build test-wheel _publish + +publish-to-testpypi: export PYPI_REPO := testpypi +publish-to-testpypi: _verify-build-publish + +publish-to-pypi: export PYPI_REPO := pypi +publish-to-pypi: _verify-build-publish + +push-release-tag: + @bash scripts/push-release-tag.sh $(DRY_RUN) + +help: + @echo "Available targets:" + @echo " build - Build Python package" + @echo " clean - Remove build artifacts" + @echo " help - Show this help message" + @echo " install-build-deps - Install build dependencies for CI" + @echo " test-wheel - Run tests against built wheel" + @echo " publish-to-pypi - Publish to PyPI" + @echo " publish-to-testpypi - Publish to TestPyPI" + @echo " push-release-tag - Create and push a release tag" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ef86a87..4628bb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ Repository = "https://github.com/LayerLens/stratix-python" [project.scripts] layerlens = "layerlens.cli:main" - [tool.rye] managed = true # version pins are in requirements-dev.lock @@ -41,6 +40,7 @@ dev-dependencies = [ "pytest-cov>=6.2.1", "ruff", "types-requests", + "build", ] [tool.rye.scripts] diff --git a/requirements-dev.lock b/requirements-dev.lock index 9c0f730..475183f 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -4,7 +4,7 @@ # last locked with the following flags: # pre: false # features: [] -# all-features: false +# all-features: true # with-sources: false # generate-hashes: false # universal: false @@ -14,6 +14,7 @@ annotated-types==0.7.0 # via pydantic anyio==4.9.0 # via httpx +build==1.3.0 certifi==2025.7.14 # via httpcore # via httpx @@ -35,6 +36,8 @@ idna==3.10 # via anyio # via httpx # via requests +importlib-metadata==8.7.0 + # via build iniconfig==2.1.0 # via pytest mypy==1.17.0 @@ -43,6 +46,7 @@ mypy-extensions==1.1.0 nodeenv==1.9.1 # via pyright packaging==25.0 + # via build # via pytest pathspec==0.12.1 # via mypy @@ -55,6 +59,8 @@ pydantic-core==2.33.2 # via pydantic pygments==2.19.2 # via pytest +pyproject-hooks==1.2.0 + # via build pyright==1.1.399 pytest==8.4.1 # via pytest-cov @@ -65,6 +71,7 @@ ruff==0.12.7 sniffio==1.3.1 # via anyio tomli==2.2.1 + # via build # via coverage # via mypy # via pytest @@ -82,3 +89,5 @@ typing-inspection==0.4.1 urllib3==2.5.0 # via requests # via types-requests +zipp==3.23.0 + # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 887d3cc..d2ea895 100644 --- a/requirements.lock +++ b/requirements.lock @@ -4,7 +4,7 @@ # last locked with the following flags: # pre: false # features: [] -# all-features: false +# all-features: true # with-sources: false # generate-hashes: false # universal: false diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 0000000..394f8d5 --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Publish the package to PyPI or TestPyPI depending +# on the PYPI_REPO (pypi | testpypi) environment variable + +if [ -z "$PYPI_REPO" ]; then + echo "Error: PYPI_REPO environment variable must be set" + exit 1 +fi + +if [ "$PYPI_REPO" != "pypi" ] && [ "$PYPI_REPO" != "testpypi" ]; then + echo "Error: PYPI_REPO must be either 'pypi' or 'testpypi'" + exit 1 +fi + +VERSION=$(bash scripts/get_version.sh) + +if [ -z "$VERSION" ]; then + echo "Error: Could not determine version" + exit 1 +fi + +echo "Publishing version $VERSION to $PYPI_REPO" + +twine upload --repository "$PYPI_REPO" dist/*"$VERSION"* \ No newline at end of file diff --git a/scripts/push-release-tag.sh b/scripts/push-release-tag.sh new file mode 100644 index 0000000..64ba944 --- /dev/null +++ b/scripts/push-release-tag.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -euo pipefail + +ROOT_DIR=$(git rev-parse --show-toplevel) + +# Parse command line arguments +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + DRY_RUN=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--dry-run]" + exit 1 + ;; + esac +done + +git fetch --tags --prune + +REPO_URL="https://github.com/LayerLens/atlas-python" +TAG_PREFIX="sdk-v" +COMMIT=$(git rev-parse --short HEAD) +VERSION=$(bash "$ROOT_DIR/scripts/get_version.sh") +TAG="${TAG_PREFIX}${VERSION}" + +if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Error: Tag $TAG already exists" + exit 1 +fi + +# Find the most recent version tag +LAST_RELEASE=$(git tag -l "${TAG_PREFIX}*" --sort=-v:refname | head -n 1) + +echo "================================================" +echo " Atlas Python SDK Release" +echo "================================================" +echo "version: ${TAG}" +echo "commit: ${COMMIT}" +echo "code: ${REPO_URL}/commit/${COMMIT}" +echo "changeset: ${REPO_URL}/compare/${LAST_RELEASE}...${COMMIT}" + +if [ "$DRY_RUN" = true ]; then + exit 0 +fi + +echo "" +echo "" +echo "Are you ready to release version ${VERSION}? Type 'YES' to continue:" +read -r CONFIRMATION + +if [ "$CONFIRMATION" != "YES" ]; then + echo "Release cancelled." + exit 1 +fi + +# Create and push the tag +echo "" +echo "Creating and pushing tag ${TAG}" +echo "" + +git tag "$TAG" "$COMMIT" +git push origin "$TAG" + +echo "" +echo "Tag ${TAG} has been created and pushed to origin. Check GitHub Actions for build progress:" +echo "https://github.com/LayerLens/atlas-python/actions/workflows/publish-sdk.yaml" +echo "" \ No newline at end of file diff --git a/scripts/template-version.sh b/scripts/template-version.sh new file mode 100644 index 0000000..a8172fd --- /dev/null +++ b/scripts/template-version.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -e + +VERSION_FILE="src/_version.py" + +GIT_COMMIT=$(git rev-parse HEAD) + +sed_inplace() { + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +# Update git commit hash +sed_inplace "s/__GIT_COMMIT__/$GIT_COMMIT/g" "$VERSION_FILE" + +# Get current version +CURRENT_VERSION=$(grep 'VERSION = ' "$VERSION_FILE" | cut -d'"' -f2) + +# If we're uploading to testpypi, add a run number to the version so we can +# test multiple times. +if [[ "$PYPI_REPO" == "testpypi" ]] && [[ -n "$GITHUB_RUN_NUMBER" ]]; then + NEW_VERSION="${CURRENT_VERSION}rc${GITHUB_RUN_NUMBER}" + sed_inplace "s/VERSION = \".*\"/VERSION = \"$NEW_VERSION\"/" "$VERSION_FILE" +fi diff --git a/scripts/validate-release-tag.sh b/scripts/validate-release-tag.sh new file mode 100644 index 0000000..c825a22 --- /dev/null +++ b/scripts/validate-release-tag.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Validate release requirements +# - Checks if the tag matches naming convention (sdk-v*.*.*) +# - Checks if the tag matches the version in the package +# - Ensures we're releasing from the main branch + +set -e + +# Get the tag from the first command line argument +if [ $# -eq 0 ]; then + echo "ERROR: Release tag argument not provided" + echo "Usage: $0 " + exit 1 +fi + +ROOT_DIR=$(git rev-parse --show-toplevel) + +# Fetch the latest tags to ensure we're up to date +git fetch --tags --prune --force + +TAG=$1 + +# Check if tag starts with sdk-v +if [[ ! "$TAG" =~ ^sdk-v ]]; then + echo "ERROR: Tag must start with 'sdk-v'" + exit 1 +fi + +# Extract version without the 'sdk-v' prefix +VERSION=${TAG#sdk-v} + +PACKAGE_VERSION=$(bash "$ROOT_DIR/scripts/get_version.sh") + +# Check if the tag version matches the package version +if [ "$VERSION" != "$PACKAGE_VERSION" ]; then + echo "ERROR: Tag version ($VERSION) does not match package version ($PACKAGE_VERSION)" + exit 1 +fi + +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [ "$CURRENT_BRANCH" != "main" ]; then + # If we're in detached HEAD state (which is likely in GitHub Actions with a tag), + # we need to check if the tag is on the main branch + if ! git rev-parse "$TAG" &>/dev/null; then + echo "ERROR: Tag $TAG does not exist in the repository" + exit 1 + fi + + TAG_COMMIT=$(git rev-parse "$TAG") + + # Ensure we have main branch history + git fetch origin main --depth=1000 + + # Check if tag is on main branch + if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/main; then + echo "ERROR: Tag $TAG is not on the main branch" + exit 1 + fi +fi + +# All checks passed +exit 0 \ No newline at end of file diff --git a/src/_version.py b/src/_version.py new file mode 100644 index 0000000..6b91277 --- /dev/null +++ b/src/_version.py @@ -0,0 +1,4 @@ +VERSION = "0.0.1" + +# Will be templated during the build +GIT_COMMIT = "__GIT_COMMIT__" \ No newline at end of file From 9a2fb705693b932c75abb82cf5c89d25f9d45138 Mon Sep 17 00:00:00 2001 From: Leandro Echevarria Date: Tue, 12 Aug 2025 16:03:34 -0300 Subject: [PATCH 2/4] feat | LAY-885 Added twine to publish the package. Updated scripts. --- .github/workflows/publish-sdk.yaml | 4 +- .github/workflows/test-publish-sdk.yaml | 4 +- pyproject.toml | 1 + requirements-dev.lock | 55 ++++++++++++++++++++++++- requirements.lock | 4 +- scripts/publish.sh | 2 +- scripts/push-release-tag.sh | 0 scripts/validate-release-tag.sh | 16 +++---- 8 files changed, 69 insertions(+), 17 deletions(-) mode change 100644 => 100755 scripts/publish.sh mode change 100644 => 100755 scripts/push-release-tag.sh mode change 100644 => 100755 scripts/validate-release-tag.sh diff --git a/.github/workflows/publish-sdk.yaml b/.github/workflows/publish-sdk.yaml index d0be54f..05c14f3 100644 --- a/.github/workflows/publish-sdk.yaml +++ b/.github/workflows/publish-sdk.yaml @@ -33,8 +33,8 @@ jobs: runs-on: ubuntu-latest env: - PYPI_USERNAME: __token__ - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} RELEASE_TAG: ${{ needs.validate.outputs.release_tag }} steps: diff --git a/.github/workflows/test-publish-sdk.yaml b/.github/workflows/test-publish-sdk.yaml index a982359..ebe789c 100644 --- a/.github/workflows/test-publish-sdk.yaml +++ b/.github/workflows/test-publish-sdk.yaml @@ -19,8 +19,8 @@ jobs: runs-on: ubuntu-latest env: - PYPI_USERNAME: __token__ - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 4628bb8..d6ec64d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ dev-dependencies = [ "ruff", "types-requests", "build", + "twine==6.1.0", ] [tool.rye.scripts] diff --git a/requirements-dev.lock b/requirements-dev.lock index 475183f..9b7ee59 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -14,6 +14,8 @@ annotated-types==0.7.0 # via pydantic anyio==4.9.0 # via httpx +backports-tarfile==1.2.0 + # via jaraco-context build==1.3.0 certifi==2025.7.14 # via httpcore @@ -23,6 +25,8 @@ charset-normalizer==3.4.3 # via requests coverage==7.10.2 # via pytest-cov +docutils==0.22 + # via readme-renderer exceptiongroup==1.3.0 # via anyio # via pytest @@ -31,42 +35,81 @@ h11==0.16.0 httpcore==1.0.9 # via httpx httpx==0.28.1 - # via atlas + # via test-atlas-lzok +id==1.5.0 + # via twine idna==3.10 # via anyio # via httpx # via requests importlib-metadata==8.7.0 # via build + # via keyring + # via twine iniconfig==2.1.0 # via pytest +jaraco-classes==3.4.0 + # via keyring +jaraco-context==6.0.1 + # via keyring +jaraco-functools==4.2.1 + # via keyring +keyring==25.6.0 + # via twine +markdown-it-py==3.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +more-itertools==10.7.0 + # via jaraco-classes + # via jaraco-functools mypy==1.17.0 mypy-extensions==1.1.0 # via mypy +nh3==0.3.0 + # via readme-renderer nodeenv==1.9.1 # via pyright packaging==25.0 # via build # via pytest + # via twine pathspec==0.12.1 # via mypy pluggy==1.6.0 # via pytest # via pytest-cov pydantic==2.11.7 - # via atlas + # via test-atlas-lzok pydantic-core==2.33.2 # via pydantic pygments==2.19.2 # via pytest + # via readme-renderer + # via rich pyproject-hooks==1.2.0 # via build pyright==1.1.399 pytest==8.4.1 # via pytest-cov pytest-cov==6.2.1 +<<<<<<< HEAD requests==2.32.5 # via atlas +======= +readme-renderer==44.0 + # via twine +requests==2.32.4 + # via id + # via requests-toolbelt + # via twine +requests-toolbelt==1.0.0 + # via twine +rfc3986==2.0.0 + # via twine +rich==14.1.0 + # via twine +>>>>>>> a605835 (feat | LAY-885 Added twine to publish the package. Updated scripts.) ruff==0.12.7 sniffio==1.3.1 # via anyio @@ -75,7 +118,11 @@ tomli==2.2.1 # via coverage # via mypy # via pytest +<<<<<<< HEAD types-requests==2.32.4.20250809 +======= +twine==6.1.0 +>>>>>>> a605835 (feat | LAY-885 Added twine to publish the package. Updated scripts.) typing-extensions==4.14.1 # via anyio # via exceptiongroup @@ -88,6 +135,10 @@ typing-inspection==0.4.1 # via pydantic urllib3==2.5.0 # via requests +<<<<<<< HEAD # via types-requests +======= + # via twine +>>>>>>> a605835 (feat | LAY-885 Added twine to publish the package. Updated scripts.) zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index d2ea895..540f4d6 100644 --- a/requirements.lock +++ b/requirements.lock @@ -27,13 +27,13 @@ h11==0.16.0 httpcore==1.0.9 # via httpx httpx==0.28.1 - # via atlas + # via test-atlas-lzok idna==3.10 # via anyio # via httpx # via requests pydantic==2.11.7 - # via atlas + # via test-atlas-lzok pydantic-core==2.33.2 # via pydantic requests==2.32.5 diff --git a/scripts/publish.sh b/scripts/publish.sh old mode 100644 new mode 100755 index 394f8d5..e6ac0f7 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -21,4 +21,4 @@ fi echo "Publishing version $VERSION to $PYPI_REPO" -twine upload --repository "$PYPI_REPO" dist/*"$VERSION"* \ No newline at end of file +twine upload --repository "$PYPI_REPO" dist/* \ No newline at end of file diff --git a/scripts/push-release-tag.sh b/scripts/push-release-tag.sh old mode 100644 new mode 100755 diff --git a/scripts/validate-release-tag.sh b/scripts/validate-release-tag.sh old mode 100644 new mode 100755 index c825a22..175464e --- a/scripts/validate-release-tag.sh +++ b/scripts/validate-release-tag.sh @@ -2,7 +2,7 @@ # Validate release requirements # - Checks if the tag matches naming convention (sdk-v*.*.*) # - Checks if the tag matches the version in the package -# - Ensures we're releasing from the main branch +# - Ensures we're releasing from the release branch set -e @@ -38,9 +38,9 @@ if [ "$VERSION" != "$PACKAGE_VERSION" ]; then fi CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) -if [ "$CURRENT_BRANCH" != "main" ]; then +if [ "$CURRENT_BRANCH" != "release" ]; then # If we're in detached HEAD state (which is likely in GitHub Actions with a tag), - # we need to check if the tag is on the main branch + # we need to check if the tag is on the release branch if ! git rev-parse "$TAG" &>/dev/null; then echo "ERROR: Tag $TAG does not exist in the repository" exit 1 @@ -48,12 +48,12 @@ if [ "$CURRENT_BRANCH" != "main" ]; then TAG_COMMIT=$(git rev-parse "$TAG") - # Ensure we have main branch history - git fetch origin main --depth=1000 + # Ensure we have release branch history + git fetch origin release --depth=1000 - # Check if tag is on main branch - if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/main; then - echo "ERROR: Tag $TAG is not on the main branch" + # Check if tag is on release branch + if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/release; then + echo "ERROR: Tag $TAG is not on the release branch" exit 1 fi fi From a75e15e4edab237ed92d2e98ead3c1a73c070049 Mon Sep 17 00:00:00 2001 From: Leandro Echevarria Date: Wed, 13 Aug 2025 16:37:07 -0300 Subject: [PATCH 3/4] feat | LAY-885 Addressing PR feedback --- .github/workflows/publish-sdk.yaml | 2 ++ .github/workflows/release-tag.yaml | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.github/workflows/publish-sdk.yaml b/.github/workflows/publish-sdk.yaml index 05c14f3..ac0d204 100644 --- a/.github/workflows/publish-sdk.yaml +++ b/.github/workflows/publish-sdk.yaml @@ -12,6 +12,7 @@ on: jobs: validate: runs-on: ubuntu-latest + environment: production outputs: release_tag: ${{ steps.set_release_tag.outputs.release_tag }} steps: @@ -31,6 +32,7 @@ jobs: build-and-publish: needs: validate runs-on: ubuntu-latest + environment: production env: TWINE_USERNAME: __token__ diff --git a/.github/workflows/release-tag.yaml b/.github/workflows/release-tag.yaml index aecf7eb..ae495c3 100644 --- a/.github/workflows/release-tag.yaml +++ b/.github/workflows/release-tag.yaml @@ -17,8 +17,25 @@ on: type: string jobs: + check-branch: + runs-on: ubuntu-latest + environment: production + steps: + - name: Check if running on release branch + run: | + if [ "${{ github.ref }}" != "refs/heads/release" ]; then + echo "Error: This workflow can only be run from the 'release' branch." + echo "Current branch: ${{ github.ref }}" + echo "Please switch to the 'release' branch and try again." + exit 1 + fi + echo "Running on release branch - proceeding with workflow." + create-release-tag: runs-on: ubuntu-latest + needs: check-branch + environment: production + if: github.ref == 'refs/heads/release' permissions: contents: write # Required to create and push tags From 9afc371a16431add421a7bc762ba8125208731ea Mon Sep 17 00:00:00 2001 From: m-peko Date: Fri, 27 Feb 2026 08:36:47 +0100 Subject: [PATCH 4/4] Couple of fixes to release process --- .github/workflows/publish-sdk.yaml | 17 +++++++++-------- .github/workflows/test-publish-sdk.yaml | 15 ++++++++------- Makefile | 13 ++++++------- pyproject.toml | 10 +++++++++- requirements-dev.lock | 18 ++++-------------- scripts/get_version.sh | 10 +--------- scripts/template-version.sh | 6 +++--- src/_version.py | 4 ---- src/layerlens/_version.py | 3 +++ 9 files changed, 43 insertions(+), 53 deletions(-) delete mode 100644 src/_version.py diff --git a/.github/workflows/publish-sdk.yaml b/.github/workflows/publish-sdk.yaml index ac0d204..bc27b0d 100644 --- a/.github/workflows/publish-sdk.yaml +++ b/.github/workflows/publish-sdk.yaml @@ -45,11 +45,12 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.13" - - name: Build, Verify and Publish - working-directory: py - run: | - make install-build-deps - make build + - name: Install build dependencies + run: make install-build-deps + - name: Build + run: make build + - name: Test wheel + run: make test-wheel - name: Upload build artifacts uses: actions/upload-artifact@v4 with: @@ -57,6 +58,6 @@ jobs: path: dist/ retention-days: 5 - name: Publish to PyPI - working-directory: py - run: | - make publish-to-pypi + run: make _publish + env: + PYPI_REPO: pypi diff --git a/.github/workflows/test-publish-sdk.yaml b/.github/workflows/test-publish-sdk.yaml index ebe789c..9703970 100644 --- a/.github/workflows/test-publish-sdk.yaml +++ b/.github/workflows/test-publish-sdk.yaml @@ -21,6 +21,7 @@ jobs: env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} + PYPI_REPO: testpypi steps: - uses: actions/checkout@v4 @@ -31,10 +32,10 @@ jobs: with: python-version: "3.13" - name: Install build dependencies - working-directory: py - run: | - make install-build-deps - - name: Verify, build and publish to TestPyPI - working-directory: py - run: | - make publish-to-testpypi + run: make install-build-deps + - name: Build + run: make build + - name: Test wheel + run: make test-wheel + - name: Publish to TestPyPI + run: make _publish diff --git a/Makefile b/Makefile index ea6227d..e511695 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,14 @@ install-build-deps: - # Install build dependencies using rye - rye sync --all-features + pip install build twine -build: install-build-deps clean _template-version +build: clean _template-version python -m build # Restore the original version file after the build - git checkout src/_version.py + git checkout src/layerlens/_version.py -test-wheel: build - # Test the built wheel - rye run pytest --wheel +test-wheel: + pip install dist/*.whl + python -c "import layerlens; print('Package imported successfully')" clean: rm -rf build dist diff --git a/pyproject.toml b/pyproject.toml index d6ec64d..f5efccc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,14 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "src/layerlens/_version.py" +pattern = '__version__ = "(?P[^"]+)"' + [project] name = "layerlens" -version = "1.2.0" +dynamic = ["version"] description = "The official Python library for the LayerLens Stratix API" license = "Apache-2.0" authors = [{ name = "LayerLens", email = "support@layerlens.ai" }] diff --git a/requirements-dev.lock b/requirements-dev.lock index 9b7ee59..2aaa85b 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -93,14 +93,11 @@ pyright==1.1.399 pytest==8.4.1 # via pytest-cov pytest-cov==6.2.1 -<<<<<<< HEAD -requests==2.32.5 - # via atlas -======= readme-renderer==44.0 # via twine -requests==2.32.4 +requests==2.32.5 # via id + # via layerlens # via requests-toolbelt # via twine requests-toolbelt==1.0.0 @@ -109,7 +106,6 @@ rfc3986==2.0.0 # via twine rich==14.1.0 # via twine ->>>>>>> a605835 (feat | LAY-885 Added twine to publish the package. Updated scripts.) ruff==0.12.7 sniffio==1.3.1 # via anyio @@ -118,11 +114,8 @@ tomli==2.2.1 # via coverage # via mypy # via pytest -<<<<<<< HEAD -types-requests==2.32.4.20250809 -======= twine==6.1.0 ->>>>>>> a605835 (feat | LAY-885 Added twine to publish the package. Updated scripts.) +types-requests==2.32.4.20250809 typing-extensions==4.14.1 # via anyio # via exceptiongroup @@ -135,10 +128,7 @@ typing-inspection==0.4.1 # via pydantic urllib3==2.5.0 # via requests -<<<<<<< HEAD - # via types-requests -======= # via twine ->>>>>>> a605835 (feat | LAY-885 Added twine to publish the package. Updated scripts.) + # via types-requests zipp==3.23.0 # via importlib-metadata diff --git a/scripts/get_version.sh b/scripts/get_version.sh index 42caa27..04ab2da 100755 --- a/scripts/get_version.sh +++ b/scripts/get_version.sh @@ -6,24 +6,16 @@ set -e ROOT_DIR=$(git rev-parse --show-toplevel) VERSION_FILE="$ROOT_DIR/src/layerlens/_version.py" -echo "Debug: ROOT_DIR=$ROOT_DIR" >&2 -echo "Debug: VERSION_FILE=$VERSION_FILE" >&2 - if [ ! -f "$VERSION_FILE" ]; then echo "Error: Version file not found at $VERSION_FILE" >&2 exit 1 fi -echo "Debug: File exists, content:" >&2 -cat "$VERSION_FILE" >&2 - VERSION=$(grep -E '^__version__\s*=' "$VERSION_FILE" | grep -o '".*"' | tr -d '"') -echo "Debug: Extracted version='$VERSION'" >&2 - if [ -z "$VERSION" ]; then echo "Error: Could not extract version from $VERSION_FILE" >&2 exit 1 fi -echo "$VERSION" \ No newline at end of file +echo "$VERSION" diff --git a/scripts/template-version.sh b/scripts/template-version.sh index a8172fd..d3d8b84 100644 --- a/scripts/template-version.sh +++ b/scripts/template-version.sh @@ -2,7 +2,7 @@ set -e -VERSION_FILE="src/_version.py" +VERSION_FILE="src/layerlens/_version.py" GIT_COMMIT=$(git rev-parse HEAD) @@ -18,11 +18,11 @@ sed_inplace() { sed_inplace "s/__GIT_COMMIT__/$GIT_COMMIT/g" "$VERSION_FILE" # Get current version -CURRENT_VERSION=$(grep 'VERSION = ' "$VERSION_FILE" | cut -d'"' -f2) +CURRENT_VERSION=$(grep '__version__ = ' "$VERSION_FILE" | cut -d'"' -f2) # If we're uploading to testpypi, add a run number to the version so we can # test multiple times. if [[ "$PYPI_REPO" == "testpypi" ]] && [[ -n "$GITHUB_RUN_NUMBER" ]]; then NEW_VERSION="${CURRENT_VERSION}rc${GITHUB_RUN_NUMBER}" - sed_inplace "s/VERSION = \".*\"/VERSION = \"$NEW_VERSION\"/" "$VERSION_FILE" + sed_inplace "s/__version__ = \".*\"/__version__ = \"$NEW_VERSION\"/" "$VERSION_FILE" fi diff --git a/src/_version.py b/src/_version.py deleted file mode 100644 index 6b91277..0000000 --- a/src/_version.py +++ /dev/null @@ -1,4 +0,0 @@ -VERSION = "0.0.1" - -# Will be templated during the build -GIT_COMMIT = "__GIT_COMMIT__" \ No newline at end of file diff --git a/src/layerlens/_version.py b/src/layerlens/_version.py index c68196d..8fb65ee 100644 --- a/src/layerlens/_version.py +++ b/src/layerlens/_version.py @@ -1 +1,4 @@ __version__ = "1.2.0" + +# Will be templated during the build +__git_commit__ = "__GIT_COMMIT__"