# ocx_sdk

*module*

Python SDK for [OCX](https://github.com/ocx-sh/ocx).

`ocx-sdk` drives the ocx binary rather than reimplementing it: ocx owns
resolution, verification, and the identifier grammar, and this package gives
you typed, CWD-independent handles over the commands it exposes.

```python
from ocx_sdk import Ocx, bootstrap

ocx = Ocx(exe=bootstrap.ensure())
project = ocx.project("/srv/build")
project.pull()
project.exec(["task", "verify"])
```

**This module is the API.** Everything listed in `__all__` is the stable
surface; every other module is underscored and package-private, and the one
public submodule is `ocx_sdk.bootstrap`. Reaching into an underscored path
means the next release may move it without notice — pre-1.0, breaking
changes ship without shims.

Start at `Ocx` for the runtime API and `bootstrap.ensure` for provisioning.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/__init__.py#L1-L1)

## ocx_sdk.PackageCommands

*class* · *dataclass*

```python
class PackageCommands
```

Re-exported from: `ocx_sdk._client`

The `ocx package` command group — machine tier.

Package operations act on the `$OCX_HOME` store and its candidate and
current symlinks. They take no project path and are CWD-independent by
construction; a path appears only where the CLI itself takes one.

Every method is multi-identifier native, mirroring the CLI's `PKG...`
with one shared resolution.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/_client.py#L1470-L3367)

### ocx_sdk.PackageCommands.push

*method*

```python
def push(*layers: str | Path, identifier: str | None = None, platform: str | None = None, metadata: str | Path | None = None, cascade: bool = False, keep_tag: bool | None = None, build_timestamp: Literal['datetime', 'date', 'none'] | None = None, annotations: Mapping[str, str] | None = None, tags_file: str | Path | None = None, sign: bool = False, key: str | None = None, signature_format: SignatureFormat | None = None, rekor_upload: bool | None = None, sbom: str | Path | None = None, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> PushResult
```

Publish a package's layers and metadata to a registry.

Retries are off by default: a push is a registry write, and
re-sending one on a timeout can publish twice. Pass `retry=`
explicitly when the target is known to be idempotent-safe.

A push that lands and then fails to sign or attest is **not rolled
back** — a pushed manifest is immutable and OCI offers no un-push —
and the signing failure decides the exit code, so the call raises.
The report survives that: `push` writes its payload bare at the JSON
root (D11), so catch `OcxProcessError`, recover the document with
`partial_report(err)`, and parse it with `PushResult.from_json`. The
package is published, and `signatures` names which platform failed.

**Parameters**

- `*layers` (`str | Path`) (default: `()`) — Layer archives or `sha256:<hex>.<ext>` references, base first, each optionally carrying a `:strip=,prefix=,from=` tail.
- `identifier` (`str | None`) (default: `None`) — The identifier to publish under. Omitted reads the build receipt beside the bundle.
- `platform` (`str | None`) (default: `None`) — The platform the manifest is scoped to. Omitted reads the receipt.
- `metadata` (`str | Path | None`) (default: `None`) — The compiled metadata sidecar. Required when no file layers are given.
- `cascade` (`bool`) (default: `False`) — Also advance the rolling tags above this version.
- `keep_tag` (`bool | None`) (default: `None`) — Write the `__ocx.keep.sha256-<hex>` tag for each platform manifest published. `None` leaves ocx's default, which writes it.
- `build_timestamp` (`Literal['datetime', 'date', 'none'] | None`) (default: `None`) — Append a UTC build-metadata segment to the published tag, for rolling continuous-deploy versions. The version core in `identifier` must already be `X.Y.Z`.
- `annotations` (`Mapping[str, str] | None`) (default: `None`) — OCI annotations for the published index.
- `tags_file` (`str | Path | None`) (default: `None`) — Append the pushed tag and any cascade tags to this file, where `ocx package announce --tags-file` picks them up. A scratch file for one pipeline run, not a persistent list.
- `sign` (`bool`) (default: `False`) — Sign each platform manifest this push writes, inline. Required — along with `sbom` — before any of `signature_format`, `key` and `rekor_upload` mean anything: ocx refuses a signing modifier with nothing to sign. Off by default — a push without it signs nothing. The signature covers each platform manifest, never the image index, whose digest is rewritten every time another platform merges into it; sign the index afterwards with `sign(tags_file=...)`, reading the file `tags_file` wrote.
- `key` (`str | None`) (default: `None`) — A key reference for the inline signing — `file://` or `env://`, the two backends ocx 0.6 implements, or a bare path, read as `file://`. The five KMS schemes parse and are then refused with exit 85 (`UnsupportedKeyBackendError`). `None` signs keyless, against Fulcio.
- `signature_format` (`SignatureFormat | None`) (default: `None`) — Which signature format(s) the inline signing produces.
- `rekor_upload` (`bool | None`) (default: `None`) — Upload the inline signature to the transparency log. `False` (`--no-rekor-upload`) is valid only alongside `key`: a keyless signature must be logged, since its Fulcio certificate lives about ten minutes and the log entry's timestamp is the only lasting proof it was signed while the certificate was valid.
- `sbom` (`str | Path | None`) (default: `None`) — After the push, attest this CycloneDX SBOM against the pushed manifest — sugar for `attest(predicate_type= "cyclonedx")` on the digest this push just wrote. Read before the push, so a bad path costs no upload.
- `timeout` (`MaybeTimeout`) (default: `UNSET`) — Seconds per attempt. Omitted takes the config's.
- `retry` (`MaybeRetry`) (default: `UNSET`) — Retry policy. Defaults to no retries.

**Returns**

- (`PushResult`) — The published identifier, digest, and tags.

**Raises**

- `ValueError` — A signing modifier (`signature_format`, `key`,
`rekor_upload`) was given without `sign=True` or `sbom=`; or
`rekor_upload=False` was given without `key`.
- `OcxProcessError` — A non-zero exit. When the push itself landed and
the signing or the SBOM attestation is what failed, see the
partial-failure note above for how to recover the report.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/_client.py#L2154-L2285)
