# 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.Ocx

*class*

```python
class Ocx
```

Re-exported from: `ocx_sdk._client`

A handle on one ocx binary.

Construction resolves the binary once — the explicit `exe`, then
`OCX_SDK_EXE`, then `PATH`, then ocx's own install symlink — and pins the
result for the handle's lifetime. Every call composes its own argv and
child environment, and the handle exposes no mutators, so it is safe to
share across threads, tasks, and event loops.

The first typed call probes `ocx version` and refuses a binary older than
`MIN_SUPPORTED`; newer than `TESTED_OCX_VERSION` is expected and only
noted at DEBUG.

A long-lived handle keeps running the binary it resolved, so a consumer
shaped like a daemon should reconstruct `Ocx()` periodically to pick up
what an `ocx self update` replaced underneath it.

> **Example**
>
> ```python
> from ocx_sdk import Ocx, OcxConfig
> 
> ocx = Ocx(config=OcxConfig(offline=True))
> project = ocx.project("/srv/build")
> project.pull()
> result = project.exec(["task", "verify"])
> ```

**Attributes**

- `exe` (`Path`) — The resolved binary this handle runs.
- `session_config` (`OcxConfig`) — The `OcxConfig` every call spawns under.

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

### ocx_sdk.Ocx.project

*method*

```python
def project(path: str | Path) -> Project
```

Return a project-tier handle rooted at `path`.

The path is made absolute immediately, so the handle keeps targeting
the same project no matter what the working directory does later.

ocx's `--project` names the project *file*, the way Cargo's
`--manifest-path` does, and accepts any filename. A directory is the
obvious thing to pass and the common case, so it is resolved to the
`ocx.toml` inside it.

A path that does not exist yet — which is every path `init()` is
called on — is read by its name: a `.toml` suffix means the file,
anything else means the directory. Guessing "file" there would make
`project('/srv/build').init()` write `/srv/ocx.toml`, one level above
where the caller pointed.

**Parameters**

- `path` (`str | Path`) — The project directory, or the project file directly.

**Returns**

- (`Project`) — A handle whose every call carries `--project <file>`.

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

### ocx_sdk.Ocx.version

*method*

```python
def version(timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> str
```

Return the binary's version, as `ocx version` prints it.

Reads the plain output — a bare semver line — which is ocx's
documented stable script contract and the recorded exception to the
`--format json` pinning rule. This call is the compatibility probe
itself, so it never triggers the gate and never raises
`VersionCompatError`: asking an unsupported binary what it is has to
work. The richer JSON envelope is available through
`VersionInfo.from_json(ocx.invoke(["--format", "json",
"version"]).stdout)`.

**Parameters**

- `timeout` (`MaybeTimeout`) (default: `UNSET`) — Seconds per attempt. Omitted takes the config's.
- `retry` (`MaybeRetry`) (default: `UNSET`) — Retry policy. `None` opts out; omitted takes the config's.

**Returns**

- (`str`) — The version string, whitespace stripped.

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

## ocx_sdk.Project

*class* · *dataclass*

```python
class Project
```

Re-exported from: `ocx_sdk._client`

A project-tier handle: every call carries `--project <file>`.

Obtained from `Ocx.project(path)`, never constructed directly. Because
the path travels explicitly on every call, no method depends on the
working directory, and an ambient `OCX_PROJECT` can never retarget one.
`init` is the exception, and only because ocx's is: it takes no flags and
writes into the working directory, so the handle sets that instead.

> **Example**
>
> ```python
> from ocx_sdk import Ocx
> 
> project = Ocx().project("/srv/build")
> project.add("ocx.sh/go-task/task:3", group="ci")
> project.lock()
> report = project.env()
> environment = report.compose().mapping
> ```

**Attributes**

- `path` (`Path`) — The absolute project file — the `ocx.toml` itself, which is what ocx's `--project` names.
- `session_config` (`OcxConfig`) — The `OcxConfig` every call spawns under.

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