Skip to content

Commit 99a6a15

Browse files
Lzokm-peko
andauthored
Feat | LAY-885 cicd publish (#7)
* feat | LAY-885 Added ground files for our publishing CICD * feat | LAY-885 Added twine to publish the package. Updated scripts. * feat | LAY-885 Addressing PR feedback * Couple of fixes to release process --------- Co-authored-by: m-peko <marinpeko5@gmail.com>
1 parent dc1c242 commit 99a6a15

13 files changed

Lines changed: 488 additions & 18 deletions

.github/workflows/publish-sdk.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# This workflow is used to publish the Python SDK to the actual PyPI.
2+
# It is triggered by a tag push, and will only publish if the tag is valid.
3+
# The tag must match the format sdk-v*.*.*
4+
5+
name: Publish Python SDK
6+
7+
on:
8+
push:
9+
tags:
10+
- "sdk-v*.*.*" # Trigger on version tags like sdk-v0.1.0 etc.
11+
12+
jobs:
13+
validate:
14+
runs-on: ubuntu-latest
15+
environment: production
16+
outputs:
17+
release_tag: ${{ steps.set_release_tag.outputs.release_tag }}
18+
steps:
19+
- uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0 # Fetch all history for checking branch
22+
- name: Set release tag
23+
id: set_release_tag
24+
# ensure the tag is valid (matches code, is on main, etc)
25+
run: |
26+
RELEASE_TAG=${GITHUB_REF#refs/tags/}
27+
echo "Using tag: $RELEASE_TAG"
28+
./scripts/validate-release-tag.sh "$RELEASE_TAG"
29+
echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV
30+
echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
31+
32+
build-and-publish:
33+
needs: validate
34+
runs-on: ubuntu-latest
35+
environment: production
36+
37+
env:
38+
TWINE_USERNAME: __token__
39+
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
40+
RELEASE_TAG: ${{ needs.validate.outputs.release_tag }}
41+
42+
steps:
43+
- uses: actions/checkout@v4
44+
- name: Set up Python
45+
uses: actions/setup-python@v5
46+
with:
47+
python-version: "3.13"
48+
- name: Install build dependencies
49+
run: make install-build-deps
50+
- name: Build
51+
run: make build
52+
- name: Test wheel
53+
run: make test-wheel
54+
- name: Upload build artifacts
55+
uses: actions/upload-artifact@v4
56+
with:
57+
name: sdk-dist
58+
path: dist/
59+
retention-days: 5
60+
- name: Publish to PyPI
61+
run: make _publish
62+
env:
63+
PYPI_REPO: pypi

.github/workflows/release-tag.yaml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# This workflow creates and pushes a release tag using the push-release-tag.sh script.
2+
# It can be triggered manually and will prompt for confirmation before creating the tag.
3+
4+
name: Create Release Tag
5+
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
dry_run:
10+
description: "Run in dry-run mode (show what would be done without actually creating/pushing the tag)"
11+
required: false
12+
type: boolean
13+
default: true
14+
confirm_release:
15+
description: "Type 'YES' to confirm you want to create and push the release tag"
16+
required: true
17+
type: string
18+
19+
jobs:
20+
check-branch:
21+
runs-on: ubuntu-latest
22+
environment: production
23+
steps:
24+
- name: Check if running on release branch
25+
run: |
26+
if [ "${{ github.ref }}" != "refs/heads/release" ]; then
27+
echo "Error: This workflow can only be run from the 'release' branch."
28+
echo "Current branch: ${{ github.ref }}"
29+
echo "Please switch to the 'release' branch and try again."
30+
exit 1
31+
fi
32+
echo "Running on release branch - proceeding with workflow."
33+
34+
create-release-tag:
35+
runs-on: ubuntu-latest
36+
needs: check-branch
37+
environment: production
38+
if: github.ref == 'refs/heads/release'
39+
40+
permissions:
41+
contents: write # Required to create and push tags
42+
43+
steps:
44+
- name: Validate confirmation
45+
if: github.event.inputs.confirm_release != 'YES' && github.event.inputs.dry_run != 'true'
46+
run: |
47+
echo "Error: You must type 'YES' in the confirm_release input to proceed with creating a release tag."
48+
echo "Received: '${{ github.event.inputs.confirm_release }}'"
49+
exit 1
50+
51+
- uses: actions/checkout@v4
52+
with:
53+
fetch-depth: 0 # Fetch all history and tags
54+
55+
- name: Make scripts executable
56+
run: |
57+
chmod +x scripts/push-release-tag.sh
58+
chmod +x scripts/get_version.sh
59+
60+
- name: Configure Git
61+
run: |
62+
git config --global user.name "github-actions[bot]"
63+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
64+
65+
- name: Run push-release-tag script (dry-run)
66+
if: github.event.inputs.dry_run == 'true'
67+
run: |
68+
echo "Running in dry-run mode..."
69+
make push-release-tag DRY_RUN=--dry-run
70+
71+
- name: Run push-release-tag script
72+
if: github.event.inputs.dry_run != 'true'
73+
env:
74+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75+
run: |
76+
echo "Creating and pushing release tag..."
77+
# Override the interactive confirmation since we already confirmed via workflow input
78+
echo "YES" | make push-release-tag
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# This workflow is used to publish the Python SDK to TestPyPI. Do not need to upgrade the
2+
# version number to use this workflow.
3+
# Only upgrade the version number when you are ready to publish to PyPi
4+
# The script will automatically add an "rc" suffix to the version number for test.pypi.org releases.
5+
6+
name: Publish Python SDK to TestPyPI
7+
8+
on:
9+
workflow_dispatch:
10+
inputs:
11+
ref:
12+
description: "Publish the given Git ref to test.pypi.org (branch, tag, or commit SHA)"
13+
required: true
14+
type: string
15+
default: "main"
16+
17+
jobs:
18+
build-and-publish-test:
19+
runs-on: ubuntu-latest
20+
21+
env:
22+
TWINE_USERNAME: __token__
23+
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
24+
PYPI_REPO: testpypi
25+
26+
steps:
27+
- uses: actions/checkout@v4
28+
with:
29+
ref: ${{ github.event.inputs.ref }}
30+
- name: Set up Python
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: "3.13"
34+
- name: Install build dependencies
35+
run: make install-build-deps
36+
- name: Build
37+
run: make build
38+
- name: Test wheel
39+
run: make test-wheel
40+
- name: Publish to TestPyPI
41+
run: make _publish

Makefile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
install-build-deps:
2+
pip install build twine
3+
4+
build: clean _template-version
5+
python -m build
6+
# Restore the original version file after the build
7+
git checkout src/layerlens/_version.py
8+
9+
test-wheel:
10+
pip install dist/*.whl
11+
python -c "import layerlens; print('Package imported successfully')"
12+
13+
clean:
14+
rm -rf build dist
15+
16+
_publish:
17+
./scripts/publish.sh
18+
19+
_template-version:
20+
@bash scripts/template-version.sh
21+
22+
_check-git-clean:
23+
@if [ -n "$$(git status --porcelain)" ]; then \
24+
echo "Error: Git working directory is not clean. Won't run publish."; \
25+
exit 1; \
26+
fi
27+
28+
_verify-build-publish: _check-git-clean build test-wheel _publish
29+
30+
publish-to-testpypi: export PYPI_REPO := testpypi
31+
publish-to-testpypi: _verify-build-publish
32+
33+
publish-to-pypi: export PYPI_REPO := pypi
34+
publish-to-pypi: _verify-build-publish
35+
36+
push-release-tag:
37+
@bash scripts/push-release-tag.sh $(DRY_RUN)
38+
39+
help:
40+
@echo "Available targets:"
41+
@echo " build - Build Python package"
42+
@echo " clean - Remove build artifacts"
43+
@echo " help - Show this help message"
44+
@echo " install-build-deps - Install build dependencies for CI"
45+
@echo " test-wheel - Run tests against built wheel"
46+
@echo " publish-to-pypi - Publish to PyPI"
47+
@echo " publish-to-testpypi - Publish to TestPyPI"
48+
@echo " push-release-tag - Create and push a release tag"

pyproject.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[tool.hatch.version]
6+
path = "src/layerlens/_version.py"
7+
pattern = '__version__ = "(?P<version>[^"]+)"'
8+
19
[project]
210
name = "layerlens"
3-
version = "1.2.0"
11+
dynamic = ["version"]
412
description = "The official Python library for the LayerLens Stratix API"
513
license = "Apache-2.0"
614
authors = [{ name = "LayerLens", email = "support@layerlens.ai" }]
@@ -30,7 +38,6 @@ Repository = "https://github.com/LayerLens/stratix-python"
3038
[project.scripts]
3139
layerlens = "layerlens.cli:main"
3240

33-
3441
[tool.rye]
3542
managed = true
3643
# version pins are in requirements-dev.lock
@@ -41,6 +48,8 @@ dev-dependencies = [
4148
"pytest-cov>=6.2.1",
4249
"ruff",
4350
"types-requests",
51+
"build",
52+
"twine==6.1.0",
4453
]
4554

4655
[tool.rye.scripts]

requirements-dev.lock

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# last locked with the following flags:
55
# pre: false
66
# features: []
7-
# all-features: false
7+
# all-features: true
88
# with-sources: false
99
# generate-hashes: false
1010
# universal: false
@@ -14,6 +14,9 @@ annotated-types==0.7.0
1414
# via pydantic
1515
anyio==4.9.0
1616
# via httpx
17+
backports-tarfile==1.2.0
18+
# via jaraco-context
19+
build==1.3.0
1720
certifi==2025.7.14
1821
# via httpcore
1922
# via httpx
@@ -22,6 +25,8 @@ charset-normalizer==3.4.3
2225
# via requests
2326
coverage==7.10.2
2427
# via pytest-cov
28+
docutils==0.22
29+
# via readme-renderer
2530
exceptiongroup==1.3.0
2631
# via anyio
2732
# via pytest
@@ -30,44 +35,86 @@ h11==0.16.0
3035
httpcore==1.0.9
3136
# via httpx
3237
httpx==0.28.1
33-
# via atlas
38+
# via test-atlas-lzok
39+
id==1.5.0
40+
# via twine
3441
idna==3.10
3542
# via anyio
3643
# via httpx
3744
# via requests
45+
importlib-metadata==8.7.0
46+
# via build
47+
# via keyring
48+
# via twine
3849
iniconfig==2.1.0
3950
# via pytest
51+
jaraco-classes==3.4.0
52+
# via keyring
53+
jaraco-context==6.0.1
54+
# via keyring
55+
jaraco-functools==4.2.1
56+
# via keyring
57+
keyring==25.6.0
58+
# via twine
59+
markdown-it-py==3.0.0
60+
# via rich
61+
mdurl==0.1.2
62+
# via markdown-it-py
63+
more-itertools==10.7.0
64+
# via jaraco-classes
65+
# via jaraco-functools
4066
mypy==1.17.0
4167
mypy-extensions==1.1.0
4268
# via mypy
69+
nh3==0.3.0
70+
# via readme-renderer
4371
nodeenv==1.9.1
4472
# via pyright
4573
packaging==25.0
74+
# via build
4675
# via pytest
76+
# via twine
4777
pathspec==0.12.1
4878
# via mypy
4979
pluggy==1.6.0
5080
# via pytest
5181
# via pytest-cov
5282
pydantic==2.11.7
53-
# via atlas
83+
# via test-atlas-lzok
5484
pydantic-core==2.33.2
5585
# via pydantic
5686
pygments==2.19.2
5787
# via pytest
88+
# via readme-renderer
89+
# via rich
90+
pyproject-hooks==1.2.0
91+
# via build
5892
pyright==1.1.399
5993
pytest==8.4.1
6094
# via pytest-cov
6195
pytest-cov==6.2.1
96+
readme-renderer==44.0
97+
# via twine
6298
requests==2.32.5
63-
# via atlas
99+
# via id
100+
# via layerlens
101+
# via requests-toolbelt
102+
# via twine
103+
requests-toolbelt==1.0.0
104+
# via twine
105+
rfc3986==2.0.0
106+
# via twine
107+
rich==14.1.0
108+
# via twine
64109
ruff==0.12.7
65110
sniffio==1.3.1
66111
# via anyio
67112
tomli==2.2.1
113+
# via build
68114
# via coverage
69115
# via mypy
70116
# via pytest
117+
twine==6.1.0
71118
types-requests==2.32.4.20250809
72119
typing-extensions==4.14.1
73120
# via anyio
@@ -81,4 +128,7 @@ typing-inspection==0.4.1
81128
# via pydantic
82129
urllib3==2.5.0
83130
# via requests
131+
# via twine
84132
# via types-requests
133+
zipp==3.23.0
134+
# via importlib-metadata

0 commit comments

Comments
 (0)