pipelex-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pipelex_sdk-0.1.0/.claude/skills/release/SKILL.md +133 -0
- pipelex_sdk-0.1.0/.github/workflows/changelog-check.yml +32 -0
- pipelex_sdk-0.1.0/.github/workflows/cla.yml +42 -0
- pipelex_sdk-0.1.0/.github/workflows/guard-branches.yml +100 -0
- pipelex_sdk-0.1.0/.github/workflows/lint-check.yml +65 -0
- pipelex_sdk-0.1.0/.github/workflows/package-check.yml +27 -0
- pipelex_sdk-0.1.0/.github/workflows/publish.yml +172 -0
- pipelex_sdk-0.1.0/.github/workflows/tests-check.yml +53 -0
- pipelex_sdk-0.1.0/.github/workflows/version-check.yml +74 -0
- pipelex_sdk-0.1.0/.gitignore +14 -0
- pipelex_sdk-0.1.0/.vscode/extensions.json +7 -0
- pipelex_sdk-0.1.0/.vscode/settings.json +28 -0
- pipelex_sdk-0.1.0/CHANGELOG.md +33 -0
- pipelex_sdk-0.1.0/CLA.md +94 -0
- pipelex_sdk-0.1.0/CLAUDE.md +110 -0
- pipelex_sdk-0.1.0/LICENSE +21 -0
- pipelex_sdk-0.1.0/Makefile +309 -0
- pipelex_sdk-0.1.0/PKG-INFO +133 -0
- pipelex_sdk-0.1.0/README.md +97 -0
- pipelex_sdk-0.1.0/docs/HANDOFF.md +56 -0
- pipelex_sdk-0.1.0/docs/architecture.md +172 -0
- pipelex_sdk-0.1.0/docs/ci-cd.md +36 -0
- pipelex_sdk-0.1.0/pipelex_sdk/__init__.py +0 -0
- pipelex_sdk-0.1.0/pipelex_sdk/_compat.py +10 -0
- pipelex_sdk-0.1.0/pipelex_sdk/_pydantic_utils.py +15 -0
- pipelex_sdk-0.1.0/pipelex_sdk/client.py +1024 -0
- pipelex_sdk-0.1.0/pipelex_sdk/errors.py +147 -0
- pipelex_sdk-0.1.0/pipelex_sdk/product_models.py +360 -0
- pipelex_sdk-0.1.0/pipelex_sdk/runs.py +198 -0
- pipelex_sdk-0.1.0/pipelex_sdk/validation_models.py +121 -0
- pipelex_sdk-0.1.0/pipelex_sdk/version.py +23 -0
- pipelex_sdk-0.1.0/pyproject.toml +363 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_construction.py +90 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_execute.py +96 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_health.py +59 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_lifecycle.py +249 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_product.py +396 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_run_fallback.py +187 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_transport.py +108 -0
- pipelex_sdk-0.1.0/tests/unit/test_client_validate.py +128 -0
- pipelex_sdk-0.1.0/tests/unit/test_error_parsing.py +45 -0
- pipelex_sdk-0.1.0/tests/unit/test_runs.py +31 -0
- pipelex_sdk-0.1.0/tests/unit/test_validation_contract.py +199 -0
- pipelex_sdk-0.1.0/tests/unit/test_version.py +35 -0
- pipelex_sdk-0.1.0/uv.lock +750 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: >
|
|
4
|
+
Automates the pipelex-sdk-python release workflow: bumps the version in pyproject.toml, finalizes the CHANGELOG.md Unreleased section, runs quality checks, regenerates uv.lock, creates a release/vX.Y.Z branch, commits, pushes, and opens a PR to main. Use when user says "release", "cut a release", "bump version", "prepare a release", "make a release", "ship it", "create release branch", or any variation of shipping a new version of the pipelex-sdk Python package. The user can optionally provide changelog content inline when invoking the skill (e.g. "/release Added the storage routes"), which will be used as the changelog entry for this version.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# pipelex-sdk-python Release Workflow
|
|
8
|
+
|
|
9
|
+
This skill handles the full release cycle for the `pipelex-sdk` Python package (import package `pipelex_sdk`, the `pipelex-sdk-python` repo). A release is a `release/vX.Y.Z` branch that PRs into `main`; merging to `main` triggers `publish.yml`, which builds the wheel, publishes it to PyPI as `pipelex-sdk` via Trusted Publishing (OIDC, no token), and creates a Sigstore-signed GitHub release from the changelog notes.
|
|
10
|
+
|
|
11
|
+
## Files touched
|
|
12
|
+
|
|
13
|
+
- **`pyproject.toml`** — the `version` field (line 3, under `[project]`)
|
|
14
|
+
- **`CHANGELOG.md`** — add `## [vX.Y.Z] - YYYY-MM-DD` entry (convert the `## [Unreleased]` section if present)
|
|
15
|
+
- **`uv.lock`** — regenerated via `make li` (lock + install)
|
|
16
|
+
|
|
17
|
+
## Workflow
|
|
18
|
+
|
|
19
|
+
### 1. Pre-flight checks
|
|
20
|
+
|
|
21
|
+
- Read the current version from `pyproject.toml`.
|
|
22
|
+
- Read `CHANGELOG.md` to understand the current state (this repo keeps a `## [Unreleased]` section at the top).
|
|
23
|
+
- Run `git status` and `git log origin/main..HEAD` to assess the working tree:
|
|
24
|
+
- If there are **uncommitted changes** (staged or unstaged), warn the user and ask whether to commit them as part of the release, stash them, or abort.
|
|
25
|
+
- If there are **unpushed commits** on the current branch, list them so the user is aware — these will be included in the release branch.
|
|
26
|
+
|
|
27
|
+
### 2. Determine the bump type
|
|
28
|
+
|
|
29
|
+
Ask the user which kind of version bump they want — **patch**, **minor**, or **major** — unless they already specified it. Show the current version and what the new version would be for each option so the choice is concrete.
|
|
30
|
+
|
|
31
|
+
While the package is pre-1.0 (`0.y.z`), treat the `0.MINOR.PATCH` segments the way the project has been using them: a breaking change bumps the minor, a backward-compatible feature or fix bumps the patch. If the changelog for this release contains a `### Breaking Changes` section (or otherwise describes a breaking change), steer the user toward at least a minor bump — this matches the repo's "pre-1.0 breaking changes → minor version bump" rule.
|
|
32
|
+
|
|
33
|
+
### 3. Run quality checks
|
|
34
|
+
|
|
35
|
+
Run `make agent-check`. This is the gate — if it fails, stop and report the errors so they can be fixed before retrying. Do not proceed past this step on failure.
|
|
36
|
+
|
|
37
|
+
### 4. Ensure we're on the right branch
|
|
38
|
+
|
|
39
|
+
The release branch must be named `release/vX.Y.Z` where X.Y.Z is the **new** version. The CI guards in this repo are strict about this:
|
|
40
|
+
|
|
41
|
+
- `guard-branches.yml` (`gate-main`) rejects any source branch other than `release/vX.Y.Z` merging into `main`.
|
|
42
|
+
- `version-check.yml` rejects a mismatch between the branch name and the `pyproject.toml` version.
|
|
43
|
+
|
|
44
|
+
Both guards match the **exact** regex `release/v[0-9]+\.[0-9]+\.[0-9]+` (strict three-segment semver, no suffix). All file modifications (changelog, version bump, lock) must happen on this branch.
|
|
45
|
+
|
|
46
|
+
- If already on `release/vX.Y.Z` matching the new version, stay on it.
|
|
47
|
+
- If on `dev`, `main`, or any other branch, create and switch to `release/vX.Y.Z` from the current HEAD.
|
|
48
|
+
- If on a `release/` branch for a **different** version, warn the user and ask how to proceed.
|
|
49
|
+
|
|
50
|
+
### 5. Finalize the changelog
|
|
51
|
+
|
|
52
|
+
Add a new version entry for the release. This repo uses the workspace-wide `## [vX.Y.Z]` header convention (the changelog and publish workflows key off it).
|
|
53
|
+
|
|
54
|
+
1. If there is an `## [Unreleased]` section, **convert it**: remove the `## [Unreleased]` heading (and any blank lines that immediately follow it) and replace it with the new `## [vX.Y.Z] - YYYY-MM-DD` heading. Any content that was under `[Unreleased]` becomes the content of the new version.
|
|
55
|
+
2. If there is no `[Unreleased]` section, insert the new version heading directly after the `# Changelog` intro block.
|
|
56
|
+
3. **Never recreate an `[Unreleased]` heading.** After a release the changelog should contain only concrete version entries — the next change adds a fresh `## [Unreleased]` section organically when someone starts the next cycle.
|
|
57
|
+
4. If the user provided changelog content when invoking the skill (e.g. `/release Added the storage routes`), **merge** that content with any existing `[Unreleased]` content (do not discard either source). Format the combined content under the appropriate headings — this repo uses `### Breaking Changes`, `### Added`, `### Changed`, `### Fixed`, `### Removed` — inferring headings from the content when possible.
|
|
58
|
+
5. If the release has no changelog content yet (neither from an `[Unreleased]` section nor from inline user input), ask the user what to include before proceeding.
|
|
59
|
+
6. The result should look like:
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
# Changelog
|
|
63
|
+
|
|
64
|
+
All notable changes to `pipelex-sdk` are documented here. ...
|
|
65
|
+
|
|
66
|
+
## [vX.Y.Z] - YYYY-MM-DD
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
- ...
|
|
70
|
+
|
|
71
|
+
## [vPREVIOUS] - PREVIOUS-DATE
|
|
72
|
+
...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 6. Bump the version in pyproject.toml
|
|
76
|
+
|
|
77
|
+
Edit `pyproject.toml` line 3 (`version = "..."` under `[project]`) to the new version string. Only change the version field — don't touch anything else.
|
|
78
|
+
|
|
79
|
+
### 7. Lock dependencies
|
|
80
|
+
|
|
81
|
+
Run `make li` to regenerate `uv.lock` and reinstall. This ensures the lockfile reflects the new version in `pyproject.toml`. The `package-check.yml` CI job runs `uv lock --locked` and fails the PR if `uv.lock` is out of sync, so this step is not optional. If it fails, stop and report the error.
|
|
82
|
+
|
|
83
|
+
### 8. Commit and push
|
|
84
|
+
|
|
85
|
+
Stage all release-related changes. This includes at minimum `pyproject.toml`, `CHANGELOG.md`, and `uv.lock`, plus any other files the user chose to include in step 1 (e.g. previously uncommitted work that belongs in this release).
|
|
86
|
+
|
|
87
|
+
Commit with the message:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
Release vX.Y.Z
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Push the branch to origin with `-u` to set up tracking.
|
|
94
|
+
|
|
95
|
+
### 9. Open a PR
|
|
96
|
+
|
|
97
|
+
Create a pull request targeting `main` with:
|
|
98
|
+
|
|
99
|
+
- **Title:** `Release vX.Y.Z`
|
|
100
|
+
- **Body:** Include:
|
|
101
|
+
- The changelog entries for this version (copied from CHANGELOG.md)
|
|
102
|
+
- A note about the version bump from old to new
|
|
103
|
+
|
|
104
|
+
Use this format for the PR body:
|
|
105
|
+
|
|
106
|
+
```markdown
|
|
107
|
+
## Release vX.Y.Z
|
|
108
|
+
|
|
109
|
+
Bumps version from `A.B.C` to `X.Y.Z`.
|
|
110
|
+
|
|
111
|
+
### Changelog
|
|
112
|
+
|
|
113
|
+
<paste the changelog entries for this version here>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Report the PR URL back to the user, and remind them that **merging the PR into `main` is what publishes** — `publish.yml` builds the wheel, pushes it to PyPI as `pipelex-sdk` (Trusted Publishing), and cuts the Sigstore-signed GitHub release automatically. Nothing publishes until the PR is merged.
|
|
117
|
+
|
|
118
|
+
## Important details
|
|
119
|
+
|
|
120
|
+
- The version follows semver: `MAJOR.MINOR.PATCH`.
|
|
121
|
+
- Always confirm the bump type with the user before making changes.
|
|
122
|
+
- If `make agent-check` fails, the release is blocked — help the user fix the issues rather than skipping the checks.
|
|
123
|
+
- The CI gates a `release/vX.Y.Z` → `main` PR with:
|
|
124
|
+
- `version-check.yml` — the `pyproject.toml` version must match the `release/vX.Y.Z` branch name.
|
|
125
|
+
- `changelog-check.yml` — `CHANGELOG.md` must contain a `## [vX.Y.Z] -` entry for the new version.
|
|
126
|
+
- `package-check.yml` — `uv.lock` must be in sync with `pyproject.toml` (`uv lock --locked`).
|
|
127
|
+
- `tests-check.yml` — the test matrix must pass on every supported Python version (3.10 through 3.14).
|
|
128
|
+
- `lint-check.yml` — ruff format, ruff lint, pyright, and mypy merge checks across the same Python matrix (the same gates as `make agent-check`).
|
|
129
|
+
- `guard-branches.yml` — only `release/vX.Y.Z` branches may target `main`.
|
|
130
|
+
- `cla.yml` — the PR author must have signed the Pipelex CLA (maintainers are allow-listed; an external first-time author will be prompted to sign before the PR can merge).
|
|
131
|
+
- All checks must pass for the PR to be mergeable, so getting the changelog, version, and lockfile right is critical.
|
|
132
|
+
- **Pre-release versions are not supported through this flow.** Unlike `mthds-python`, this repo's `guard-branches.yml` (`gate-main`) and `version-check.yml` both match the exact regex `release/v[0-9]+\.[0-9]+\.[0-9]+` — a PEP 440 suffix (`a`/`b`/`rc`, e.g. `0.2.0rc1`) on a `release/v0.2.0rc1` branch would be **rejected** by the branch guard even though `publish.yml` can detect pre-releases. Stick to strict three-segment versions for the `release/vX.Y.Z` → `main` flow; raise it with the user if they ask for a pre-release.
|
|
133
|
+
- Today's date for the changelog entry: use the current date in `YYYY-MM-DD` format.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Changelog Version Check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
types: [opened, synchronize, reopened]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
check-changelog:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Check Changelog Version
|
|
16
|
+
run: |
|
|
17
|
+
# Get version from pyproject.toml
|
|
18
|
+
VERSION=$(grep -m 1 'version = ' pyproject.toml | cut -d '"' -f 2)
|
|
19
|
+
echo "Version from pyproject.toml: $VERSION"
|
|
20
|
+
|
|
21
|
+
# Look for the version in the changelog
|
|
22
|
+
if ! grep -q "## \[v$VERSION\] -" CHANGELOG.md; then
|
|
23
|
+
echo "❌ Error: No changelog entry found for version v$VERSION"
|
|
24
|
+
echo ""
|
|
25
|
+
echo "The following versions are in the changelog:"
|
|
26
|
+
grep -E "^## \[v[0-9]+" CHANGELOG.md | head -10
|
|
27
|
+
echo ""
|
|
28
|
+
echo "Please add a changelog entry: ## [v$VERSION] - YYYY-MM-DD"
|
|
29
|
+
exit 1
|
|
30
|
+
else
|
|
31
|
+
echo "✅ Changelog entry found for version v$VERSION"
|
|
32
|
+
fi
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: "CLA Assistant bot"
|
|
2
|
+
on:
|
|
3
|
+
issue_comment:
|
|
4
|
+
types: [created]
|
|
5
|
+
pull_request_target:
|
|
6
|
+
types: [opened, closed, synchronize]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
actions: write
|
|
10
|
+
contents: read
|
|
11
|
+
pull-requests: write
|
|
12
|
+
statuses: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
CLAAssistant:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- name: Get GitHub App token
|
|
19
|
+
id: app-token
|
|
20
|
+
uses: actions/create-github-app-token@v3
|
|
21
|
+
with:
|
|
22
|
+
app-id: ${{ secrets.CLA_GH_APP_ID }}
|
|
23
|
+
private-key: ${{ secrets.CLA_GH_APP_PRIVATE_KEY }}
|
|
24
|
+
owner: Pipelex
|
|
25
|
+
repositories: |
|
|
26
|
+
cla-signatures
|
|
27
|
+
pipelex-sdk-python
|
|
28
|
+
|
|
29
|
+
- name: "CLA Assistant"
|
|
30
|
+
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
|
31
|
+
uses: contributor-assistant/github-action@v2.6.1
|
|
32
|
+
env:
|
|
33
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
|
+
PERSONAL_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
35
|
+
with:
|
|
36
|
+
path-to-signatures: "signatures/version1/cla.json"
|
|
37
|
+
path-to-document: "https://github.com/Pipelex/pipelex-sdk-python/blob/main/CLA.md"
|
|
38
|
+
branch: main
|
|
39
|
+
allowlist: lchoquel,thomashebrard,bot*
|
|
40
|
+
remote-organization-name: Pipelex
|
|
41
|
+
remote-repository-name: cla-signatures
|
|
42
|
+
signed-commit-message: "$contributorName has signed the CLA in $owner/$repo#$pullRequestNo"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
name: Guard branch flow
|
|
2
|
+
on:
|
|
3
|
+
pull_request_target:
|
|
4
|
+
types: [opened, edited, synchronize, reopened]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
# ───────────────────────────────────────────────────────────────
|
|
8
|
+
# 1) Only release/vX.Y.Z → main
|
|
9
|
+
# ───────────────────────────────────────────────────────────────
|
|
10
|
+
gate-main:
|
|
11
|
+
if: github.event.pull_request.base.ref == 'main'
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Verify source branch is a Release
|
|
15
|
+
env:
|
|
16
|
+
HEAD: ${{ github.event.pull_request.head.ref }}
|
|
17
|
+
run: |
|
|
18
|
+
echo "PR → main from $HEAD"
|
|
19
|
+
if [[ ! "$HEAD" =~ ^release\/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
20
|
+
echo "::error::Only release/vX.Y.Z branches may merge into main."
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# ───────────────────────────────────────────────────────────────
|
|
25
|
+
# 2) Only work-branches → release/vX.Y.Z, pre-release/vX.Y.Z..., or dev
|
|
26
|
+
# ───────────────────────────────────────────────────────────────
|
|
27
|
+
gate-release:
|
|
28
|
+
if: startsWith(github.event.pull_request.base.ref, 'release/v') || startsWith(github.event.pull_request.base.ref, 'pre-release/v') || github.event.pull_request.base.ref == 'dev'
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- name: Verify source branch uses allowed prefix
|
|
32
|
+
env:
|
|
33
|
+
HEAD: ${{ github.event.pull_request.head.ref }}
|
|
34
|
+
run: |
|
|
35
|
+
echo "PR → ${{ github.event.pull_request.base.ref }} from $HEAD"
|
|
36
|
+
if [[ "$HEAD" == "dev" ]]; then
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
if [[ ! "$HEAD" =~ ^(fix|feature|refactor|chore|docs|ci-cd|changelog|codex)\/[A-Za-z0-9._\/\<\>\=\-]+$ ]]; then
|
|
40
|
+
echo "::error::Branch must start with fix/, feature/, refactor/, chore/, docs/, or ci-cd/."
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# ───────────────────────────────────────────────────────────────
|
|
45
|
+
# 3) Prevent forks from editing your workflows
|
|
46
|
+
# ───────────────────────────────────────────────────────────────
|
|
47
|
+
protect-workflows:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
# Least privilege: querying the author's permission and diffing the head only needs read.
|
|
50
|
+
permissions:
|
|
51
|
+
contents: read
|
|
52
|
+
steps:
|
|
53
|
+
# Trust must hinge on the author's EFFECTIVE repository permission, not author_association.
|
|
54
|
+
# author_association is a social label: an org MEMBER or a COLLABORATOR can hold read-only
|
|
55
|
+
# access, so an association allow-list would let a read-only insider's workflow edits slip
|
|
56
|
+
# past this guard. Resolve the real permission and treat only write/maintain/admin as trusted.
|
|
57
|
+
- name: Resolve author repository permission
|
|
58
|
+
id: perm
|
|
59
|
+
uses: actions/github-script@v7
|
|
60
|
+
with:
|
|
61
|
+
script: |
|
|
62
|
+
const username = context.payload.pull_request.user.login;
|
|
63
|
+
let data = { permission: 'none', role_name: 'none' };
|
|
64
|
+
try {
|
|
65
|
+
({ data } = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
66
|
+
owner: context.repo.owner,
|
|
67
|
+
repo: context.repo.repo,
|
|
68
|
+
username,
|
|
69
|
+
}));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// A 404 means the author is not a resolvable collaborator (deleted/renamed
|
|
72
|
+
// account, or no access) — treat as untrusted and let the workflow-diff
|
|
73
|
+
// check run. Re-throw anything else so a transient API failure fails closed.
|
|
74
|
+
if (error.status !== 404) throw error;
|
|
75
|
+
}
|
|
76
|
+
// The legacy `permission` field collapses roles: admin → "admin",
|
|
77
|
+
// maintain & write → "write", triage & read → "read", none → "none".
|
|
78
|
+
const trusted = data.permission === 'admin' || data.permission === 'write';
|
|
79
|
+
core.info(`Author ${username}: permission=${data.permission} role=${data.role_name} trusted=${trusted}`);
|
|
80
|
+
core.setOutput('trusted', trusted ? 'true' : 'false');
|
|
81
|
+
|
|
82
|
+
- name: Checkout code
|
|
83
|
+
if: steps.perm.outputs.trusted != 'true'
|
|
84
|
+
uses: actions/checkout@v4
|
|
85
|
+
with:
|
|
86
|
+
# In pull_request_target the default checkout is the BASE branch; without this the
|
|
87
|
+
# diff below would compare base-against-base and never see the fork's changes. We
|
|
88
|
+
# only fetch/diff/grep here — the untrusted head is never executed — so checking
|
|
89
|
+
# out the PR head SHA is safe.
|
|
90
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
91
|
+
fetch-depth: 0
|
|
92
|
+
- name: Detect workflow changes
|
|
93
|
+
if: steps.perm.outputs.trusted != 'true'
|
|
94
|
+
run: |
|
|
95
|
+
git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1
|
|
96
|
+
CHANGED=$(git diff --name-only FETCH_HEAD HEAD | grep -E '^\.github/workflows/.*\.ya?ml$' || true)
|
|
97
|
+
if [ -n "$CHANGED" ]; then
|
|
98
|
+
echo "::error::External contributors may not modify workflow files: $CHANGED"
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Lint check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
# --------------------------------------------------------------------------
|
|
8
|
+
# 1. Matrix job — one runner *per* Python version
|
|
9
|
+
# --------------------------------------------------------------------------
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint (${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
17
|
+
env:
|
|
18
|
+
VIRTUAL_ENV: ${{ github.workspace }}/.venv
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v4
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Check UV installation
|
|
29
|
+
run: make check-uv
|
|
30
|
+
|
|
31
|
+
- name: Verify UV installation
|
|
32
|
+
run: uv --version
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: PYTHON_VERSION=${{ matrix.python-version }} TEST_PROFILE=ci make install
|
|
36
|
+
|
|
37
|
+
- name: Run ruff format merge check
|
|
38
|
+
run: make merge-check-ruff-format
|
|
39
|
+
|
|
40
|
+
- name: Run ruff lint merge check
|
|
41
|
+
run: make merge-check-ruff-lint
|
|
42
|
+
|
|
43
|
+
- name: Run pyright merge check
|
|
44
|
+
run: make merge-check-pyright
|
|
45
|
+
|
|
46
|
+
- name: Run mypy merge check
|
|
47
|
+
run: make merge-check-mypy
|
|
48
|
+
|
|
49
|
+
# --------------------------------------------------------------------------
|
|
50
|
+
# 2. Aggregator job — the *single* required status check
|
|
51
|
+
# --------------------------------------------------------------------------
|
|
52
|
+
lint-all:
|
|
53
|
+
name: Lint (all versions)
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
needs: lint # wait for every matrix leg
|
|
56
|
+
if: always() # run even if one leg already failed
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
- name: Fail if any matrix leg failed
|
|
60
|
+
run: |
|
|
61
|
+
if [ "${{ needs.lint.result }}" != "success" ]; then
|
|
62
|
+
echo "::error::At least one Python version failed linting."
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
echo "✅ All Python versions passed lint checks."
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: package-check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
uv-lock-check:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- name: Checkout code
|
|
11
|
+
uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- name: Install uv
|
|
14
|
+
uses: astral-sh/setup-uv@v3
|
|
15
|
+
with:
|
|
16
|
+
enable-cache: true
|
|
17
|
+
|
|
18
|
+
- name: Check if uv.lock is up to date
|
|
19
|
+
run: |
|
|
20
|
+
uv lock --locked
|
|
21
|
+
if ! git diff --exit-code uv.lock; then
|
|
22
|
+
echo "❌ uv.lock is out of date!"
|
|
23
|
+
echo "Please run 'uv lock' to update the lock file."
|
|
24
|
+
exit 1
|
|
25
|
+
else
|
|
26
|
+
echo "✅ uv.lock is up to date!"
|
|
27
|
+
fi
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
name: Publish Python 🐍 distribution 📦 to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build distribution 📦
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
persist-credentials: false
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.x"
|
|
21
|
+
- name: Install pypa/build
|
|
22
|
+
run: >-
|
|
23
|
+
python3 -m
|
|
24
|
+
pip install
|
|
25
|
+
build
|
|
26
|
+
--user
|
|
27
|
+
- name: Build a binary wheel and a source tarball
|
|
28
|
+
run: python3 -m build
|
|
29
|
+
- name: Store the distribution packages
|
|
30
|
+
uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: python-package-distributions
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish-to-pypi:
|
|
36
|
+
name: >-
|
|
37
|
+
Publish Python 🐍 distribution 📦 to PyPI
|
|
38
|
+
needs:
|
|
39
|
+
- build
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
environment:
|
|
42
|
+
name: pypi
|
|
43
|
+
url: https://pypi.org/p/pipelex-sdk
|
|
44
|
+
permissions:
|
|
45
|
+
id-token: write # IMPORTANT: mandatory for trusted publishing
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- name: Download all the dists
|
|
49
|
+
uses: actions/download-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: python-package-distributions
|
|
52
|
+
path: dist/
|
|
53
|
+
- name: Publish distribution 📦 to PyPI
|
|
54
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
55
|
+
|
|
56
|
+
github-release:
|
|
57
|
+
name: >-
|
|
58
|
+
Create GitHub Release with Changelog
|
|
59
|
+
needs:
|
|
60
|
+
- build
|
|
61
|
+
- publish-to-pypi
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
|
|
64
|
+
permissions:
|
|
65
|
+
contents: write # IMPORTANT: mandatory for making GitHub Releases
|
|
66
|
+
id-token: write # IMPORTANT: mandatory for sigstore
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
with:
|
|
71
|
+
persist-credentials: false
|
|
72
|
+
- name: Extract version and detect pre-release
|
|
73
|
+
id: get_version
|
|
74
|
+
run: |
|
|
75
|
+
VERSION=$(grep -m 1 'version = ' pyproject.toml | cut -d '"' -f 2)
|
|
76
|
+
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
77
|
+
|
|
78
|
+
# Detect if version is a pre-release (PEP 440: contains a, b, or rc)
|
|
79
|
+
if [[ "$VERSION" =~ (a|b|rc)[0-9]+$ ]]; then
|
|
80
|
+
echo "IS_PRERELEASE=true" >> $GITHUB_ENV
|
|
81
|
+
echo "Detected pre-release version: $VERSION"
|
|
82
|
+
else
|
|
83
|
+
echo "IS_PRERELEASE=false" >> $GITHUB_ENV
|
|
84
|
+
echo "Detected stable version: $VERSION"
|
|
85
|
+
fi
|
|
86
|
+
- name: Extract changelog notes for current version
|
|
87
|
+
id: get_changelog
|
|
88
|
+
run: |
|
|
89
|
+
VERSION="${{ env.VERSION }}"
|
|
90
|
+
echo "Extracting changelog for version v$VERSION"
|
|
91
|
+
|
|
92
|
+
# Find the start of the current version section
|
|
93
|
+
START_LINE=$(grep -n "## \[v$VERSION\] - " CHANGELOG.md | cut -d: -f1)
|
|
94
|
+
|
|
95
|
+
if [ -z "$START_LINE" ]; then
|
|
96
|
+
echo "Warning: No changelog entry found for version v$VERSION"
|
|
97
|
+
echo "CHANGELOG_NOTES=" >> $GITHUB_ENV
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Find the start of the next version section (previous version)
|
|
102
|
+
NEXT_VERSION_LINE=$(tail -n +$((START_LINE + 1)) CHANGELOG.md | grep -n "^## \[v.*\] - " | head -1 | cut -d: -f1)
|
|
103
|
+
|
|
104
|
+
if [ -z "$NEXT_VERSION_LINE" ]; then
|
|
105
|
+
# No next version found, extract from current version till end of file
|
|
106
|
+
CHANGELOG_CONTENT=$(tail -n +$START_LINE CHANGELOG.md)
|
|
107
|
+
else
|
|
108
|
+
# Extract content from current version header to before next version
|
|
109
|
+
END_LINE=$((START_LINE + NEXT_VERSION_LINE - 1))
|
|
110
|
+
CHANGELOG_CONTENT=$(sed -n "$START_LINE,$((END_LINE - 1))p" CHANGELOG.md)
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# Clean up the content but preserve the blank line after the header
|
|
114
|
+
# First, get the header line and add a blank line after it
|
|
115
|
+
HEADER_LINE=$(echo "$CHANGELOG_CONTENT" | head -1)
|
|
116
|
+
CONTENT_LINES=$(echo "$CHANGELOG_CONTENT" | tail -n +2 | sed '/^$/d' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
|
|
117
|
+
|
|
118
|
+
# Combine header + blank line + content
|
|
119
|
+
CHANGELOG_CONTENT=$(printf "%s\n\n%s" "$HEADER_LINE" "$CONTENT_LINES")
|
|
120
|
+
|
|
121
|
+
# Escape for GitHub Actions
|
|
122
|
+
echo "CHANGELOG_NOTES<<EOF" >> $GITHUB_ENV
|
|
123
|
+
echo "$CHANGELOG_CONTENT" >> $GITHUB_ENV
|
|
124
|
+
echo "EOF" >> $GITHUB_ENV
|
|
125
|
+
- name: Download all the dists
|
|
126
|
+
uses: actions/download-artifact@v4
|
|
127
|
+
with:
|
|
128
|
+
name: python-package-distributions
|
|
129
|
+
path: dist/
|
|
130
|
+
- name: Sign the dists with Sigstore
|
|
131
|
+
uses: sigstore/gh-action-sigstore-python@v3.0.0
|
|
132
|
+
with:
|
|
133
|
+
inputs: >-
|
|
134
|
+
./dist/*.tar.gz
|
|
135
|
+
./dist/*.whl
|
|
136
|
+
- name: Create GitHub Release
|
|
137
|
+
env:
|
|
138
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
139
|
+
run: |
|
|
140
|
+
# Set pre-release flag if version is a pre-release
|
|
141
|
+
PRERELEASE_FLAG=""
|
|
142
|
+
TITLE_SUFFIX=""
|
|
143
|
+
if [ "$IS_PRERELEASE" = "true" ]; then
|
|
144
|
+
PRERELEASE_FLAG="--prerelease"
|
|
145
|
+
TITLE_SUFFIX=" (Pre-release)"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if [ -n "$CHANGELOG_NOTES" ]; then
|
|
149
|
+
gh release create "v$VERSION" \
|
|
150
|
+
--repo "$GITHUB_REPOSITORY" \
|
|
151
|
+
--title "v$VERSION$TITLE_SUFFIX" \
|
|
152
|
+
--generate-notes \
|
|
153
|
+
--notes "$CHANGELOG_NOTES" \
|
|
154
|
+
$PRERELEASE_FLAG
|
|
155
|
+
else
|
|
156
|
+
gh release create "v$VERSION" \
|
|
157
|
+
--repo "$GITHUB_REPOSITORY" \
|
|
158
|
+
--title "v$VERSION$TITLE_SUFFIX" \
|
|
159
|
+
--generate-notes \
|
|
160
|
+
--notes "Release v$VERSION" \
|
|
161
|
+
$PRERELEASE_FLAG
|
|
162
|
+
fi
|
|
163
|
+
- name: Upload artifact signatures to GitHub Release
|
|
164
|
+
env:
|
|
165
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
166
|
+
# Upload to GitHub Release using the `gh` CLI.
|
|
167
|
+
# `dist/` contains the built packages, and the
|
|
168
|
+
# sigstore-produced signatures and certificates.
|
|
169
|
+
run: >-
|
|
170
|
+
gh release upload
|
|
171
|
+
"v$VERSION" dist/**
|
|
172
|
+
--repo "$GITHUB_REPOSITORY"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Tests check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
|
|
6
|
+
concurrency:
|
|
7
|
+
group: ${{ github.workflow }}-${{ github.head_ref }}
|
|
8
|
+
cancel-in-progress: true
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
# --------------------------------------------------------------------------
|
|
12
|
+
# 1. Test matrix — one job per supported Python version
|
|
13
|
+
# --------------------------------------------------------------------------
|
|
14
|
+
matrix-test:
|
|
15
|
+
name: Tests (py${{ matrix.python-version }})
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
21
|
+
# Least privilege: this job runs untrusted PR code and never uses OIDC, so it must not
|
|
22
|
+
# be able to mint an id-token. Read-only contents is all the test steps need.
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
env:
|
|
26
|
+
VIRTUAL_ENV: ${{ github.workspace }}/.venv
|
|
27
|
+
ENV: dev
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: PYTHON_VERSION=${{ matrix.python-version }} make install
|
|
33
|
+
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: make gha-tests
|
|
36
|
+
|
|
37
|
+
# --------------------------------------------------------------------------
|
|
38
|
+
# 2. Aggregator job — the *single* required status check
|
|
39
|
+
# --------------------------------------------------------------------------
|
|
40
|
+
tests-all:
|
|
41
|
+
name: Tests (all)
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
needs: [matrix-test]
|
|
44
|
+
if: always()
|
|
45
|
+
|
|
46
|
+
steps:
|
|
47
|
+
- name: Fail if any test job failed
|
|
48
|
+
run: |
|
|
49
|
+
if [ "${{ needs.matrix-test.result }}" != "success" ]; then
|
|
50
|
+
echo "::error::At least one test job failed."
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
echo "All test jobs passed."
|