Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,43 @@ The `openshell` package on PyPI provides the Python SDK only. It does not instal
uv add openshell
```

Curated SDK methods cover common sandbox workflows. For gateway RPCs that do
not yet have a curated wrapper, `SandboxClient.raw` exposes the complete
generated OpenShell service over the same authenticated channel. The public
wire types are available from `openshell.raw`:

```python
import os

from openshell import SandboxClient, raw

with SandboxClient.from_active_cluster() as client:
response = client.raw.UpdateProvider(
raw.openshell_pb2.UpdateProviderRequest(
workspace="my-workspace",
provider=raw.datamodel_pb2.Provider(
metadata=raw.datamodel_pb2.ObjectMeta(
name="user-token",
workspace="my-workspace",
),
credentials={"TOKEN": os.environ["USER_TOKEN"]},
),
),
timeout=30,
)
```

The raw layer returns protobuf messages verbatim. Prefer curated methods when
available, pass an explicit timeout to raw calls, and keep the owning
`SandboxClient` open for the lifetime of calls or streams. Exposing an RPC does
not grant permission to invoke it; the gateway applies the same authentication
and authorization checks to curated and raw calls.

`SandboxClient.channel` exposes the same authenticated channel when an
additional generated service client is needed, for example
`raw.InferenceStub(client.channel)`. The `SandboxClient` continues to own that
channel and its lifecycle.

**Helm chart:**

> **Experimental** — the Kubernetes deployment path is under active development. Expect rough edges and breaking changes.
Expand Down
29 changes: 29 additions & 0 deletions python/openshell/raw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Generated OpenShell gRPC surface for advanced SDK use.

The modules exported here contain the uncurated protobuf wire types. Prefer the
high-level SDK where it has a suitable method; use this module with
``SandboxClient.raw`` when the gateway RPC has not received a curated wrapper.
"""

from ._proto import (
datamodel_pb2,
inference_pb2,
openshell_pb2,
options_pb2,
sandbox_pb2,
)
from ._proto.inference_pb2_grpc import InferenceStub
from ._proto.openshell_pb2_grpc import OpenShellStub

__all__ = [
"InferenceStub",
"OpenShellStub",
"datamodel_pb2",
"inference_pb2",
"openshell_pb2",
"options_pb2",
"sandbox_pb2",
]
22 changes: 22 additions & 0 deletions python/openshell/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,28 @@ def __init__(
)
self._stub = openshell_pb2_grpc.OpenShellStub(self._channel)

@property
def raw(self) -> openshell_pb2_grpc.OpenShellStub:
"""Generated client for every OpenShell gateway RPC.

This is the uncurated protobuf API. Request and response types are
available from :mod:`openshell.raw`. Calls reuse this client's channel,
including its TLS and bearer-token configuration. Unless overridden by
the caller, generated methods do not inherit this client's default
timeout.
"""
return self._stub

@property
def channel(self) -> grpc.Channel:
"""Authenticated channel for constructing additional generated clients.

The channel is owned by this client and closes when :meth:`close` is
called. Generated service clients are available from
:mod:`openshell.raw`.
"""
return self._channel

@classmethod
def from_active_cluster(
cls,
Expand Down
42 changes: 42 additions & 0 deletions python/openshell/sandbox_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import pytest

import openshell.sandbox as sandbox_module
from openshell import raw
from openshell._proto import openshell_pb2
from openshell.sandbox import (
_OIDC_TOKEN_EXPIRY_GRACE_SECONDS,
Expand Down Expand Up @@ -437,6 +438,47 @@ def _client_with_fake_stub(stub: object) -> SandboxClient:
return client


def test_raw_exposes_the_authenticated_generated_stub() -> None:
stub = object()
client = _client_with_fake_stub(stub)

assert client.raw is stub


def test_raw_stub_covers_every_gateway_rpc() -> None:
client = SandboxClient("127.0.0.1:1")
try:
service = raw.openshell_pb2.DESCRIPTOR.services_by_name["OpenShell"]
missing = [
method.name
for method in service.methods
if not hasattr(client.raw, method.name)
]
assert missing == []
finally:
client.close()


def test_channel_exposes_the_authenticated_transport() -> None:
channel = object()
client = _client_with_fake_stub(object())
client._channel = cast("Any", channel)

assert client.channel is channel


def test_raw_exports_gateway_wire_types() -> None:
request = raw.openshell_pb2.UpdateProviderRequest(workspace="example")
provider = raw.datamodel_pb2.Provider(type="example")
config_request = raw.sandbox_pb2.GetSandboxConfigRequest()

assert request.workspace == "example"
assert provider.type == "example"
assert config_request is not None
assert raw.InferenceStub is not None
assert raw.OpenShellStub is not None


def test_exec_sends_stdin_payload() -> None:
stub = _FakeStub()
client = _client_with_fake_stub(stub)
Expand Down
Loading