Skip to main content

Python client for hogland — Firecracker-backed sandboxes (hogboxes)

Project description

hogland (Python)

Python client for hogland's Firecracker-backed sandboxes (hogboxes).

The SDK exposes the user-facing slice of the hogplane API (/v1/hogboxes/*, /v1/limits, /v1/me, plus snapshots and devboxes) as a small, typed surface designed to be a viable backend for PostHog's internal SandboxBase interface — see examples/sandbox_base_adapter.py.

Install

uv pip install posthog-hogland
# or
pip install posthog-hogland

The PyPI distribution is posthog-hogland; the importable package is hogland (same split as pip install pillow / from PIL import ...).

Quickstart

from hogland import Hogland

client = Hogland()  # reads HOG_TOKEN + HOG_HOST from env (same vars as the CLI)

with client.create(cpus=4, memory_mib=8192, disk_gib=50) as box:
    box.write_file("/work/run.py", b"print('hi')\n", mkdir=True)

    result = box.exec(["python", "/work/run.py"], timeout_seconds=30)
    assert result.exit_code == 0
    print(result.stdout)

    # Streaming exec — yields ExecEvent frames as they arrive.
    for event in box.exec_stream(["bash", "-c", "for i in 1 2 3; do echo $i; sleep 1; done"]):
        if event.kind == "stdout":
            print(event.data, end="")
        elif event.kind == "exit":
            print(f"exit: {event.exit_code}")

    # Snapshot for next session. Returns a record whose .id can be
    # passed back as snapshot_id on the next create.
    snap = box.snapshot()

Async is symmetric:

import asyncio
from hogland import AsyncHogland

async def main() -> None:
    async with AsyncHogland() as client:
        async with await client.create(cpus=4, memory_mib=8192) as box:
            result = await box.exec(["uname", "-a"])
            print(result.stdout)

asyncio.run(main())

API at a glance

Method Returns Notes
Hogland.create(...) / .get(id) / .list() / .iter_boxes() Hogbox / HogboxList Create, fetch, paginate user-owned boxes
Hogland.me() / .limits() Me / Limits Caller identity, server-advertised valid ranges
Hogbox.exec(argv, timeout_seconds, env, workdir) ExecResult Batch exec, ≤1 MiB stdout/stderr per stream
Hogbox.exec_stream(argv, ...) Iterator[ExecEvent] SSE streaming exec, no output cap
Hogbox.write_file(path, bytes, mode, mkdir) / .read_file(path) FileWriteResponse / bytes Atomic file IO; ≤64 MiB
Hogbox.snapshot() SnapshotRecord Pause → sync → resume; returns id usable as next snapshot_id
Hogbox.pause() / .resume() / .delete() Hogbox / None Lifecycle
Hogbox.proxy_url(port, path) str Authenticated proxy URL into the box at port

Auth: pass an API credential via HOG_TOKEN env or token=... ctor arg. The SDK transports it as Authorization: Bearer <credential> and is agnostic to credential type — hogplane resolves it through one of several paths (see Auth below). The proxy_url is hit with the same credential; there's no per-tunnel token system (see docs/PYTHON_SDK_CODEGEN_RESEARCH.md §1d).

Errors are typed: AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, ValidationError, RateLimitError, ServerError. All derive from APIError (which derives from HoglandError). The original problem-details body is on .body; the HTTP status is on .status_code.

Configuration

export HOG_TOKEN=...                          # API credential (see Auth)
export HOG_HOST=https://hogland.posthog.dev   # API base URL

Config is resolved from constructor args (token=, base_url=) first, then from these env vars. There is no on-disk config-file fallback today; matching the CLI's ~/.config/hogland/config.json (see pkg/cliconfig/cliconfig.go) is on the roadmap.

Auth

Hogplane authenticates each request to one of these paths — see docs/AUTH_PLAN.md for the full model. The SDK transports any of them identically; the credential string you put in HOG_TOKEN / token= is what selects the path:

Path Credential Who uses it
APIToken Long-lived token minted at signup (tok_...) Humans, CLI users, scripts
K8s SA (EKS OIDC) Projected K8s ServiceAccount JWT with hogland audience In-cluster automation (e.g. PostHog Django → hogland)
GitHub OIDC GitHub Actions workflow JWT CI workflows in PostHog org
Tailscale Identity injected by Tailscale Operator ingress Tailnet-resident humans

For in-cluster automation (the primary SDK consumer), the recommended shape is:

  1. Pod spec mounts a projected SA token volume with hogland's audience (default: hogland) at e.g. /var/run/secrets/hogland.posthog.dev/token. Don't reuse /var/run/secrets/kubernetes.io/serviceaccount/token — its audience is the cluster API server, which won't match hogland.
  2. The integration reads that file at request time (not at process boot — K8s rotates the file every ~50 min) and passes the contents as token=.
  3. A k8s_sa TrustMapping on hogland side, keyed on {issuer, namespace, sa_name}, resolves the JWT to a Principal.

See examples/sandbox_base_adapter.py for a reference implementation and INTEGRATION_NOTES.md for the operational checklist.

Multi-deploy / regional setups

The SDK is region-agnostic by construction — one Hogland instance is one base URL plus one credential. If hogland ever runs as separate regional deploys (e.g. hogland-eu.posthog.dev and hogland-us.posthog.dev), the consumer holds one client per region:

clients = {
    "eu": Hogland(base_url="https://hogland-eu.posthog.dev", token=eu_token),
    "us": Hogland(base_url="https://hogland-us.posthog.dev", token=us_token),
}

box = clients[region].create(cpus=4, memory_mib=8192)

This is the same shape Modal users write today (their _get_modal_region(deployment) helper picks the right Modal cloud); the only difference is that with hogland the regional decision is which base_url, not which kwarg. If the deploys share a Principal (federated TrustMapping matched on the same K8s SA), the token is the same across regions; if not, the consumer rotates per-region credentials at construction time.

The vendored examples/sandbox_base_adapter.py factors this through a small _hogland_client(region=...) factory — extend that one function in your repo when regional routing becomes a real requirement.

How the SDK is built

The Pydantic v2 models in src/hogland/_generated/models.py are codegen'd from internal/clitest/testdata/openapi.yaml by scripts/gen_models.py (which filters to the user-facing slice and runs datamodel-code-generator underneath). CI runs the regen and fails the build if the result differs from the committed file. The hand-written facade (_client.py, _async.py, _box.py, _http.py, _sse.py) sits on top and is the public surface.

The choice of "models-only codegen + hand-written facade" is the modern-AI-SDK shape (OpenAI, Anthropic). See docs/PYTHON_SDK_CODEGEN_RESEARCH.md for the analysis and tradeoffs.

Development

# From the repo root.
cd python
uv venv --python 3.12
uv pip install -e '.[dev]'

# Regenerate models from openapi.yaml.
task py:gen-models

# Run tests, lint, type-check.
uv run pytest -q
uv run ruff check .
uv run ruff format --check .
uv run ty check src tests

Release

See RELEASING.md for the operational flow (bump, tag, push) and the one-time PyPI Trusted Publishing setup.

Short version: bump __version__ in src/hogland/_version.py — the single source of truth — then tag vX.Y.Z-py on main. CI verifies the tag matches _version.py, builds, smoke-tests the wheel, attests, publishes via OIDC, and creates a GitHub Release. The -py suffix keeps this release track separate from the CLI's v*-cli tags.

Changes per version are tracked in CHANGELOG.md.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

posthog_hogland-0.3.0.tar.gz (45.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

posthog_hogland-0.3.0-py3-none-any.whl (34.5 kB view details)

Uploaded Python 3

File details

Details for the file posthog_hogland-0.3.0.tar.gz.

File metadata

  • Download URL: posthog_hogland-0.3.0.tar.gz
  • Upload date:
  • Size: 45.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for posthog_hogland-0.3.0.tar.gz
Algorithm Hash digest
SHA256 049cc244929181748d0e5f7ba58e278247784916857b8778d47ee53acef6860b
MD5 9dc5e9fe26b73b2de62f48cbc1cbdd22
BLAKE2b-256 fae2624e0b1a103624b044269697fcfe7b03a36d412be01584c042628c257237

See more details on using hashes here.

File details

Details for the file posthog_hogland-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: posthog_hogland-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 34.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for posthog_hogland-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 59467f03a001a2e011571b35fd69a5755a9166227aab67c7b45cd41a81975b3d
MD5 91aff53828552e798be7851c2617d64e
BLAKE2b-256 eb678bbe83ea2d656cd054e2bf54fece46a2c498b65c5c9767b02ee078cd0eb5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page