Documentation
¶
Overview ¶
Package client provides a Go client for the python-executor service.
The python-executor service allows remote execution of Python code in isolated Docker containers. This client handles all the complexity of the API, including tar archive creation, multipart requests, and response parsing.
Quick Start ¶
c := client.New("http://pyexec.cluster:9999/")
tarData, _ := client.TarFromMap(map[string]string{
"main.py": `print("Hello from Go!")`,
})
result, err := c.ExecuteSync(context.Background(), tarData, &client.Metadata{
Entrypoint: "main.py",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Stdout)
Creating Tar Archives ¶
Several helper functions are provided for creating tar archives:
- TarFromMap: Create from a map of filename to content
- TarFromFiles: Create from a list of file paths
- TarFromDirectory: Create from a directory path
- TarFromReader: Create from an io.Reader (e.g., stdin)
Example (AsyncExecution) ¶
Example_asyncExecution demonstrates asynchronous execution. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
tarData, _ := client.TarFromMap(map[string]string{
"main.py": `
import time
time.sleep(1)
print("Async complete!")
`,
})
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
// Submit asynchronously
execID, err := c.ExecuteAsync(ctx, tarData, &client.Metadata{
Entrypoint: "main.py",
})
if err != nil {
fmt.Printf("Error submitting: %v\n", err)
return
}
fmt.Printf("Submitted execution ID received: %t\n", strings.HasPrefix(execID, "exe_"))
// Wait for completion
result, err := c.WaitForCompletion(ctx, execID, 500*time.Millisecond)
if err != nil {
fmt.Printf("Error waiting: %v\n", err)
return
}
fmt.Printf("Status: %s\n", result.Status)
fmt.Printf("Output: %s", strings.TrimSpace(result.Stdout))
}
Output: Submitted execution ID received: true Status: completed Output: Async complete!
Example (EvalMultiLine) ¶
Example_evalMultiLine demonstrates REPL evaluation with multi-line code. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Multi-line code with variable assignment and expression
result, err := c.Eval(ctx, &client.SimpleExecRequest{
Code: `x = 10
y = 5
x * y`,
EvalLastExpr: true,
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Exit code: %d\n", result.ExitCode)
if result.Result != nil {
fmt.Printf("Result: %s\n", *result.Result)
}
}
Output: Exit code: 0 Result: 50
Example (EvalSimple) ¶
Example_evalSimple demonstrates simple REPL-style evaluation. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Simple expression evaluation
result, err := c.Eval(ctx, &client.SimpleExecRequest{
Code: "2 + 2",
EvalLastExpr: true,
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Exit code: %d\n", result.ExitCode)
if result.Result != nil {
fmt.Printf("Result: %s\n", *result.Result)
}
}
Output: Exit code: 0 Result: 4
Example (EvalWithImport) ¶
Example_evalWithImport demonstrates REPL evaluation with imports. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Import a module and evaluate an expression
result, err := c.Eval(ctx, &client.SimpleExecRequest{
Code: "import math\nmath.sqrt(16)",
EvalLastExpr: true,
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Exit code: %d\n", result.ExitCode)
if result.Result != nil {
fmt.Printf("Result: %s\n", *result.Result)
}
}
Output: Exit code: 0 Result: 4.0
Example (EvalWithPrint) ¶
Example_evalWithPrint demonstrates that print statements produce stdout while expressions produce results. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Print produces stdout, not a result
result, err := c.Eval(ctx, &client.SimpleExecRequest{
Code: `print("Hello from eval!")`,
EvalLastExpr: true,
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Exit code: %d\n", result.ExitCode)
fmt.Printf("Stdout: %s", result.Stdout)
if result.Result != nil && *result.Result != "" {
fmt.Printf("Result: %s\n", *result.Result)
} else {
fmt.Println("Result: (none)")
}
}
Output: Exit code: 0 Stdout: Hello from eval! Result: (none)
Example (ExecuteSync) ¶
Example_executeSync demonstrates synchronous execution. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
tarData, err := client.TarFromMap(map[string]string{
"main.py": `print("Hello, World!")`,
})
if err != nil {
fmt.Printf("Error creating tar: %v\n", err)
return
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result, err := c.ExecuteSync(ctx, tarData, &client.Metadata{
Entrypoint: "main.py",
})
if err != nil {
fmt.Printf("Error executing: %v\n", err)
return
}
fmt.Printf("Exit code: %d\n", result.ExitCode)
fmt.Printf("Output: %s", strings.TrimSpace(result.Stdout))
}
Output: Exit code: 0 Output: Hello, World!
Example (ExecuteWithConfig) ¶
Example_executeWithConfig demonstrates execution with custom configuration. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
tarData, _ := client.TarFromMap(map[string]string{
"main.py": `
import sys
print(f"Args: {sys.argv[1:]}")
print("Config test passed")
`,
})
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result, err := c.ExecuteSync(ctx, tarData, &client.Metadata{
Entrypoint: "main.py",
ScriptArgs: []string{"--verbose", "test.txt"},
Config: &client.ExecutionConfig{
TimeoutSeconds: 60,
NetworkDisabled: true,
MemoryMB: 512,
},
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Exit code: %d\n", result.ExitCode)
}
Output: Exit code: 0
Example (MultiFileExecution) ¶
Example_multiFileExecution demonstrates executing multiple files. This example requires a running server.
package main
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func getServerURL() string {
if url := os.Getenv("PYEXEC_SERVER"); url != "" {
return url
}
return "http://pyexec.cluster:9999/"
}
func main() {
c := client.New(getServerURL())
tarData, _ := client.TarFromMap(map[string]string{
"main.py": `
from helper import calculate
result = calculate(5, 3)
print(f"Result: {result}")
`,
"helper.py": `
def calculate(a, b):
return a * b
`,
})
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result, err := c.ExecuteSync(ctx, tarData, &client.Metadata{
Entrypoint: "main.py",
})
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Output: %s", strings.TrimSpace(result.Stdout))
}
Output: Output: Result: 15
Index ¶
- func DetectEntrypoint(tarData []byte) (string, error)
- func TarFromDirectory(dirPath string) ([]byte, error)
- func TarFromFiles(files []string) ([]byte, error)
- func TarFromMap(files map[string]string) ([]byte, error)
- func TarFromReader(r io.Reader, filename string) ([]byte, error)
- type AsyncResponse
- type Client
- func (c *Client) Eval(ctx context.Context, req *SimpleExecRequest) (*ExecutionResult, error)
- func (c *Client) ExecuteAsync(ctx context.Context, tarData []byte, metadata *Metadata) (string, error)
- func (c *Client) ExecuteSync(ctx context.Context, tarData []byte, metadata *Metadata) (*ExecutionResult, error)
- func (c *Client) GetExecution(ctx context.Context, executionID string) (*ExecutionResult, error)
- func (c *Client) KillExecution(ctx context.Context, executionID string) error
- func (c *Client) WaitForCompletion(ctx context.Context, executionID string, pollInterval time.Duration) (*ExecutionResult, error)
- type CodeFile
- type ExecutionConfig
- type ExecutionResult
- type ExecutionStatus
- type KillResponse
- type Metadata
- type Option
- type SimpleExecRequest
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DetectEntrypoint ¶
DetectEntrypoint finds the Python entrypoint in a tar archive.
The detection order is:
- main.py (highest priority)
- __main__.py
- First .py file found
Returns an error if no Python files are found.
Example ¶
ExampleDetectEntrypoint demonstrates automatic entrypoint detection.
package main
import (
"fmt"
"github.com/geraldthewes/python-executor/pkg/client"
)
func main() {
// Archive with main.py
tarData, _ := client.TarFromMap(map[string]string{
"main.py": `print("main")`,
"helper.py": `print("helper")`,
})
entrypoint, err := client.DetectEntrypoint(tarData)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Detected entrypoint: %s\n", entrypoint)
}
Output: Detected entrypoint: main.py
func TarFromDirectory ¶
TarFromDirectory creates an uncompressed tar archive from a directory.
All files in the directory are added recursively with relative paths.
Example:
tarData, err := client.TarFromDirectory("./myproject")
func TarFromFiles ¶
TarFromFiles creates an uncompressed tar archive from a list of file paths.
Each file is added to the archive with its base name (not the full path).
Example:
tarData, err := client.TarFromFiles([]string{
"main.py",
"utils.py",
"config.json",
})
func TarFromMap ¶
TarFromMap creates a tar archive from a map of filename to content.
This is the most convenient way to create a tar archive for simple scripts.
Example:
tarData, err := client.TarFromMap(map[string]string{
"main.py": `print("Hello!")`,
"helper.py": `def greet(): print("Hi!")`,
})
Example ¶
ExampleTarFromMap demonstrates creating a tar archive from a map.
package main
import (
"fmt"
"github.com/geraldthewes/python-executor/pkg/client"
)
func main() {
files := map[string]string{
"main.py": `print("Hello from main!")`,
"helper.py": `def greet(): return "Hi!"`,
}
tarData, err := client.TarFromMap(files)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Created tar archive: %t\n", len(tarData) > 0)
}
Output: Created tar archive: true
func TarFromReader ¶
TarFromReader creates a tar archive from an io.Reader (e.g., stdin).
The content is read entirely and stored in the archive under the given filename.
Example:
tarData, err := client.TarFromReader(os.Stdin, "main.py")
Types ¶
type AsyncResponse ¶
type AsyncResponse struct {
ExecutionID string `json:"execution_id"`
}
AsyncResponse is returned when submitting async execution.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the Go client for the python-executor service.
Create a new client with New and use methods like Client.ExecuteSync and Client.ExecuteAsync to execute Python code remotely.
func New ¶
New creates a new python-executor client.
The baseURL should point to the python-executor server, e.g., "http://pyexec.cluster:9999/" or "http://localhost:8080".
Options can be used to customize the client:
c := client.New("http://localhost:8080",
client.WithTimeout(60 * time.Second),
)
Example ¶
ExampleNew demonstrates creating a new client.
package main
import (
"fmt"
"time"
"github.com/geraldthewes/python-executor/pkg/client"
)
func main() {
// Create a client with default settings
c := client.New("http://pyexec.cluster:9999/")
_ = c
// Create a client with custom timeout
c2 := client.New("http://localhost:8080",
client.WithTimeout(60*time.Second),
)
_ = c2
fmt.Println("Clients created")
}
Output: Clients created
func (*Client) Eval ¶
func (c *Client) Eval(ctx context.Context, req *SimpleExecRequest) (*ExecutionResult, error)
Eval executes Python code using the simplified JSON API with REPL-style evaluation.
This method uses the /api/v1/eval endpoint which accepts JSON instead of multipart/form-data with tar archives, making it ideal for simple code execution.
When EvalLastExpr is true (the default for this endpoint), the last expression in the code will be evaluated and its value returned in the Result field.
Example:
result, err := c.Eval(ctx, &client.SimpleExecRequest{
Code: "x = 5\nx * 2",
EvalLastExpr: true,
})
if err != nil {
return err
}
fmt.Println(*result.Result) // Output: 10
func (*Client) ExecuteAsync ¶
func (c *Client) ExecuteAsync(ctx context.Context, tarData []byte, metadata *Metadata) (string, error)
ExecuteAsync submits Python code for asynchronous execution.
Returns an execution ID immediately. Use Client.GetExecution to check status or Client.WaitForCompletion to wait for the result.
Example:
execID, err := c.ExecuteAsync(ctx, tarData, &client.Metadata{
Entrypoint: "main.py",
})
if err != nil {
return err
}
// Later, wait for completion
result, err := c.WaitForCompletion(ctx, execID, 2*time.Second)
func (*Client) ExecuteSync ¶
func (c *Client) ExecuteSync(ctx context.Context, tarData []byte, metadata *Metadata) (*ExecutionResult, error)
ExecuteSync executes Python code and waits for the result.
This method blocks until execution completes. Use Client.ExecuteAsync for long-running scripts.
Example:
tarData, _ := client.TarFromMap(map[string]string{
"main.py": `print("Hello!")`,
})
result, err := c.ExecuteSync(ctx, tarData, &client.Metadata{
Entrypoint: "main.py",
})
if err != nil {
return err
}
fmt.Printf("Exit code: %d\n", result.ExitCode)
fmt.Printf("Output: %s\n", result.Stdout)
func (*Client) GetExecution ¶
GetExecution retrieves the current status and result of an execution.
Returns the execution status which may be pending, running, completed, failed, or killed. Once completed, the result includes stdout, stderr, and exit code.
func (*Client) KillExecution ¶
KillExecution terminates a running execution.
The Docker container running the Python code will be forcefully stopped.
func (*Client) WaitForCompletion ¶
func (c *Client) WaitForCompletion(ctx context.Context, executionID string, pollInterval time.Duration) (*ExecutionResult, error)
WaitForCompletion polls the server until the execution completes.
The method polls at the specified interval until the execution reaches a terminal state (completed, failed, or killed).
Example:
execID, _ := c.ExecuteAsync(ctx, tarData, metadata)
// Poll every 2 seconds
result, err := c.WaitForCompletion(ctx, execID, 2*time.Second)
if err != nil {
return err
}
fmt.Println(result.Stdout)
type CodeFile ¶
type CodeFile struct {
Name string `json:"name"` // filename (e.g., "main.py")
Content string `json:"content"` // file content
}
CodeFile represents a single file with its content
type ExecutionConfig ¶
type ExecutionConfig struct {
// TimeoutSeconds is the maximum execution time (default: 300).
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
// NetworkDisabled disables network access if true (default: true).
NetworkDisabled bool `json:"network_disabled,omitempty"`
// MemoryMB is the memory limit in megabytes (default: 1024).
MemoryMB int `json:"memory_mb,omitempty"`
// DiskMB is the disk space limit in megabytes (default: 2048).
DiskMB int `json:"disk_mb,omitempty"`
CPUShares int `json:"cpu_shares,omitempty"`
}
ExecutionConfig holds resource limits and execution settings.
Example:
config := &client.ExecutionConfig{
TimeoutSeconds: 60,
NetworkDisabled: false,
MemoryMB: 2048,
}
type ExecutionResult ¶
type ExecutionResult struct {
// ExecutionID is the unique identifier for this execution.
ExecutionID string `json:"execution_id"`
// Status is the current execution state.
Status ExecutionStatus `json:"status"`
// Stdout is the standard output from the Python script.
Stdout string `json:"stdout,omitempty"`
// Stderr is the standard error from the Python script.
Stderr string `json:"stderr,omitempty"`
// ExitCode is the process exit code (0 = success).
ExitCode int `json:"exit_code"`
// Error is an error message if the execution failed internally.
Error string `json:"error,omitempty"`
// ErrorType is the Python exception type (e.g., "SyntaxError", "NameError").
ErrorType string `json:"error_type,omitempty"`
// ErrorLine is the line number where the error occurred.
ErrorLine int `json:"error_line,omitempty"`
// StartedAt is when execution started (UTC).
StartedAt *time.Time `json:"started_at,omitempty"`
// FinishedAt is when execution finished (UTC).
FinishedAt *time.Time `json:"finished_at,omitempty"`
// DurationMs is the total execution time in milliseconds.
DurationMs int64 `json:"duration_ms,omitempty"`
// Result contains the value of the last expression when EvalLastExpr is true.
// The value is the repr() of the Python object, or null if the last
// statement was not an expression.
Result *string `json:"result,omitempty"`
}
ExecutionResult contains the output and status of an execution.
type ExecutionStatus ¶
type ExecutionStatus string
ExecutionStatus represents the status of a code execution.
const ( // StatusPending indicates the execution is queued but not yet started. StatusPending ExecutionStatus = "pending" // StatusRunning indicates the execution is currently in progress. StatusRunning ExecutionStatus = "running" // StatusCompleted indicates the execution finished (check ExitCode for success). StatusCompleted ExecutionStatus = "completed" // StatusFailed indicates the execution failed due to an internal error. StatusFailed ExecutionStatus = "failed" // StatusKilled indicates the execution was terminated by the user. StatusKilled ExecutionStatus = "killed" )
Execution status constants.
type KillResponse ¶
type KillResponse struct {
Status string `json:"status"`
}
KillResponse is returned when killing an execution.
type Metadata ¶
type Metadata struct {
// Entrypoint is the Python file to execute (required).
Entrypoint string `json:"entrypoint"`
// DockerImage is the Docker image to use (default: python:3.11-slim).
DockerImage string `json:"docker_image,omitempty"`
// RequirementsTxt is the contents of requirements.txt for pip install.
RequirementsTxt string `json:"requirements_txt,omitempty"`
// PreCommands are shell commands to run before Python execution.
PreCommands []string `json:"pre_commands,omitempty"`
// Stdin is data to provide on standard input.
Stdin string `json:"stdin,omitempty"`
// Config contains resource limits and settings.
Config *ExecutionConfig `json:"config,omitempty"`
// EnvVars are environment variables in "KEY=value" format.
EnvVars []string `json:"env_vars,omitempty"`
// ScriptArgs are arguments passed to the Python script (sys.argv).
ScriptArgs []string `json:"script_args,omitempty"`
// EvalLastExpr enables REPL-style behavior for simple execution.
// Internal use only - set via SimpleExecRequest.EvalLastExpr.
EvalLastExpr bool `json:"-"`
}
Metadata contains execution parameters sent to the server.
At minimum, Entrypoint must be specified. All other fields are optional.
Example:
metadata := &client.Metadata{
Entrypoint: "main.py",
RequirementsTxt: "requests\nnumpy",
Config: &client.ExecutionConfig{
NetworkDisabled: false, // Allow network for pip
},
}
type Option ¶
type Option func(*Client)
Option is a functional option for configuring the Client.
func WithHTTPClient ¶
WithHTTPClient sets a custom HTTP client.
Use this to configure custom transport settings, proxies, or TLS configuration.
Example:
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
httpClient := &http.Client{Transport: transport}
c := client.New(url, client.WithHTTPClient(httpClient))
func WithTimeout ¶
WithTimeout sets the HTTP client timeout.
The default timeout is 5 minutes.
Example:
c := client.New(url, client.WithTimeout(60*time.Second))
type SimpleExecRequest ¶
type SimpleExecRequest struct {
// Code is the source code to execute (for single-file execution)
// If provided, creates a main.py with this content
Code string `json:"code,omitempty"`
// Files allows multiple files to be provided (Piston-compatible)
// Takes precedence over Code if both are provided
Files []CodeFile `json:"files,omitempty"`
// Entrypoint is the file to execute (defaults to "main.py")
Entrypoint string `json:"entrypoint,omitempty"`
// Stdin is the standard input to provide to the script
Stdin string `json:"stdin,omitempty"`
// Config contains execution resource limits
Config *ExecutionConfig `json:"config,omitempty"`
// PythonVersion specifies the Python version to use (e.g., "3.10", "3.11", "3.12", "3.13")
// If not specified, uses the server default (typically 3.12)
PythonVersion string `json:"python_version,omitempty"`
// EvalLastExpr enables REPL-style behavior: if the last statement is an
// expression, its value is captured and returned in the Result field.
// Only applies to single-file code execution.
EvalLastExpr bool `json:"eval_last_expr,omitempty"`
// RequirementsTxt allows explicit package specification.
// These are merged with auto-detected packages (user-provided takes precedence).
// Set to "-" to disable auto-detection entirely for this request.
RequirementsTxt string `json:"requirements_txt,omitempty"`
}
SimpleExecRequest is the JSON-only execution request format Compatible with Replit/Piston-style APIs for simpler integrations