Skip to navigation

Python SDK

View as Markdown

Use the Python SDK to manage sandboxes from applications, notebooks, and automation. It can reuse gateway registration and authentication state created by the OpenShell CLI. Use the SDK and gateway from the same OpenShell release when possible.

Install the SDK

The package requires Python 3.11 or later. Add it to your project with uv:

uv add openshell

The openshell package contains the SDK. It does not install the OpenShell CLI.

Connect to a Registered Gateway

Register and select a gateway with the CLI first. Then construct the client from the active gateway:

from openshell import SandboxClient
with SandboxClient.from_active_cluster() as client:
health = client.health()
print(health.version)

from_active_cluster() reads the selected gateway endpoint, TLS files, and OIDC token state. It refreshes an expiring OIDC token by default and writes the rotated bundle back for other OpenShell processes.

For a direct local plaintext connection, pass a host:port endpoint:

from openshell import SandboxClient
with SandboxClient("127.0.0.1:8080") as client:
print(client.health().version)

For service automation against an OIDC gateway, use ClientCredentialsAuth. Non-loopback endpoints require TLS:

import os
from openshell import ClientCredentialsAuth, SandboxClient
auth = ClientCredentialsAuth(
issuer="https://idp.example.com/realms/openshell",
client_id="openshell-service",
client_secret=lambda: os.environ["OPENSHELL_OIDC_CLIENT_SECRET"],
audience="openshell-gateway",
)
with SandboxClient.from_active_cluster(client_credentials=auth) as client:
print(client.health().version)

Retrieve Provider Credentials

Connect directly with a trusted OU=operator client certificate after enabling operator authentication. Do not supply a bearer token or use an OIDC login for this connection. No sandbox creation or provider attachment is required.

from datetime import timedelta
from pathlib import Path
from openshell import SandboxClient, TlsConfig
tls = TlsConfig(
ca_path=Path("ca.crt"),
cert_path=Path("operator.crt"),
key_path=Path("operator.key"),
)
with SandboxClient("gateway.example.com:443", tls=tls) as client:
credentials = client.providers().get_credentials(
"my-graph",
["MS_GRAPH_ACCESS_TOKEN"],
workspace="production",
minimum_remaining_lifetime=timedelta(minutes=5),
)
token = credentials["MS_GRAPH_ACCESS_TOKEN"].value

ProviderClient also accepts an existing grpc.Channel. Its get_credentials() method requires an explicit workspace and key selection. It returns a dictionary of ProviderCredentialValue objects with value and optional expiration_time, a UTC datetime. Pass a timedelta for minimum_remaining_lifetime; omission or zero uses the gateway default. The helper uses the connection’s timeout and does not retry or cache exports. Gateway failures raise GatewayError; invalid local selections raise ValueError.

The value is excluded from the object’s repr, but serialization still exposes it. Keep exported secrets out of logs and shared output. See Retrieve Runtime Credentials for freshness, supported credentials, and the export security boundary.

Create and Use a Sandbox

Python SDK methods take an explicit workspace. This example creates a sandbox, waits for readiness, runs a command, and waits for deletion:

from openshell import SandboxClient
with SandboxClient.from_active_cluster() as client:
sandbox = client.create(
workspace="default",
name="sdk-example",
)
client.wait_ready(
sandbox.name,
workspace="default",
timeout_seconds=120,
)
result = client.exec(
sandbox.name,
["python", "-c", "print('hello from OpenShell')"],
workspace="default",
)
print(result.stdout, end="")
deletion = client.delete(sandbox.name, workspace="default")
client.wait_deleted(
sandbox.name,
workspace="default",
expected_sandbox_id=deletion.sandbox_id,
)

Use create_session() when you want an object that retains the sandbox name and workspace for repeated exec, stop, start, and delete operations. List methods return lazy Pager instances; use list_all() only when you want to fetch the complete collection.

Call Generated RPCs

The generated gRPC stubs include concrete request and response types for synchronous grpc.Channel and asynchronous grpc.aio.Channel connections. Install the dependency type stubs in your development environment for editor signatures and type checking:

uv add --dev types-grpcio 'types-protobuf>=6.32,<7'

Construct OpenShellStub with your channel. The channel determines whether calls return responses directly or awaitable gRPC calls:

import grpc.aio
from openshell._proto import datamodel_pb2 as dm
from openshell._proto import openshell_pb2 as pb
from openshell._proto.openshell_pb2_grpc import OpenShellStub
async def get_provider(channel: grpc.aio.Channel, name: str) -> pb.ProviderResponse:
stub = OpenShellStub(channel)
return await stub.GetProvider(
pb.GetProviderRequest(
workspace_scope=dm.WorkspaceSelector(workspace="default"),
name=name,
),
timeout=30,
)

Your application configures the channel’s TLS and authentication, supplies RPC deadlines, and closes the channel. Create and use async channels on the same event loop. Cancellation and gRPC errors propagate directly. Generated stubs do not reuse SandboxClient authentication automatically; SandboxClient and ProviderClient remain synchronous.

If you annotate a stored async stub explicitly, use a deferred annotation such as "openshell_pb2_grpc.OpenShellAsyncStub". That type exists only in the .pyi file; construct OpenShellStub at runtime. The same convention applies to credential-driver stubs.

Implement a Credential Driver

The SDK includes generated message classes, type stubs, and gRPC bindings for the credential-driver and extension protocols:

from openshell._proto import credential_driver_pb2, credential_driver_pb2_grpc
from openshell._proto import datamodel_pb2, extension_pb2

Subclass credential_driver_pb2_grpc.CredentialDriverServicer and register your implementation with add_CredentialDriverServicer_to_server() from the same module. Clients use CredentialDriverStub. The generated messages share datamodel_pb2.CredentialHandle and extension_pb2.PeerMetadata with the SDK. Use bindings from the same OpenShell release as your gateway; these modules expose the internal credential-driver contract.

Next Steps