Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/publish-sdk.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make sure this only runs on the release-branch?

This should never run on main.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This action runs when a new tag is pushed. The tag must follow this pattern sdk-v*.*.* for this action to run.

Then the publish script makes sure we are working with the release branch

tags:
- "sdk-v*.*.*" # Trigger on version tags like sdk-v0.1.0 etc.

jobs:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make sure this job runs on our production github enviornment?

That way it need manual approval before it can run. Like we do in the atlas-app: https://github.com/LayerLens/atlas-app/blob/main/.github/workflows/deploy-production-backend.yaml#L12

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

validate:
runs-on: ubuntu-latest
environment: production
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
environment: production

env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is TWINE? Is that something we setup on Pypi?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: 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:
name: sdk-dist
path: dist/
retention-days: 5
- name: Publish to PyPI
run: make _publish
env:
PYPI_REPO: pypi
78 changes: 78 additions & 0 deletions .github/workflows/release-tag.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# 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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make sure this only can be run on the release branch + in the production enviornment so we ensure manual review is needed for publishing?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

inputs:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we specify major vs minor vs patch releases?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current setup we have, I think we should manually bump the version in the _version file, then release the tag.
At least AFAIK that's the approach of braintrust seeing their PRs

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:
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

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
41 changes: 41 additions & 0 deletions .github/workflows/test-publish-sdk.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 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:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
PYPI_REPO: testpypi

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
run: make install-build-deps
- name: Build
run: make build
- name: Test wheel
run: make test-wheel
- name: Publish to TestPyPI
run: make _publish
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
install-build-deps:
pip install build twine

build: clean _template-version
python -m build
# Restore the original version file after the build
git checkout src/layerlens/_version.py

test-wheel:
pip install dist/*.whl
python -c "import layerlens; print('Package imported successfully')"

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"
13 changes: 11 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "src/layerlens/_version.py"
pattern = '__version__ = "(?P<version>[^"]+)"'

[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" }]
Expand Down Expand Up @@ -30,7 +38,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
Expand All @@ -41,6 +48,8 @@ dev-dependencies = [
"pytest-cov>=6.2.1",
"ruff",
"types-requests",
"build",
"twine==6.1.0",
]

[tool.rye.scripts]
Expand Down
58 changes: 54 additions & 4 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,6 +14,9 @@ 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
# via httpx
Expand All @@ -22,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
Expand All @@ -30,44 +35,86 @@ 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
readme-renderer==44.0
# via twine
requests==2.32.5
# via atlas
# via id
# via layerlens
# via requests-toolbelt
# via twine
requests-toolbelt==1.0.0
# via twine
rfc3986==2.0.0
# via twine
rich==14.1.0
# via twine
ruff==0.12.7
sniffio==1.3.1
# via anyio
tomli==2.2.1
# via build
# via coverage
# via mypy
# via pytest
twine==6.1.0
types-requests==2.32.4.20250809
typing-extensions==4.14.1
# via anyio
Expand All @@ -81,4 +128,7 @@ typing-inspection==0.4.1
# via pydantic
urllib3==2.5.0
# via requests
# via twine
# via types-requests
zipp==3.23.0
# via importlib-metadata
Loading
Loading