Metadata-Version: 2.4
Name: go-judge-client
Version: 0.2.0
Summary: Typed asynchronous streaming client for go-judge
License-Expression: MIT
License-File: LICENSE
License-File: proto/LICENSE
License-File: proto/PROTOBUF_LICENSE
Requires-Dist: pydantic>=2.12,<3
Requires-Dist: grpcio>=1.84,<2
Requires-Dist: protobuf>=7.35.1,<8
Requires-Dist: typer>=0.25,<1 ; extra == 'cli'
Requires-Python: >=3.12
Project-URL: Repository, https://github.com/Inzhenerka/go-judge-client
Project-URL: Documentation, https://github.com/Inzhenerka/go-judge-client/blob/main/README.md
Project-URL: Issues, https://github.com/Inzhenerka/go-judge-client/issues
Provides-Extra: cli
Description-Content-Type: text/markdown

# go-judge-client

An asynchronous, typed Python SDK for running programs on an existing **go-judge** server.
Live stdout/stderr, named runtime profiles, binary project files, expected artifacts and explicit cancellation.
Python 3.12+, Pydantic v2, `grpc.aio`. No web service or sandbox installer is included.
Linux is the supported deployment platform; CI tests Python 3.12–3.14. Windows is outside the support scope.

Install and check a local checkout (PyPI publication is enabled separately; see [releases](docs/releases.md)):

```sh
uv sync
uv run poe check
```

For another uv project, use `uv add /path/to/go-judge-client`. The CLI is optional:
`uv add '/path/to/go-judge-client[cli]'`. The core dependencies do not include Typer.

## Run a command

The server must already be running and contain the requested executable:

```python
import asyncio
from go_judge_client import AsyncGoJudgeClient, ExecutionRequest


async def main() -> None:
    async with AsyncGoJudgeClient("localhost:15051", insecure=True) as client:
        result = await client.execute(ExecutionRequest(command=("/bin/echo", "Hello")))
        print(result.stdout_text())
        print(result.success, result.status)


asyncio.run(main())
```

`result.success` means the program was accepted and all required artifacts were received without
file errors. Program failures are results; transport failures raise exceptions. `ExecutionStatus`
is a string enum, so existing comparisons such as `result.status == "accepted"` also work.

## Streaming Python API

The endpoint must already have the selected runtime installed. This example matches the
[runtime image](examples/runtime-image/Dockerfile), using explicit loopback plaintext:

```python
import asyncio
from go_judge_client import (
    AsyncGoJudgeClient,
    ExecutionRequest,
    RuntimeProfile,
    ExpectedFile,
    TextOutput,
    Artifact,
    Finished,
)


async def main() -> None:
    profiles = {
        "python": RuntimeProfile(
            command=("/usr/local/runtimes/python-data/bin/python", "-u"),
            default_entrypoint="main.py",
        ),
    }
    async with AsyncGoJudgeClient(
        "localhost:15051",
        insecure=True,
        runtimes=profiles,
        max_concurrency=4,
    ) as client:
        request = ExecutionRequest(
            runtime="python",
            files={
                "main.py": "from pathlib import Path\nprint('Hello', flush=True)\n"
                "Path('result.bin').write_bytes(Path('input.bin').read_bytes())\n",
                "input.bin": b"\x00\xff",
            },
            expected_files=(ExpectedFile(path="result.bin"),),
        )
        async with client.stream(request) as execution:
            async for event in execution.text_events():
                match event:
                    case TextOutput(stream=stream, text=text):
                        print(stream, text, end="")
                    case Artifact(file=file):
                        print(file.path, file.size_bytes)
                    case Finished(result=result):
                        print(result.summary().model_dump(mode="json"))


asyncio.run(main())
```

`await client.execute(ExecutionRequest(command=("/bin/echo", "Hello")))` uses unary `Exec`
and returns a bounded captured result through the server's ordinary queue. On go-judge v1.13.3,
`-parallelism` limits these requests across client instances and API workers.

| API | RPC | Server `parallelism` | Output |
| --- | --- | --- | --- |
| `execute()` | `Exec` | Respected | Captured stdout/stderr and artifacts in the final result |
| `stream()` | `ExecStream` | Bypassed | Live output events, then the final result |

Both share the client's local `max_concurrency` limit. This local limit applies to one client instance;
server `parallelism` is **not** a combined limit for ordinary and streaming work. The CLI `run` command
uses streaming. Streaming here means live output with finite stdin; interactive stdin/TTY commands are
not yet a public API.

**Migration from 0.1.1:** `execute()` no longer drains `ExecStream`. Its signature and result model stay
the same, but it now waits in the server queue and uses output collectors. Its operation timeout includes
that wait. There is no automatic fallback to streaming or replay on RPC failure. Proxies must allow
`pb.Executor/Exec` as well as `ExecStream` if both APIs are used.

`execution.text_events()` handles split UTF-8 sequences and decoder finalization automatically.
Iterating `execution` directly retains the byte-preserving `Output` API. Choose one reader per execution.
`result.stdout_text()` and `result.stderr_text()` decode captured output, returning `None` if capture was disabled.
Use `capture_output=False` when forwarding logs without retaining another transcript.

## Terminal playground

The canonical executable is `go-judge-client`; `gj` is an exact alias.

Build the test image once, then start the local server in the foreground, without authentication
(Docker with Linux containers is required):

```sh
docker build -t go-judge-client-test:1.13.3 -f examples/runtime-image/Dockerfile .
uv run poe judge
```

The earlier pinned server failed functional acceptance on native ARM64 Docker Desktop; amd64
emulation on that host also failed seccomp initialization. The refreshed image is not yet verified on ARM64. Use a native Linux amd64 server for release
acceptance; see [current verification](docs/compatibility.md#dependency-refresh-2026-10-14).
The Python SDK itself is tested on macOS ARM64 and Linux ARM64.

Keep that terminal open. Press `Ctrl+C` to stop and remove the container. The server is exposed only on
`127.0.0.1:15051`, so local CLI commands do not need `--token-env`.

```sh
uv run gj profiles --profiles examples/runtimes.toml
uv run gj check --target localhost:15051 --insecure
uv run gj run examples/python-data --profiles examples/runtimes.toml \
  --runtime python-data --expect output/result.bin --target localhost:15051 --insecure
```

`check` proves transport connectivity only. `run` streams output and saves artifacts under `gj-output/`
without silently overwriting existing files. Use `--overwrite` to atomically replace regular artifact files. The development group installs the CLI for local tests.

## Contracts worth knowing

- Input strings are UTF-8 content; bytes remain bytes. Paths are sandbox-relative POSIX paths.
- An operation owns its RPC and local permit. Exit its context, including on handler errors and disconnects.
- `await execution.cancel()` performs bounded local cleanup. It does not prove remote process termination.
- Artifacts and `Finished` are emitted only after a valid final response **and** successful RPC termination.
- Missing required files and nonzero program exits are results; transport/configuration failures are exceptions.
- `await client.check_health()` checks authenticated service access via read-only FileList;
  it returns a boolean and does not execute code or consume an execution permit.
- Readiness is not authentication or sandbox health. TLS is the default; plaintext is an explicit local choice.
- There is no execution retry or fallback to polling. A failed connection can leave the remote outcome unknown.

Read [CLI usage](docs/cli.md), [profiles](docs/profiles.md), [artifacts](docs/artifacts.md),
[lifecycle and security](docs/lifecycle-and-security.md), [compatibility evidence](docs/compatibility.md),
[development](docs/development.md), and [application integration](docs/pumproom-integration.md).
