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

*property*

```python
patch: PatchCommands
```

The `ocx patch` command group.

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

### ocx_sdk.Ocx.session_config

*property*

```python
session_config: OcxConfig
```

The `OcxConfig` this handle spawns under.

Read-only, and the config itself is frozen: derive a variant with
`with_config(...)` rather than trying to write through this. Not
spelled `config` because `ocx.config` is the `ocx config` command
group, and command alignment wins over the shorter name.

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

### ocx_sdk.Ocx.about

*method*

```python
def about(timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> AboutInfo
```

Report what this ocx build is and where it keeps its state.

**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**

- (`AboutInfo`) — The parsed `ocx about` payload.

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

### ocx_sdk.Ocx.login

*method*

```python
def login(registry: str | None = None, *, username: str, token: str, allow_insecure_store: bool = False, verify: bool = True, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> LoginResult
```

Authenticate to a registry and let ocx persist the credentials.

The token travels on stdin through `--password-stdin`, never in argv,
and is redacted from logs and errors for the duration of the call.

Retries are off by default: a timed-out login exits 75, and
re-sending credentials on a timeout is the auth-retry mistake the
policy exists to prevent. Pass `retry=` explicitly to override.

**Parameters**

- `registry` (`str | None`) (default: `None`) — The registry hostname. `None` falls back to ocx's `OCX_DEFAULT_REGISTRY`.
- `username` (`str`) — The account name. Required — the SDK never prompts.
- `token` (`str`) — The password or token, written to ocx's stdin.
- `allow_insecure_store` (`bool`) (default: `False`) — Permit the plaintext `auths` fallback when no credential helper is configured.
- `verify` (`bool`) (default: `True`) — Check the credentials against the registry before storing them.
- `timeout` (`MaybeTimeout`) (default: `UNSET`) — Seconds per attempt. Omitted takes the config's.
- `retry` (`MaybeRetry`) (default: `UNSET`) — Retry policy. Defaults to no retries.

**Returns**

- (`LoginResult`) — The registry and username ocx recorded.

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

### ocx_sdk.Ocx.spawn_async

*method* · *async*

```python
async def spawn_async(argv: Sequence[str], **popen_kw: Any) -> asyncio.subprocess.Process
```

Start an arbitrary ocx command line on the event loop.

**Parameters**

- `argv` (`Sequence[str]`) — The command and its arguments, without the binary.
- `**popen_kw` (`Any`) (default: `{}`) — Forwarded to the subprocess factory. `args`, `shell`, and `executable` are rejected.

**Returns**

- (`asyncio.subprocess.Process`) — The running child.

**Raises**

- `ValueError` — `args`, `shell`, or `executable` was passed.
- `TypeError` — `env` was passed — see `spawn`.

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

### ocx_sdk.Ocx.with_config

*method*

```python
def with_config(**overrides: Unpack[ConfigOverrides]) -> Ocx
```

Derive a handle with some configuration fields replaced.

The derived handle shares this one's binary, host environment,
`on_log`, and compatibility memo — only the configuration differs.
Changing any of the others means constructing a fresh `Ocx`.

**Parameters**

- `**overrides` (`Unpack[ConfigOverrides]`) (default: `{}`) — `OcxConfig` field names and their new values, as `ConfigOverrides` spells them.

**Returns**

- (`Ocx`) — The derived handle.

**Raises**

- `TypeError` — An override names no `OcxConfig` field. A type checker
catches that first; the runtime guard is for callers who
build the keywords dynamically.

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