diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 59f0af6..e11adf8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,78 +67,31 @@ jobs: - name: Install dependencies run: uv sync --dev - # Handle workflow_dispatch (manual trigger) - - name: Bump version and create release - if: github.event_name == 'workflow_dispatch' - id: create_release + # Compute and stage the new version locally — defer commit/tag/release + # until lint+tests+build+publish all pass, so any failure leaves main untouched. + - name: Compute new version + id: version run: | - # Get current version from __init__.py using grep/sed (avoid importing) CURRENT_VERSION=$(grep -E "^__version__ = " src/unstract/api_deployments/__init__.py | sed -E 's/__version__ = "(.*)"/\1/') echo "Current version: $CURRENT_VERSION" - # Calculate new version based on input IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION" MAJOR=${VERSION_PARTS[0]} MINOR=${VERSION_PARTS[1]} PATCH=${VERSION_PARTS[2]} case "${{ github.event.inputs.version_bump }}" in - "major") - MAJOR=$((MAJOR + 1)) - MINOR=0 - PATCH=0 - ;; - "minor") - MINOR=$((MINOR + 1)) - PATCH=0 - ;; - "patch") - PATCH=$((PATCH + 1)) - ;; + "major") MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; + "minor") MINOR=$((MINOR + 1)); PATCH=0 ;; + "patch") PATCH=$((PATCH + 1)) ;; esac NEW_VERSION="$MAJOR.$MINOR.$PATCH" echo "New version: $NEW_VERSION" echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - # Update version in __init__.py sed -i "s/__version__ = \"$CURRENT_VERSION\"/__version__ = \"$NEW_VERSION\"/" src/unstract/api_deployments/__init__.py - # Commit version changes - git add src/unstract/api_deployments/__init__.py - git commit -m "chore: bump version to $NEW_VERSION [skip ci]" - git push origin main - - # Create git tag - git tag "v$NEW_VERSION" - git push origin "v$NEW_VERSION" - - # Create GitHub release - RELEASE_NOTES="${{ github.event.inputs.release_notes }}" - if [ -z "$RELEASE_NOTES" ]; then - gh release create "v$NEW_VERSION" \ - --title "Release v$NEW_VERSION" \ - --generate-notes \ - ${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }} - else - gh release create "v$NEW_VERSION" \ - --title "Release v$NEW_VERSION" \ - --notes "$RELEASE_NOTES" \ - --generate-notes \ - ${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }} - fi - - echo "Created release v$NEW_VERSION" - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - - # Set version for subsequent steps - - name: Set version output - id: version - run: | - echo "version=${{ steps.create_release.outputs.version }}" >> $GITHUB_OUTPUT - - # Verify the version was updated correctly - name: Verify version update run: | PACKAGE_VERSION=$(grep -E "^__version__ = " src/unstract/api_deployments/__init__.py | sed -E 's/__version__ = "(.*)"/\1/') @@ -149,27 +102,53 @@ jobs: exit 1 fi - # Create test environment - name: Create test env run: cp tests/sample.env tests/.env - # Run linting - name: Run linting run: uv run ruff check src/ - # Run tests - name: Run tests run: uv run pytest tests/ - # Build the package - name: Build package run: uv build - # Publish to PyPI using Trusted Publishers + # PyPI publish first because it is the only step that cannot be undone — + # if a later commit/tag/release step fails, the PyPI artifact is the + # source of truth and the git metadata can be retried manually. - name: Publish to PyPI run: uv publish - # Output success message + - name: Commit version bump and create release + run: | + NEW_VERSION="${{ steps.version.outputs.version }}" + + git add src/unstract/api_deployments/__init__.py + git commit -m "chore: bump version to $NEW_VERSION [skip ci]" + git push origin main + + git tag "v$NEW_VERSION" + git push origin "v$NEW_VERSION" + + RELEASE_NOTES="${{ github.event.inputs.release_notes }}" + if [ -z "$RELEASE_NOTES" ]; then + gh release create "v$NEW_VERSION" \ + --title "Release v$NEW_VERSION" \ + --generate-notes \ + ${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }} + else + gh release create "v$NEW_VERSION" \ + --title "Release v$NEW_VERSION" \ + --notes "$RELEASE_NOTES" \ + --generate-notes \ + ${{ github.event.inputs.pre_release == 'true' && '--prerelease' || '' }} + fi + + echo "Created release v$NEW_VERSION" + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + - name: Success message run: | echo "Successfully published version ${{ steps.version.outputs.version }} to PyPI using uv publish with Trusted Publishers" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97dd996..304fbda 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,5 +32,8 @@ jobs: - name: Create test env run: cp tests/sample.env tests/.env + - name: Lint (ruff) + run: uv run ruff check src/ + - name: Tests (pytest) run: uv run pytest tests/ -v diff --git a/src/unstract/api_deployments/__init__.py b/src/unstract/api_deployments/__init__.py index 094e8a8..bbe774f 100644 --- a/src/unstract/api_deployments/__init__.py +++ b/src/unstract/api_deployments/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.4.0" +__version__ = "1.2.1" from .client import APIDeploymentsClient as APIDeploymentsClient diff --git a/src/unstract/clone/exceptions.py b/src/unstract/clone/exceptions.py index 3933c1c..47feb68 100644 --- a/src/unstract/clone/exceptions.py +++ b/src/unstract/clone/exceptions.py @@ -18,7 +18,7 @@ def __init__( class NameConflictError(CloneError): - """Raised when ``on_name_conflict='abort'`` and the target has a like-named entity.""" + """Raised on name collision when ``on_name_conflict='abort'``.""" class DependencyMissingError(CloneError): diff --git a/src/unstract/clone/phases/adapter.py b/src/unstract/clone/phases/adapter.py index 522629f..c98bf63 100644 --- a/src/unstract/clone/phases/adapter.py +++ b/src/unstract/clone/phases/adapter.py @@ -83,7 +83,7 @@ def _clone_one( tgt = existing[0] if self.ctx.options.on_name_conflict == "abort": raise NameConflictError( - f"adapter '{name}' [{atype}] already exists in target as {tgt['id']}" + f"adapter '{name}' [{atype}] already on target as {tgt['id']}" ) with lock: result.adopted += 1 diff --git a/src/unstract/clone/phases/api_deployment.py b/src/unstract/clone/phases/api_deployment.py index df55983..0d2575f 100644 --- a/src/unstract/clone/phases/api_deployment.py +++ b/src/unstract/clone/phases/api_deployment.py @@ -76,7 +76,7 @@ def _clone_one( tgt_wf_id = self.ctx.remap.resolve("workflow", src_wf_id) if not tgt_wf_id: logger.warning( - "no workflow remap for api_deployment '%s' (src workflow %s) — skipping", + "no workflow remap for api_deployment '%s' (src wf %s) — skipping", api_name, src_wf_id, ) @@ -99,7 +99,7 @@ def _clone_one( tgt = existing[0] if self.ctx.options.on_name_conflict == "abort": raise NameConflictError( - f"api_deployment '{api_name}' already exists in target as {tgt['id']}" + f"api_deployment '{api_name}' already on target as {tgt['id']}" ) with lock: result.adopted += 1 diff --git a/src/unstract/clone/phases/connector.py b/src/unstract/clone/phases/connector.py index 88215da..2a30b93 100644 --- a/src/unstract/clone/phases/connector.py +++ b/src/unstract/clone/phases/connector.py @@ -86,7 +86,7 @@ def _clone_one( metadata = src.get("connector_metadata") or {} if not metadata: logger.info( - "skipping connector '%s' (src=%s, catalog=%s) — source returned no metadata", + "skipping connector '%s' (src=%s, catalog=%s) — no source metadata", name, src_id, src.get("connector_id"), diff --git a/src/unstract/clone/phases/custom_tool.py b/src/unstract/clone/phases/custom_tool.py index c43d64f..03eec4f 100644 --- a/src/unstract/clone/phases/custom_tool.py +++ b/src/unstract/clone/phases/custom_tool.py @@ -270,7 +270,7 @@ def _create_fresh( with lock: result.failed += 1 result.errors.append( - f"import {tool_name}: missing target adapter remap for default profile" + f"import {tool_name}: missing target adapter remap for default" ) return None diff --git a/src/unstract/clone/phases/files.py b/src/unstract/clone/phases/files.py index 22de854..a241fc1 100644 --- a/src/unstract/clone/phases/files.py +++ b/src/unstract/clone/phases/files.py @@ -384,7 +384,7 @@ def _ensure_default_doc( tgt_docs = self.ctx.target.list_prompt_documents(tgt_tool_id) except Exception as e: logger.warning( - "files: skipping default-doc set for tool=%s — list tgt docs failed: %s", + "files: skipping default-doc set for tool=%s — tgt list failed: %s", tool_name, e, ) diff --git a/src/unstract/clone/phases/pipeline.py b/src/unstract/clone/phases/pipeline.py index 9892b1c..cdad5f0 100644 --- a/src/unstract/clone/phases/pipeline.py +++ b/src/unstract/clone/phases/pipeline.py @@ -55,7 +55,7 @@ def run(self, report: CloneReport) -> PhaseResult: skipped_types = len(src_pipelines) - len(migratable) if skipped_types: logger.info( - "Found %d source pipeline(s); skipping %d of unsupported type (DEFAULT/APP)", + "Found %d source pipeline(s); skipping %d unsupported (DEFAULT/APP)", len(src_pipelines), skipped_types, ) diff --git a/src/unstract/clone/phases/tool_instance.py b/src/unstract/clone/phases/tool_instance.py index 293d206..4b80383 100644 --- a/src/unstract/clone/phases/tool_instance.py +++ b/src/unstract/clone/phases/tool_instance.py @@ -103,7 +103,7 @@ def _clone_workflow_tools( return if len(src_instances) > 1: logger.warning( - "source workflow %s has %d tool_instances (expected ≤1) — migrating first only", + "source workflow %s has %d tool_instances (>1) — migrating first only", src_wf_id, len(src_instances), ) diff --git a/src/unstract/clone/report.py b/src/unstract/clone/report.py index 4bec96a..a0f63d1 100644 --- a/src/unstract/clone/report.py +++ b/src/unstract/clone/report.py @@ -27,7 +27,7 @@ class PhaseResult: @dataclass class Endpoint: - """Just enough about an endpoint for the report header — never carries the API key.""" + """Endpoint identity for the report header — never carries the API key.""" base_url: str organization_id: str