Metadata-Version: 2.4
Name: rustygate
Version: 0.1.0
Classifier: Programming Language :: Rust
Requires-Dist: pytest ; extra == 'dev'
Requires-Dist: pytest-timeout ; extra == 'dev'
Requires-Dist: httpx ; extra == 'dev'
Requires-Dist: websockets ; extra == 'dev'
Requires-Dist: jupyter-client ; extra == 'dev'
Requires-Dist: jupygate ; extra == 'dev'
Requires-Dist: ipymini ; extra == 'dev'
Provides-Extra: dev
Summary: Jupyter kernel gateway with terminals, files, and cells APIs, as a single binary
Author: Jeremy Howard
Maintainer-email: "fast.ai" <infos@fast.ai>
License: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/AnswerDotAI/rustygate
Project-URL: Issues, https://github.com/AnswerDotAI/rustygate/issues
Project-URL: Repository, https://github.com/AnswerDotAI/rustygate

# rustygate

A Jupyter kernel gateway as one binary: kernels, terminals, files, and per-cell notebook operations, over the same REST and websocket API shapes `jupyter_server` uses.

## Running

    rustygate [--host H] [--port N] [--token T] [--root DIR]

The defaults are 127.0.0.1, port 8787, no auth, and the working directory as the files root. With a token set, requests carry `Authorization: token T` or `?token=T`.

## The APIs

Kernels follow `jupyter_server`'s routes: `GET/POST /api/kernels`, `GET/DELETE /api/kernels/{id}`, `POST .../interrupt`, `POST .../restart`, and the client websocket at `.../channels`. The kernel model adds `pid`, `last_heartbeat`, and `path`. A kernel that misses three heartbeats shows `execution_state` "unresponsive" until the next echo. The gateway never kills an unresponsive kernel. A kernel becomes `dead` only when its process exits.

Terminals mirror `jupyter_server_terminals`: `GET/POST /api/terminals`, `GET/DELETE /api/terminals/{name}`, and a websocket at `.../channel`. Binary frames carry pty bytes verbatim in both directions. Text frames carry JSON control messages: `setup`, `gap`, `eof`, and inbound `set_size`.

The files API is jupyter's contents API, notebook-blind and byte-oriented. `GET/PUT/PATCH/POST/DELETE /api/contents/{path}`. Every model carries `name`, `path`, `type`, `size`, `mtime`, and `writable`. Costly fields are requested with `fields=`, for example `fields=hash,content`. PATCH renames. POST copies, with `copy_from` in the body and the target as the path. DELETE refuses a non-empty directory.

The cells API edits ipynb files per cell. `GET /api/cells/{path}` returns the parsed cells and the file hash. `fields=hashes` returns per-cell hashes for cheap sync, and `ids=` fetches a subset. `POST /api/cells/{path}` applies a batch of ops atomically:

    {"ops": [
      {"op": "add", "cell": {...}, "after": "id"},
      {"op": "update", "id": "x", "source": "..."},
      {"op": "delete", "id": "x"}
    ]}

Update replaces each provided key wholesale. Add without an anchor appends, and a missing cell id is generated and returned in `added_ids`. Any failure rolls back the whole batch.

## Hashes and conditional writes

One hash serves everywhere: sha256 of the file's bytes, the same value in directory listings, contents models, cells responses, and broadcasts. Every mutation in both APIs accepts `expected_hash` and `session_id` query parameters. A stale `expected_hash` is rejected with 409 and the current hash. Per-cell hashes are opaque: compare them, never recompute them.

## Change notifications

A kernel created with a `path` binds to that notebook, and every client on its websocket is subscribed. Changes arrive as ordinary Jupyter-envelope messages on the gateway-private `cells` channel, `msg_type` `cell_ops`, with the path, the new file hash, and ops in the same vocabulary as the cells API. The op author, identified by `session_id`, does not receive its own change. Writes through the contents API and foreign writes detected by the gateway's file watcher arrive the same way, as diffed ops. `rename`, `deleted`, and `reset` events join the stream; `reset` means re-fetch the file.

Ops are not writable over the websocket; use the REST APIs. After a reconnect that reports dropped messages, re-sync from `GET /api/cells/{path}?fields=hashes` instead of trusting the op stream.

