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

*class* · *dataclass*

```python
class ComposedEnv
```

Re-exported from: `ocx_sdk._envmodel`

A merged environment, ready for a child process.

Build one with `merge()`; `EnvReport.compose()` is the caller-facing route.

The values stay off every human-readable surface: a composed environment
inherits whatever `OCX_AUTH_*` its base carried, and a `repr` is exactly
what a traceback or a pytest diff prints (CWE-532). `__repr__` names the
keys and masks every value, the same shape `SpawnEnv` uses.

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

### ocx_sdk.ComposedEnv.mapping

*property*

```python
mapping: dict[str, str]
```

Return the merged environment as a fresh `dict`.

The non-invasive form, and the documented default: pass it as
`subprocess.run(..., env=...)` instead of mutating the process.

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

### ocx_sdk.ComposedEnv.activate

*method*

```python
def activate() -> Generator[None]
```

Apply this environment to `os.environ` for the duration of the block.

Process-global and single-owner: it changes what every thread and every
subprocess sees, and a second, overlapping `activate()` raises instead
of nesting. Concurrent code passes `mapping` to the subprocess call.

The revert is diff-based — only the keys this environment sets are
recorded on entry and restored on exit, keys that were absent are
deleted again, and unrelated changes made inside the block survive.
It runs in `finally`; `os._exit`, `execve`, and SIGKILL leave no chance
to run it at all.

**Raises**

- `RuntimeError` — Another `activate()` block already owns `os.environ`.

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

## ocx_sdk.ConfigOverrides

*class*

```python
class ConfigOverrides(TypedDict)
```

Bases: `TypedDict`

Re-exported from: `ocx_sdk._config`

The `OcxConfig` fields a `with_config` call may replace, all optional.

What makes `Ocx.with_config(...)` and `Project.with_config(...)` check
their keyword arguments instead of accepting `**overrides: Any`, so a
misspelled field is a type error rather than a `TypeError` at runtime.
Public for the same reason `MaybeRetry` is: a wrapper around this SDK can
forward a caller's overrides with `**overrides: Unpack[ConfigOverrides]`
rather than re-declaring the field list itself.

Mirrors `OcxConfig` field for field — a field added there and not here is
a field `with_config` would refuse.

Optional at the class level rather than per field: this module postpones
annotation evaluation, and a `NotRequired[...]` inside a string annotation
is invisible to `__required_keys__`, so anything introspecting the type at
runtime would be told every field was mandatory. Every field is optional
here anyway, which is exactly what `total=False` says.

> **Example**
>
> ```python
> from typing import Unpack
> 
> from ocx_sdk import ConfigOverrides, Ocx
> 
> 
> def hermetic(ocx: Ocx, **overrides: Unpack[ConfigOverrides]) -> Ocx:
>     return ocx.with_config(no_config=True, **overrides)
> ```

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