rage

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2026 License: MIT

README

RAGE

Really Adequate Go-python Engine

RAGE is an embeddable Python 3.14 runtime written in Go. It allows you to run Python code directly from your Go applications without any external dependencies or CGO.

RAGE comes with optional batteries included. If you wan't some of the python standard library there are many modules you can use. If you don't want to use the python standard library at all you don't have to. If you only want to use a few modules from the standard library you can pick and choose which modules are made available to scripts.

Features

  • Pure Go implementation - no CGO, no external Python installation required
  • Embeddable - designed to be used as a library in Go applications
  • Timeout support - prevent infinite loops with execution timeouts
  • Context cancellation - integrate with Go's context for graceful shutdown
  • Standard library modules - math, random, string, sys, time, re, collections, json, os, datetime, typing, asyncio, csv, itertools, functools, io, base64, abc, dataclasses, copy, operator
  • Go interoperability - call Go functions from Python and vice versa

Installation

go get github.com/ATSOTECK/rage

Tests

  • Unit tests: go test ./...
    • In-package runtime tests (internal/runtime/*_test.go) — operations, conversions, items/slicing, type primitives
    • External compile+execute tests (test/*_test.go) — builtins, stdlib modules
    • Compiler tests (internal/compiler/*_test.go)
  • Integration tests: 118 scripts with 2222 tests covering data types, operators, control flow, functions, classes, exceptions, exception groups, generators, comprehensions, closures, decorators, imports, context managers, metaclasses, descriptors, string formatting, dataclasses, copy module, and more
    • Run with go run test/integration/integration_test_runner.go

Quick Start

Run Python Code
package main

import (
    "fmt"
    "log"

    "github.com/ATSOTECK/rage/pkg/rage"
)

func main() {
    // Simple one-liner
    result, err := rage.Run(`print("Hello from Python!")`)
    if err != nil {
        log.Fatal(err)
    }

    // Evaluate an expression
    result, err = rage.Eval(`2 ** 10`)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result) // 1024
}
Using State for More Control
package main

import (
    "fmt"
    "log"

    "github.com/ATSOTECK/rage/pkg/rage"
)

func main() {
    // Create a new execution state
    state := rage.NewState()
    defer state.Close()

    // Set variables accessible from Python
    state.SetGlobal("name", rage.String("World"))
    state.SetGlobal("count", rage.Int(42))

    // Run Python code
    _, err := state.Run(`
greeting = "Hello, " + name + "!"
doubled = count * 2
numbers = [1, 2, 3, 4, 5]
total = sum(numbers)
    `)
    if err != nil {
        log.Fatal(err)
    }

    // Get variables set by Python
    fmt.Println(state.GetGlobal("greeting")) // Hello, World!
    fmt.Println(state.GetGlobal("doubled"))  // 84
    fmt.Println(state.GetGlobal("total"))    // 15
}
Registering Go Functions
package main

import (
    "fmt"
    "strings"

    "github.com/ATSOTECK/rage/pkg/rage"
)

func main() {
    state := rage.NewState()
    defer state.Close()

    // Register a Go function callable from Python
    state.Register("shout", func(s *rage.State, args ...rage.Value) rage.Value {
        if len(args) == 0 {
            return rage.None
        }
        text, _ := rage.AsString(args[0])
        return rage.String(strings.ToUpper(text) + "!")
    })

    // Call it from Python
    state.Run(`message = shout("hello world")`)
    fmt.Println(state.GetGlobal("message")) // HELLO WORLD!
}
Timeouts and Context
package main

import (
    "context"
    "fmt"
    "time"

    "github.com/ATSOTECK/rage/pkg/rage"
)

func main() {
    // Using timeout
    _, err := rage.RunWithTimeout(`
while True:
    pass  # infinite loop
    `, 2*time.Second)
    fmt.Println(err) // execution timeout

    // Using context
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    state := rage.NewState()
    defer state.Close()

    _, err = state.RunWithContext(ctx, `
x = 0
while True:
    x += 1
    `)
    fmt.Println(err) // context deadline exceeded
}

Demo: Python as Configuration

The demo/ directory contains a complete example of RAGE's core use case: replacing static config files (TOML, JSON, YAML) with Python scripts. A Go "game server" loads its configuration from Python, showcasing things static formats can't do:

go run demo/main.go                       # development settings
RAGE_ENV=production go run demo/main.go   # production settings

The Go side registers helpers and injects values, then runs pure Python config files:

state := rage.NewStateWithModules(
    rage.WithModules(rage.ModuleMath, rage.ModuleDataclasses, rage.ModuleCollections),
)
defer state.Close()

// Make Go functions callable from Python
state.Register("env", func(_ *rage.State, args ...rage.Value) rage.Value {
    name, _ := rage.AsString(args[0])
    if v := os.Getenv(name); v != "" {
        return rage.String(v)
    }
    return rage.String("")
})
state.SetGlobal("cpu_count", rage.Int(int64(runtime.NumCPU())))

// Load Python config and extract results as Go types
src, _ := os.ReadFile("config/settings.py")
state.RunWithFilename(string(src), "config/settings.py")
settings, _ := rage.AsDict(state.GetGlobal("settings"))

The Python config scripts use conditionals, computed values, validation, cross-file imports, and comprehensions:

# config/common.py — shared constants, imported by other config files
materials = {"wood": {"damage": 1.0, ...}, "iron": {"damage": 1.5, ...}, ...}
rarities = ["common", "uncommon", "rare", "epic", "legendary"]
zones = ["Forest", "Desert", "Mountains", "Swamp", "Volcano"]
# config/settings.py — environment-aware, self-validating config
environment = env("RAGE_ENV", "development")

if environment == "production":
    db_pool_size = cpu_count * 4    # computed from Go-injected value
else:
    db_pool_size = 2

assert db_pool_size > 0, "db_pool_size must be positive"  # validates at load time
api_url = f"https://{host}:{port}/api/v1"                  # derived string
# config/items.py — imports shared data, generates 15 weapons from a template
from common import materials, rarities

def make_weapon(name, base_damage, material, tier=1):
    mat = materials[material]
    return {"name": f"{material.title()} {name}", "damage": int(base_damage * mat["damage"] * (1 + tier * 0.25)), ...}

swords = [make_weapon("Sword", 15, mat, tier) for mat in materials for tier in [1, 3, 5]]
# config/levels.py — stdlib + local imports, formulas generate 50 levels
import math
from common import zones

xp_for_level = [math.floor(100 * math.pow(1.15, level)) for level in range(50)]

See demo/README.md for the full walkthrough.

Controlling Standard Library Modules

By default, NewState() enables all available stdlib modules. For more control:

// Create state with only specific modules
state := rage.NewStateWithModules(
    rage.WithModule(rage.ModuleMath),
    rage.WithModule(rage.ModuleString),
)
defer state.Close()

// Or enable multiple at once
state := rage.NewStateWithModules(
    rage.WithModules(rage.ModuleMath, rage.ModuleString, rage.ModuleTime),
)

// Create a bare state with no modules
state := rage.NewBareState()
state.EnableModule(rage.ModuleMath)    // Enable one
state.EnableAllModules()                // Or enable all later

// Check what's enabled
if state.IsModuleEnabled(rage.ModuleMath) {
    fmt.Println("Math module is available")
}
Available Modules
Module Constant Description
math rage.ModuleMath Mathematical functions (sin, cos, sqrt, etc.)
random rage.ModuleRandom Random number generation
string rage.ModuleString String constants (ascii_letters, digits, etc.)
sys rage.ModuleSys System information (version, platform)
time rage.ModuleTime Time functions (time, sleep)
re rage.ModuleRe Regular expressions
collections rage.ModuleCollections Container datatypes (Counter, defaultdict)
json rage.ModuleJSON JSON encoding and decoding
os rage.ModuleOS OS interface (environ, path manipulation)
datetime rage.ModuleDatetime Date and time types
typing rage.ModuleTyping Type hint support
asyncio rage.ModuleAsyncio Basic async/await support
csv rage.ModuleCSV CSV file reading and writing
itertools rage.ModuleItertools Iterator building blocks (chain, combinations, permutations, etc.)
functools rage.ModuleFunctools Higher-order functions (partial, reduce, lru_cache, wraps)
io rage.ModuleIO File I/O operations
base64 rage.ModuleBase64 Base16, Base32, Base64 data encodings
abc rage.ModuleAbc Abstract base classes (ABC, ABCMeta, abstractmethod)
dataclasses rage.ModuleDataclasses Data class decorator and field utilities
copy rage.ModuleCopy Shallow and deep copy operations
operator rage.ModuleOperator Operator functions (length_hint, index)

Working with Values

RAGE uses the rage.Value interface to represent Python values.

Creating Values
// Primitives
none := rage.None
b := rage.Bool(true)
i := rage.Int(42)
f := rage.Float(3.14)
c := rage.Complex(1, 2)  // 1+2j
s := rage.String("hello")

// Collections
list := rage.List(rage.Int(1), rage.Int(2), rage.Int(3))
tuple := rage.Tuple(rage.String("a"), rage.String("b"))
dict := rage.Dict("name", rage.String("Alice"), "age", rage.Int(30))

// From Go values (automatic conversion)
val := rage.FromGo(map[string]any{
    "numbers": []any{1, 2, 3},
    "active":  true,
})
Type Checking
if rage.IsInt(val) {
    n, _ := rage.AsInt(val)
    fmt.Println("Integer:", n)
}

if rage.IsList(val) {
    items, _ := rage.AsList(val)
    for _, item := range items {
        fmt.Println(item)
    }
}

if rage.IsDict(val) {
    dict, _ := rage.AsDict(val)
    for k, v := range dict {
        fmt.Printf("%s: %v\n", k, v)
    }
}
Converting to Go Values
val := state.GetGlobal("result")

// Get the underlying Go value
goVal := val.GoValue()

// Or use type-specific helpers
if n, ok := rage.AsInt(val); ok {
    fmt.Println("Got integer:", n)
}

Compile Once, Run Many

For repeated execution, compile once and run multiple times:

state := rage.NewState()
defer state.Close()

// Compile once
code, err := state.Compile(`result = x * 2`, "multiply.py")
if err != nil {
    log.Fatal(err)
}

// Execute multiple times with different inputs
for i := 0; i < 5; i++ {
    state.SetGlobal("x", rage.Int(int64(i)))
    state.Execute(code)
    fmt.Println(state.GetGlobal("result"))
}
// Output: 0, 2, 4, 6, 8

Error Handling

// Compilation errors
_, err := rage.Run(`def broken syntax`)
if compErr, ok := err.(*rage.CompileErrors); ok {
    for _, e := range compErr.Errors {
        fmt.Println("Compile error:", e)
    }
}

// Runtime errors
_, err = rage.Run(`x = 1 / 0`)
if err != nil {
    fmt.Println("Runtime error:", err)
}

Current Status

RAGE is under active development. Currently supported:

Implemented
  • Data types: None, bool, int, float, complex, str, bytes, bytearray, list, tuple, dict, set, frozenset, range, slice
  • Operators: arithmetic, comparison, logical, bitwise, matrix multiplication (@), in-place operations
  • Control flow: if/elif/else, for, while, break, continue, pass, match/case
  • Functions: def, lambda, recursion, closures, *args, **kwargs, default arguments, nonlocal
  • Classes: class definitions, __init__, __new__, instance attributes, methods, single and multiple inheritance (C3 linearization), properties, classmethods, staticmethods, metaclasses (class Foo(metaclass=Meta)), __slots__, __init_subclass__, __set_name__
  • Exception handling: try/except/else/finally, raise, raise from, custom exception types, exception attributes (.args, .__cause__, .__context__), exception groups (ExceptionGroup, BaseExceptionGroup, except* syntax with .subgroup(), .split(), .derive())
  • Generators: yield, yield from, generator expressions
  • Decorators: function and class decorators
  • Comprehensions: list [x for x in items], dict {k: v for k, v in items}, set {x for x in items}
  • Imports: import, from...import, relative imports
  • Context managers: with statement support
  • String formatting: f-strings, % printf-style (%s, %d, %f, %e, %g, %x, %o, %c, %(key)s dict formatting, flags, * width/precision)
  • Walrus operator: assignment expressions (:=)
  • Extended unpacking: a, *rest, b = [1, 2, 3, 4]
  • Descriptor protocol: __get__, __set__, __delete__, __set_name__ (data descriptors, non-data descriptors, class-level access)
  • Dunder methods for custom classes: __new__, __init__, __del__, __str__, __repr__, __call__, __hash__, __len__, __iter__, __next__, __contains__, __getattr__, __getattribute__, __setattr__, __delattr__, __dir__, __getitem__, __setitem__, __delitem__, __missing__, __enter__, __exit__, __mro_entries__, __bool__, __int__, __index__, __abs__, __neg__, __pos__, __invert__, __bytes__, __format__, __sizeof__, __copy__, __deepcopy__, __class_getitem__, operator overloading (__add__, __sub__, __mul__, __matmul__, __eq__, __lt__, etc. including reflected variants)
  • Built-in functions: print (with sep, end, flush), len, range, str, int, float, complex, bool, list, dict, tuple, set, bytes, bytearray, type, isinstance, issubclass, abs, min, max, sum, enumerate, zip, map, filter, any, all, reversed, sorted, repr, format, input, ord, chr, hasattr, getattr, setattr, delattr, dir, vars, id, pow, divmod, hex, oct, bin, round, callable, property, classmethod, staticmethod, super, iter, next
Not Yet Implemented
  • Full async/await - async generators, async context managers (basic support via asyncio module)
Security Notes

Reflection builtins (globals, locals, compile, exec, eval) are opt-in and disabled by default. Enable them explicitly if needed:

state := rage.NewStateWithModules(
    rage.WithAllModules(),
    rage.WithBuiltin(rage.BuiltinGlobals),
    rage.WithBuiltin(rage.BuiltinExec),
)

Thread Safety

Each State is NOT safe for concurrent use. Create separate States for concurrent execution, or use appropriate synchronization.

// Safe: separate states per goroutine
for i := 0; i < 10; i++ {
    go func(n int) {
        state := rage.NewState()
        defer state.Close()
        state.SetGlobal("n", rage.Int(int64(n)))
        state.Run(`result = n * n`)
    }(i)
}

License

MIT

Contributing

pls

Directories

Path Synopsis
cmd
rage command
Demo: Python-as-Configuration for a game server.
Demo: Python-as-Configuration for a game server.
internal
stdlib
Package stdlib provides standard library modules for the RAGE Python runtime.
Package stdlib provides standard library modules for the RAGE Python runtime.
pkg
rage
Package rage provides a public API for embedding the RAGE Python runtime in Go applications.
Package rage provides a public API for embedding the RAGE Python runtime in Go applications.
test
integration command
Package main runs all Python test scripts and validates their output.
Package main runs all Python test scripts and validates their output.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL