Skip to content

Commit 0539a0f

Browse files
UN-3479 [FIX] release workflow: validate before tag/publish + lint cleanup
Fixes the issues exposed when the v1.3/v1.4 dispatches blew up: - 10 E501 line-too-long violations in src/unstract/clone/ (caused the lint failure in release run 26506031574) - Release workflow did commit-bump + push-to-main + tag + GH release BEFORE running lint/tests/build, so a lint failure left main with a phantom version bump and an orphan release. Lint/tests/build now run against the bumped __init__.py in-place, and only on success does the workflow commit, tag, release, and PyPI-publish. - Add ruff to the PR gate (test.yml) so lint regressions block at PR time instead of release time. - Revert __version__ from 1.4.0 to 1.2.1 so the next manual trigger produces the intended v1.3.0 cleanly. Orphan v1.4.0 tag + GitHub release have been deleted out-of-band. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 9b5d4d8 commit 0539a0f

12 files changed

Lines changed: 49 additions & 67 deletions

File tree

.github/workflows/main.yml

Lines changed: 35 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -67,53 +67,67 @@ jobs:
6767
- name: Install dependencies
6868
run: uv sync --dev
6969

70-
# Handle workflow_dispatch (manual trigger)
71-
- name: Bump version and create release
70+
# Compute and stage the new version locally — defer commit/tag/release
71+
# until lint+tests+build pass so a verification failure leaves main untouched.
72+
- name: Compute new version
7273
if: github.event_name == 'workflow_dispatch'
73-
id: create_release
74+
id: version
7475
run: |
75-
# Get current version from __init__.py using grep/sed (avoid importing)
7676
CURRENT_VERSION=$(grep -E "^__version__ = " src/unstract/api_deployments/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
7777
echo "Current version: $CURRENT_VERSION"
7878
79-
# Calculate new version based on input
8079
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
8180
MAJOR=${VERSION_PARTS[0]}
8281
MINOR=${VERSION_PARTS[1]}
8382
PATCH=${VERSION_PARTS[2]}
8483
8584
case "${{ github.event.inputs.version_bump }}" in
86-
"major")
87-
MAJOR=$((MAJOR + 1))
88-
MINOR=0
89-
PATCH=0
90-
;;
91-
"minor")
92-
MINOR=$((MINOR + 1))
93-
PATCH=0
94-
;;
95-
"patch")
96-
PATCH=$((PATCH + 1))
97-
;;
85+
"major") MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
86+
"minor") MINOR=$((MINOR + 1)); PATCH=0 ;;
87+
"patch") PATCH=$((PATCH + 1)) ;;
9888
esac
9989
10090
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
10191
echo "New version: $NEW_VERSION"
10292
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
10393
104-
# Update version in __init__.py
10594
sed -i "s/__version__ = \"$CURRENT_VERSION\"/__version__ = \"$NEW_VERSION\"/" src/unstract/api_deployments/__init__.py
10695
107-
# Commit version changes
96+
- name: Verify version update
97+
run: |
98+
PACKAGE_VERSION=$(grep -E "^__version__ = " src/unstract/api_deployments/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
99+
echo "Package version: $PACKAGE_VERSION"
100+
echo "Target version: ${{ steps.version.outputs.version }}"
101+
if [ "$PACKAGE_VERSION" != "${{ steps.version.outputs.version }}" ]; then
102+
echo "Version mismatch! Exiting..."
103+
exit 1
104+
fi
105+
106+
- name: Create test env
107+
run: cp tests/sample.env tests/.env
108+
109+
- name: Run linting
110+
run: uv run ruff check src/
111+
112+
- name: Run tests
113+
run: uv run pytest tests/
114+
115+
- name: Build package
116+
run: uv build
117+
118+
# Only reached if lint/tests/build all passed — safe to commit, tag, release.
119+
- name: Commit version bump and create release
120+
if: github.event_name == 'workflow_dispatch'
121+
run: |
122+
NEW_VERSION="${{ steps.version.outputs.version }}"
123+
108124
git add src/unstract/api_deployments/__init__.py
109125
git commit -m "chore: bump version to $NEW_VERSION [skip ci]"
110126
git push origin main
111127
112-
# Create git tag
113128
git tag "v$NEW_VERSION"
114129
git push origin "v$NEW_VERSION"
115130
116-
# Create GitHub release
117131
RELEASE_NOTES="${{ github.event.inputs.release_notes }}"
118132
if [ -z "$RELEASE_NOTES" ]; then
119133
gh release create "v$NEW_VERSION" \
@@ -132,44 +146,9 @@ jobs:
132146
env:
133147
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
134148

135-
# Set version for subsequent steps
136-
- name: Set version output
137-
id: version
138-
run: |
139-
echo "version=${{ steps.create_release.outputs.version }}" >> $GITHUB_OUTPUT
140-
141-
# Verify the version was updated correctly
142-
- name: Verify version update
143-
run: |
144-
PACKAGE_VERSION=$(grep -E "^__version__ = " src/unstract/api_deployments/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
145-
echo "Package version: $PACKAGE_VERSION"
146-
echo "Target version: ${{ steps.version.outputs.version }}"
147-
if [ "$PACKAGE_VERSION" != "${{ steps.version.outputs.version }}" ]; then
148-
echo "Version mismatch! Exiting..."
149-
exit 1
150-
fi
151-
152-
# Create test environment
153-
- name: Create test env
154-
run: cp tests/sample.env tests/.env
155-
156-
# Run linting
157-
- name: Run linting
158-
run: uv run ruff check src/
159-
160-
# Run tests
161-
- name: Run tests
162-
run: uv run pytest tests/
163-
164-
# Build the package
165-
- name: Build package
166-
run: uv build
167-
168-
# Publish to PyPI using Trusted Publishers
169149
- name: Publish to PyPI
170150
run: uv publish
171151

172-
# Output success message
173152
- name: Success message
174153
run: |
175154
echo "Successfully published version ${{ steps.version.outputs.version }} to PyPI using uv publish with Trusted Publishers"

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@ jobs:
3232
- name: Create test env
3333
run: cp tests/sample.env tests/.env
3434

35+
- name: Lint (ruff)
36+
run: uv run ruff check src/
37+
3538
- name: Tests (pytest)
3639
run: uv run pytest tests/ -v

src/unstract/api_deployments/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.4.0"
1+
__version__ = "1.2.1"
22

33
from .client import APIDeploymentsClient as APIDeploymentsClient
44

src/unstract/clone/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(
1818

1919

2020
class NameConflictError(CloneError):
21-
"""Raised when ``on_name_conflict='abort'`` and the target has a like-named entity."""
21+
"""Raised on name collision when ``on_name_conflict='abort'``."""
2222

2323

2424
class DependencyMissingError(CloneError):

src/unstract/clone/phases/adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def _clone_one(
8383
tgt = existing[0]
8484
if self.ctx.options.on_name_conflict == "abort":
8585
raise NameConflictError(
86-
f"adapter '{name}' [{atype}] already exists in target as {tgt['id']}"
86+
f"adapter '{name}' [{atype}] already on target as {tgt['id']}"
8787
)
8888
with lock:
8989
result.adopted += 1

src/unstract/clone/phases/api_deployment.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def _clone_one(
7676
tgt_wf_id = self.ctx.remap.resolve("workflow", src_wf_id)
7777
if not tgt_wf_id:
7878
logger.warning(
79-
"no workflow remap for api_deployment '%s' (src workflow %s) — skipping",
79+
"no workflow remap for api_deployment '%s' (src wf %s) — skipping",
8080
api_name,
8181
src_wf_id,
8282
)
@@ -99,7 +99,7 @@ def _clone_one(
9999
tgt = existing[0]
100100
if self.ctx.options.on_name_conflict == "abort":
101101
raise NameConflictError(
102-
f"api_deployment '{api_name}' already exists in target as {tgt['id']}"
102+
f"api_deployment '{api_name}' already on target as {tgt['id']}"
103103
)
104104
with lock:
105105
result.adopted += 1

src/unstract/clone/phases/connector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _clone_one(
8686
metadata = src.get("connector_metadata") or {}
8787
if not metadata:
8888
logger.info(
89-
"skipping connector '%s' (src=%s, catalog=%s) — source returned no metadata",
89+
"skipping connector '%s' (src=%s, catalog=%s) — no source metadata",
9090
name,
9191
src_id,
9292
src.get("connector_id"),

src/unstract/clone/phases/custom_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def _create_fresh(
270270
with lock:
271271
result.failed += 1
272272
result.errors.append(
273-
f"import {tool_name}: missing target adapter remap for default profile"
273+
f"import {tool_name}: missing target adapter remap for default"
274274
)
275275
return None
276276

src/unstract/clone/phases/files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ def _ensure_default_doc(
384384
tgt_docs = self.ctx.target.list_prompt_documents(tgt_tool_id)
385385
except Exception as e:
386386
logger.warning(
387-
"files: skipping default-doc set for tool=%s — list tgt docs failed: %s",
387+
"files: skipping default-doc set for tool=%s — tgt list failed: %s",
388388
tool_name,
389389
e,
390390
)

src/unstract/clone/phases/pipeline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def run(self, report: CloneReport) -> PhaseResult:
5555
skipped_types = len(src_pipelines) - len(migratable)
5656
if skipped_types:
5757
logger.info(
58-
"Found %d source pipeline(s); skipping %d of unsupported type (DEFAULT/APP)",
58+
"Found %d source pipeline(s); skipping %d unsupported (DEFAULT/APP)",
5959
len(src_pipelines),
6060
skipped_types,
6161
)

0 commit comments

Comments
 (0)