feat: add public release capsule/pipeline API - #75
Conversation
Add release_capsule / release_pipeline SDK methods wrapping the new
POST /capsules/{id}/release and POST /pipelines/{id}/release endpoints,
plus CapsuleReleaseResults / ReleaseVersion models. Bump version to
0.17.0 and MIN_SERVER_VERSION to 4.8.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zvikagart
left a comment
There was a problem hiding this comment.
The code is idiomatic and matches the existing sync_capsule / sync_pipeline pattern, but it models a backend contract that no longer exists: it tracks the first BE commit (OceanCodes/backend#278) and was never updated for the refinements in OceanCodes/backend#288 (same story, sc-198066).
Blockers
- The 200 body is a job, not a flags object — the spec returns
CapsuleReleaseJob(job_id+statusrequired, plusstarted/duration/errorand, on completion only,release_capsule/release_version). GET /{capsules,pipelines}/{id}/release/{job_id}is missing from the SDK — that is the actual polling mechanism (routes.go, pipelines twin).- The unmet-requirement flags now come back as 403
CapsuleReleaseValidationIssueswith renamed, inverted, issue-only fields — unmodeled here, and the names in this PR are the pre-rename ones.
Details inline.
|
|
||
| @dataclass_json | ||
| @dataclass(frozen=True) | ||
| class CapsuleReleaseResults: |
There was a problem hiding this comment.
Blocker — this is not the 200 response shape. POST /release returns CapsuleReleaseJob:
| field | type | required |
|---|---|---|
job_id |
string | yes |
status |
CapsuleReleaseJobStatus (created/started/completed/failed/canceled/canceling) |
yes |
started |
int64 | |
duration |
int | |
release_capsule |
string | |
release_version |
Version |
|
error |
string |
None of job_id, status, started, duration, error are modeled here, and the ten validation flags were removed from the success body entirely. Against a real 4.8 server every flag deserializes to None, and release_capsule/release_version are also empty — the handler only populates them once the job completes, not on the POST. So release_capsule() returns an all-None object and silently drops the one field that matters.
Also needed: a CapsuleReleaseJobStatus StrEnum alongside CapsuleStatus, and the class should be renamed CapsuleReleaseJob to match the spec.
Second blocker, same model: the unmet-requirement flags now arrive as 403 CapsuleReleaseValidationIssues (handler), present only when true, with problem-oriented names: missing_reproducible_run, uncommitted_files, missing_metadata, non_default_branch, git_out_of_sync, unreleased_pipeline_capsules, missing_release_functionality, invalid_app_panel, unreleased_post_run_capsule. The names here are all pre-rename and inverted in polarity. Since _error_handler raises on 403, they're currently reachable only as an untyped Error.data dict — worth a typed model on the error path.
| default=None, | ||
| metadata={"description": "Whether the required metadata is present"}, | ||
| ) | ||
| no_credentials: Optional[bool] = dataclass_field( |
There was a problem hiding this comment.
no_credentials was dropped outright in OceanCodes/backend#288 — it is not a release blocker, and no equivalent exists in CapsuleReleaseValidationIssues.
|
|
||
| @dataclass_json | ||
| @dataclass(frozen=True) | ||
| class ReleaseVersion: |
There was a problem hiding this comment.
This duplicates the spec's shared Version schema, which is also what Capsule.versions holds — currently typed Optional[list[dict]] above. Name it Version after the spec and reuse it for both, rather than adding a release-only twin.
Also, the spec marks major_version / minor_version / release_time as required; default=0 here makes them optional. Capsule models required fields without defaults — follow that.
|
|
||
| return GitSyncResults.from_dict(res.json()) | ||
|
|
||
| def release_capsule(self, capsule_id: str) -> CapsuleReleaseResults: |
There was a problem hiding this comment.
Blocker — the polling story in this docstring isn't the contract. "poll the release capsule and watch for a version higher than the returned release_version" was the pre-#288 design. The real mechanism is GET /capsules/{capsule_id}/release/{job_id} (route), and it isn't wrapped here at all — so with the current return type there's no job_id and the caller cannot poll.
Please add get_release_job(capsule_id, job_id) (plus the pipeline delegate), and ideally a wait_until_completed mirroring Computations.wait_until_completed.
Worth documenting too: a 400 when the capsule was never released (which the docstring covers) and a 403 carrying the validation issues (which it doesn't).
| ) | ||
| # Re-exports for backward compatibility | ||
| from codeocean.models.capsule import ( # noqa: F401 | ||
| ReleaseVersion, |
There was a problem hiding this comment.
This block is # Re-exports for backward compatibility — names that predate the models/ split. A brand-new type belongs in the regular import list above it.
| """Sync a pipeline with its linked external Git repository.""" | ||
| return self._capsules.sync_capsule(pipeline_id) | ||
|
|
||
| def release_pipeline(self, pipeline_id: str) -> CapsuleReleaseResults: |
There was a problem hiding this comment.
Same stale polling advice as release_capsule — the pipeline twin is GET /pipelines/{pipeline_id}/release/{job_id}, which needs a delegate here.
| return session | ||
|
|
||
| def test_release_capsule_returns_results(self): | ||
| """release_capsule posts to the capsule release route and parses the results.""" |
There was a problem hiding this comment.
Style matches test_git_sync.py nicely — but these assert the stale contract, so they pass green while the client is wrong against a 4.8 server. After the model rewrite they should cover the job response (job_id + status), the completed-job GET carrying release_capsule/release_version, and a 403 validation-issues case.
| ========= | ||
|
|
||
| ## 0.17.0 (2026-08-13) | ||
| - feat: add release support for capsules and pipelines (`release_capsule` / `release_pipeline`) |
There was a problem hiding this comment.
Missing the PR link that every prior entry carries, e.g. - [#75](https://github.com/codeocean/codeocean-sdk-python/pull/75) feat: ....
Address PR review: the release API returns an asynchronous CapsuleReleaseJob
(job_id + status), not a flags object, and is polled via
GET .../release/{job_id}. Unmet requirements come back as 403
CapsuleReleaseValidationIssues.
- Replace CapsuleReleaseResults/ReleaseVersion with CapsuleReleaseJob,
CapsuleReleaseJobStatus, and a shared Version model (now also used by
Capsule.versions); drop the removed no_credentials check.
- Add CapsuleReleaseValidationIssues for the 403 error-path body.
- Add get_release_job and wait_until_release_completed (plus pipeline
delegates); release_capsule/release_pipeline now return the job.
- Move new types out of the backward-compat re-export block.
- Rewrite tests for the job response, completed-job GET, and validation issues.
- Add PR link to the CHANGELOG entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| release_time: int = dataclass_field( | ||
| metadata={"description": "Release time (int64 timestamp, seconds)"}, | ||
| ) | ||
| doi: Optional[str] = dataclass_field( |
There was a problem hiding this comment.
Almost certain DOI is not in use in VPCs so we can remove
| versions: Optional[list[Version]] = dataclass_field( | ||
| default=None, | ||
| metadata={ | ||
| "description": "Capsule versions with major_version, minor_version, release_time, and DOI" |
There was a problem hiding this comment.
| "description": "Capsule versions with major_version, minor_version, release_time, and DOI" | |
| "description": "Capsule versions with major and minor version, and release time" |
| CHANGELOG | ||
| ========= | ||
|
|
||
| ## 0.17.0 (2026-08-13) |
There was a problem hiding this comment.
We hold off adding the change log until we release, but if already added:
| ## 0.17.0 (2026-08-13) | |
| ## 0.17.0 (TBD) |
| ========= | ||
|
|
||
| ## 0.17.0 (2026-08-13) | ||
| - [#75](https://github.com/codeocean/codeocean-sdk-python/pull/75) feat: add release support for capsules and pipelines (`release_capsule` / `release_pipeline`, `get_release_job`, `wait_until_release_completed`) |
There was a problem hiding this comment.
The PR name
| - [#75](https://github.com/codeocean/codeocean-sdk-python/pull/75) feat: add release support for capsules and pipelines (`release_capsule` / `release_pipeline`, `get_release_job`, `wait_until_release_completed`) | |
| - [#75](https://github.com/codeocean/codeocean-sdk-python/pull/75) feat: add public release capsule/pipeline API |
- Remove doi from Version (not used in VPCs, per review) and drop it from the Capsule.versions description; update the release-job test accordingly. - CHANGELOG: mark 0.17.0 as (TBD) and use the PR title for the entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add release_capsule / release_pipeline SDK methods wrapping the new POST /capsules/{id}/release and POST /pipelines/{id}/release endpoints, plus CapsuleReleaseResults / ReleaseVersion models. Bump version to 0.17.0 and MIN_SERVER_VERSION to 4.8.0.