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() / .destroy() 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.1.0.tar.gz (39.2 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.1.0-py3-none-any.whl (29.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: posthog_hogland-0.1.0.tar.gz
  • Upload date:
  • Size: 39.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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.1.0.tar.gz
Algorithm Hash digest
SHA256 86ec1dfc99ea7b9652283e14b38552bd2256a764bd21b9fddf854d808463ec0b
MD5 310f72892152f7dd1c1bea165b09d879
BLAKE2b-256 d3043cc2cc93105bd737f5a20c026fcb185854a3f3676ca0c1471c9a3bf69b52

See more details on using hashes here.

File details

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

File metadata

  • Download URL: posthog_hogland-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 29.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 093bb298dfdf8422cddf9ec77d8257c5728be589cc5caf9e7b1437aade3a8089
MD5 bad00dfa757fe6bca787ebc7f8d9a40f
BLAKE2b-256 a70df71912ee0f53c74db9dcb513fa1b02b263a8e8e88076ca590d7d380525d3

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