micropython

package module
v0.0.0-...-690ffba Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

README

Embedded MicroPython Environment For Go

Go Reference

If you're hovering over this it's already too late
Figure 1. MicroPython Go.

micropython-go is a cgo-free embeddable interpreter for MicroPython.

It uses a custom WASM build of MicroPython, transpiled to native Go code using wasm2go.

[!IMPORTANT] This project is still experimental and until a tagged (and stable) version is released, both the public API and internal modules are subject to breaking changes.

Installation

go get github.com/gregfurman/micropython-go

Quick Start

The following shows an example of creating a MicroPython instance, and using it's builtin len function to ascertain the length of a string:

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	// Boots an interpreter.
	in, err := micropython.NewInstance(ctx)
	if err != nil {
		log.Fatal(err)
	}
	// Close should be called to ensure this cleans-up
	defer in.Close()

	// Call reaches any Python global by name, builtins included. Arguments here are
	// ordinary Go values, lowered to an equivalent Python type on the way in.
	result, err := in.Call(ctx, "len", "abc")
	if err != nil {
		log.Fatal(err)
	}

	// Results come back converted too. Every Python int arrives as an int64.
	fmt.Printf("The length of 'abc' is %d\n", result.(int64))
}

// Output:
// The length of 'abc' is 3

Instances

An Instance is one long-running interpreter. State persists between evaluations, and calls are fast because nothing is rewound between them.

It is safe to call from several goroutines, but a single interpreter runs one call at a time, so concurrent calls queue rather than overlap. For parallelism, give each worker its own interpreter with Clone, or use a Program.

in, _ := micropython.NewInstance(ctx)
defer in.Close()

in.Exec(ctx, "total = 0")
in.Exec(ctx, "total += 5")

val, _ := in.Eval(ctx, "total * 2")
fmt.Println(val) // 10

Programs

A Program compiles a script once and serves calls from a pool of interpreters, so Call is safe across goroutines.

Compiling boots one interpreter, runs the source, and snapshots its memory. Later instances are restored from that snapshot rather than booted, so each call starts from the post-script state without re-running it. Nothing in the Python heap survives a call; effects that reached outside the guest are not rewound.

p, err := micropython.CompileSource(ctx, `
def score(row):
    total = row["a"] * 2 + row["b"]
    return {"id": row["id"], "score": total, "ok": total > 10}
`)
if err != nil {
	log.Fatal(err)
}
defer p.Close()

got, err := p.Call(ctx, "score", map[string]any{"id": "r-1", "a": 4, "b": 5})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("%#v\n", got)
// map[string]interface {}{"id":"r-1", "ok":true, "score":13}

The pool defaults to runtime.NumCPU(); set it with WithPoolSize.

Heap size

An interpreter costs its Python heap plus about 250 KiB of interpreter image, and nothing is shared between them. The heap defaults to 128 KiB, so a single Instance is roughly 0.4 MiB.

A Program also holds a snapshot and builds interpreters as concurrency demands them, up to WithPoolSize: about 0.7 MiB idle, and about 2.3 MiB after twelve calls have run in parallel.

Restoring a snapshot copies the heap, so smaller heaps also make isolated calls cheaper:

p, err := micropython.CompileSource(ctx, src, micropython.WithHeapSize(256*1024))

Too small and the guest raises MemoryError, leaving the Program usable. Size it to what your script actually allocates.

Values

Arguments are ordinary Go values, converted recursively. Custom structs go through an encoding/json fallback.

Go Type Python Type
nil None
bool bool
all signed and unsigned integer types int
float32, float64 float
string str
[]byte bytes
non-byte slices and arrays list
Go maps dict
micropython.Tuple(...) tuple
micropython.Set(...) set
micropython.FrozenSet(...) frozenset
struct{...} JSON round-trip to dict
micropython.Of(v) whatever the rules above make of v, explicitly

Where Go's type doesn't preserve the Python distinction you want (e.g a slice could mean list or tuple) use the builders:

p.Call(ctx, "f", []any{1, 2})                           // list
p.Call(ctx, "f", micropython.Tuple(micropython.Int(1))) // tuple

Configuration can be bound as globals instead of spliced into the source:

p, err := micropython.CompileSource(ctx, src, micropython.WithGlobals(micropython.Globals{
    "NAME":   micropython.Str("service"),
    "LIMITS": micropython.Dict(micropython.Item{Key: micropython.Str("retries"), Val: micropython.Int(3)}),
}))

Host functions

DefineFunction binds a Go function to a global Python callable.

Note, that when using micropython.WithHostFunc, host functions will be prior to a source script being loaded in. This allows for scripts to reference host functions.

in, _ := micropython.NewInstance(ctx)
defer in.Close()

rates := map[string]float64{"EUR": 1.09, "GBP": 1.27}

in.DefineFunction(ctx, "usd", func(args []any) (any, error) {
    code := args[0].(string)
    rate, ok := rates[code]
    if !ok {
        return nil, micropython.Raise("KeyError", code)
    }
    return rate * float64(args[1].(int64)), nil
})

out, _ := in.Exec(ctx, `print(round(usd("EUR", 100), 2))`)
fmt.Print(out) // 109.0

Arguments and return values convert per the table above. A binding is part of interpreter state, so it lasts for the life of the Instance and any Clone taken afterwards inherits it. In addition, when used with a Program, the same host function closure will be shared across instances.

Errors and cancellation

A guest that raises comes back as an ordinary Go error and leaves the interpreter usable:

var exc *micropython.PythonError
if _, err := p.Call(ctx, "lookup", "missing"); errors.As(err, &exc) {
    fmt.Println(exc.Type())    // KeyError
    fmt.Println(exc.Message()) // missing
    fmt.Println(exc.Raw())     // the traceback as MicroPython printed it
}

In the other direction, an error returned from a host function raises at the Python call site as HostError, a class this port adds so guest code can single out host-boundary failures. It subclasses RuntimeError, so existing handlers still catch it. micropython.Raise resolves against the builtin exceptions instead, falling back to HostError for unknown names:

return nil, micropython.Raise("KeyError", code) // guest catches KeyError

A panic inside a host function is recovered and raised as HostError rather than unwinding into the interpreter.

A call stops when its context does. An in-flight Instance can also be interrupted from another goroutine with Cancel:

ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
_, err := p.Call(ctx, "maybe_forever") // context.DeadlineExceeded

Compatability & Limitations

See the MicroPython docs for details on how it differs from CPython.

Also, see SUPPORT_MATRIX.md for how a micropython-go instance differs from that of a traditional MicroPython build.

Functionally, this implementation differs due to:

  • No standard I/O or filesystem: import cannot reach real files, open() raises OSError, os and sys.stdout are absent, and print() output is returned by Exec rather than written to stdout.
  • Stack depth: recursion is bounded by the host C stack to roughly 340-385 Python frames, depending on how many arguments and locals each frame carries. Overflowing raises RuntimeError and leaves the interpreter usable. The limit is MICROPY_C_STACK_SIZE in build/mpconfigport.h, set to 96 KiB.
  • Structs via JSON: scalars, maps, and slices use direct values; custom Go structs go through encoding/json. Prefer maps on hot paths.

Contributing

Contributions are welcome, especially on the C and build tooling. Since I work primarily in Go, and those parts were written with AI assistance.

Changing the C sources or build configuration means recompiling the WebAssembly module. The embedded MicroPython sources are v1.28.0. You need wasi-sdk 25+ and Binaryen, which makes the generated Go module safe for the garbage collector.

export WASI_SDK_PATH=/path/to/wasi-sdk-33.0
export BINARYEN_PATH=/path/to/binaryen

make       # builds out/guest.wasm, regenerates internal/micropython/micropython.go
make test

Documentation

Overview

Example

Compile once, call many times. A Program holds the interpreter as it stood after the source ran, so every call starts from that point rather than re-running the module.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	p, err := micropython.CompileSource(ctx, `
def score(row):
    total = row["a"] * 2 + row["b"]
    return {"id": row["id"], "score": total, "ok": total > 10}
`)
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	row := micropython.Of(map[string]any{"id": "r-1", "a": 4, "b": 5})

	got, err := p.Call(ctx, "score", row)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got)

}
Output:
map[id:r-1 ok:true score:13]

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrClosed indicates the interpreter or pool has been shut down and can no longer be used.
	ErrClosed = api.ErrClosed

	// ErrInterrupted indicates the guest execution was halted, either by context cancellation or a manual Cancel call.
	ErrInterrupted = api.ErrInterrupted

	// ErrInstanceNotInitialised is returned if an operation is attempted on an improperly constructed Instance.
	ErrInstanceNotInitialised = errors.New("cannot perform operation on Instance that has not been initialised")
)

Functions

func Raise

func Raise(typ, msg string) error

Raise returns an error that makes the guest raise a specific Python exception. Return it from a HostFunc to control which class the caller sees:

in.DefineFunction(ctx, "lookup", func(args []any) (any, error) {
    return nil, micropython.Raise("KeyError", "missing")
})

Python then catches it as a KeyError. A typ that does not name a builtin exception falls back to HostError, as does any other error a HostFunc returns, carrying that error's text as the message.

Types

type Globals

type Globals map[string]Value

Globals is the set of names a program starts with, and their values.

p, err := micropython.Compile(ctx, src, micropython.WithGlobals(micropython.Globals{
    "NAME":   micropython.Str("service"),
    "LIMITS": micropython.Dict(micropython.Str("retries"), micropython.Int(3)),
}))

The values are the built ones in value.go and nothing else, so a Go type with no Python equivalent is a compile error rather than an encoding one.

type HostFunc

type HostFunc func(args []any) (any, error)

HostFunc is a Go function callable from Python. See Instance.DefineFunction.

type Instance

type Instance struct {
	// contains filtered or unexported fields
}

Instance represents a single, stateful MicroPython interpreter.

Unlike a Program, an Instance maintains state between calls. Variables, imports, and function definitions created during one execution will persist and remain available for subsequent calls.

An Instance is safe for concurrent use across multiple goroutines, but it is strictly sequential. Because it operates on a single linear WebAssembly memory, concurrent calls will queue and execute one at a time. If you need parallel execution, use a Program or Clone this instance.

Example

An Instance is one interpreter that keeps what it is told, for a session rather than a handler. Unlike a Program, state carries between calls.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	in, err := micropython.NewInstance(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()

	if _, err := in.Exec(ctx, `
total = 0

def add(n):
    global total
    total += n
    return total
`); err != nil {
		log.Fatal(err)
	}

	var running []any
	for _, n := range []int64{1, 2, 3} {
		got, err := in.Call(ctx, "add", micropython.Int(n))
		if err != nil {
			log.Fatal(err)
		}
		running = append(running, got)
	}
	fmt.Println(running...)

	// Eval reads one expression back.
	got, err := in.Eval(ctx, "total * 10")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got)

}
Output:
1 3 6
60

func NewInstance

func NewInstance(ctx context.Context, opts ...Option) (*Instance, error)

NewInstance boots a fresh MicroPython interpreter.

If any globals are provided via options, they are injected into the Python environment immediately upon startup.

func (*Instance) Call

func (i *Instance) Call(ctx context.Context, name string, args ...any) (any, error)

Call invokes a Python global function by name, passing the provided arguments, and returns its result translated into a native Go value.

Because the Instance is stateful, the Python function may interact with or mutate globals that persist after the Call returns.

func (*Instance) Cancel

func (i *Instance) Cancel() error

Cancel interrupts any Python execution currently in flight on this instance. The running code receives a KeyboardInterrupt.

Safe from any goroutine. Cancelling an idle interpreter is also safe but does nothing: the request is cleared when the next call begins, so it cannot make a later call fail.

It is best effort. The request lands at the next VM hook, so a guest inside one long C-level operation -- a regex match, a big-int multiply, a sort -- does not stop until that finishes.

func (*Instance) Clone

func (i *Instance) Clone(ctx context.Context) (*Instance, error)

Clone captures a snapshot of the interpreter's current memory and returns a completely independent Instance starting from that exact state.

Cloning momentarily locks the underlying interpreter while the memory is copied, meaning no other calls can execute until the clone is complete.

func (*Instance) Close

func (i *Instance) Close() error

Close gracefully tears down the instance, interrupting any currently executing logic and freeing the underlying WebAssembly memory.

Subsequent operations on this Instance will return ErrClosed.

func (*Instance) DefineFunction

func (i *Instance) DefineFunction(ctx context.Context, name string, fn HostFunc) error

DefineFunction binds a Go function to a global Python name, letting the guest call back into the host.

Arguments arrive already converted to native Go values, following the same rules as the Type Conversion table in reverse; a []any holds a Python list or tuple, and a map[any]any holds a dict. The returned value is converted back using those same rules, so a Value built with Of, Tuple, Str and friends controls the Python type precisely.

Returning an error raises an exception at the Python call site: HostError carrying the error's text, or a specific builtin class if the error came from Raise. HostError subclasses RuntimeError, so guest code can catch host-side failures specifically with "except HostError" while a general "except RuntimeError" still sees them. A panic inside fn is recovered and raised the same way rather than unwinding into the interpreter.

The binding is part of interpreter state, so it persists for the life of the Instance and is inherited by any Clone taken afterwards. Redefining a name replaces it.

Example

DefineFunction lets Python call back into Go. The binding is part of the interpreter's state, so it stays available to everything that runs after it.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	in, err := micropython.NewInstance(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()

	rates := map[string]float64{"EUR": 1.09, "GBP": 1.27}

	// Arguments arrive as native Go values; the result is converted back.
	err = in.DefineFunction(ctx, "usd", func(args []any) (any, error) {
		code, ok := args[0].(string)
		if !ok {
			return nil, fmt.Errorf("usd: want a currency code, got %T", args[0])
		}
		rate, ok := rates[code]
		if !ok {
			return nil, micropython.Raise("KeyError", code)
		}
		return rate * float64(args[1].(int64)), nil
	})
	if err != nil {
		log.Fatal(err)
	}

	out, err := in.Exec(ctx, `
for code in ("EUR", "GBP", "JPY"):
    try:
        print(code, round(usd(code, 100), 2))
    except KeyError as e:
        print(code, "no rate for", e)
`)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(out)

}
Output:
EUR 109.0
GBP 127.0
JPY no rate for JPY
Example (Error)

A host function that fails for a reason with no Python equivalent raises HostError, so guest code can catch host-boundary failures on their own without also catching the interpreter's errors.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	in, err := micropython.NewInstance(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()

	if err := in.DefineFunction(ctx, "fetch", func([]any) (any, error) {
		return nil, errors.New("connection refused")
	}); err != nil {
		log.Fatal(err)
	}

	// In Python, as HostError.
	out, err := in.Exec(ctx, `
try:
    fetch()
except HostError as e:
    print("guest caught:", e)
`)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(out)

	// In Go, as an ordinary error carrying the same text.
	var exc *micropython.PythonError
	if _, err := in.Eval(ctx, "fetch()"); errors.As(err, &exc) {
		fmt.Printf("host saw: %s / %s\n", exc.Type(), exc.Message())
	}

}
Output:
guest caught: connection refused
host saw: HostError / connection refused

func (*Instance) Err

func (i *Instance) Err() error

Err reports whether the interpreter is in an unrecoverable state.

It returns nil if the instance is healthy. If the WebAssembly VM experienced a fatal trap (like memory corruption) or the instance was explicitly Closed, this returns the corresponding error.

func (*Instance) Eval

func (i *Instance) Eval(ctx context.Context, expr string) (any, error)

Eval evaluates a single Python expression (e.g., "1 + 1" or "my_dict['key']") and returns the resulting native Go value.

Unlike Exec, Eval cannot execute multi-line statements or variable assignments.

func (*Instance) Exec

func (i *Instance) Exec(ctx context.Context, src string) (string, error)

Exec runs arbitrary Python source code as a script and returns whatever the script printed to stdout.

Variables, imports, and functions defined during Exec will remain available in the Instance for future calls to Eval, Exec, or Call.

Example

Exec returns whatever the source printed, for code written to print rather than return.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	in, err := micropython.NewInstance(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()

	out, err := in.Exec(ctx, "for i in range(3):\n    print('line', i)\n")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(out)

}
Output:
line 0
line 1
line 2

func (*Instance) Set

func (i *Instance) Set(ctx context.Context, name string, v Value) error

Set directly binds a Go value to a global Python variable by name, bypassing the need to parse source text.

type Item

type Item struct {
	Key Value
	Val Value
}

Item is one entry of a Dict.

type Option

type Option interface {
	ProgramOption
	// contains filtered or unexported methods
}

Option configures an Instance or a Program.

func WithGlobals

func WithGlobals(g Globals) Option

WithGlobals binds each name before the source runs, which is how configuration reaches it without being spliced into the text.

Example

Globals reach the source without being spliced into its text, so nothing has to be quoted or escaped.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	p, err := micropython.CompileSource(ctx, `
def describe():
    return "%s allows %d retries" % (NAME, LIMITS["retries"])
`, micropython.WithGlobals(micropython.Globals{
		"NAME":   micropython.Str("service"),
		"LIMITS": micropython.Dict(micropython.Item{Key: micropython.Str("retries"), Val: micropython.Int(3)}),
	}))
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	got, err := p.Call(ctx, "describe")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got)

}
Output:
service allows 3 retries

func WithHeapSize

func WithHeapSize(bytes int) Option

WithHeapSize sets the interpreter's Python heap, in bytes. Zero takes the module's default.

The heap is most of what an interpreter costs: creating one and rewinding it between calls are both proportional to it, so a program that does not need much is markedly cheaper with less. Too little and the guest raises MemoryError.

func WithHostFunc

func WithHostFunc(name string, fn HostFunc) Option

WithHostFunc binds a Go function to a global Python name before the source runs, so module-level code can call it. Equivalent to DefineFunction, except that a Program registers it ahead of its snapshot and so keeps it across calls; a Program has no other way to define one.

Repeated use accumulates. Binding the same name twice keeps the last.

A Program registers fn once, before the snapshot, so every pooled interpreter shares that one closure. What fn closes over is Go state: the per-call rewind does not reset it, and pooled interpreters call it in parallel.

var n atomic.Int64 // atomic: called from several interpreters at once
p, _ := micropython.CompileSource(ctx, "def f(): return tick()",
    micropython.WithHostFunc("tick", func([]any) (any, error) {
        return n.Add(1), nil
    }))
p.Call(ctx, "f") // 1
p.Call(ctx, "f") // 2, not rewound to 1

func WithSourceScript

func WithSourceScript(src string) Option

WithSourceScript runs src at module level once, before anything else calls in. Globals and host functions are bound first, so src can use them. A Program snapshots the result, so every call starts from it.

micropython.CompileSource(ctx, src) // shorthand for
micropython.Compile(ctx, micropython.WithSourceScript(src))

type Program

type Program struct {
	// contains filtered or unexported fields
}

Program is a pool of pre-compiled MicroPython interpreters, safe for concurrent use.

Compile builds one interpreter, configures it with the given options, and snapshots it. That snapshot is the baseline every later call starts from: a call borrows an interpreter, runs, and the interpreter is rewound to the snapshot before returning to the pool, so calls cannot see each other's changes to Python state.

Close must be called when the Program is no longer needed.

func Compile

func Compile(ctx context.Context, opts ...ProgramOption) (*Program, error)

Compile builds an interpreter from opts and captures it as the Program's starting state. Use CompileSource to run Python source as part of that state.

WithPoolSize sets how many interpreters stay idle between calls, defaulting to runtime.NumCPU; it is not a ceiling on how many exist at once.

func CompileSource

func CompileSource(ctx context.Context, src string, opts ...ProgramOption) (*Program, error)

CompileSource runs src at module level and captures the result as the Program's starting state. Shorthand for Compile with WithSourceScript.

func (*Program) Call

func (p *Program) Call(ctx context.Context, name string, args ...any) (any, error)

Call invokes a named Python function with the provided arguments.

The function executes in total isolation. Any changes made to Python's global state during the execution are discarded before the underlying interpreter is returned to the internal pool.

Example (Cancellation)

A runaway guest stops when the context does. There is no scheduler inside the module, so this is what bounds a call.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	p, err := micropython.CompileSource(context.Background(), `
def spin():
    while True:
        pass

def add(a, b):
    return a + b
`)
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	if _, err := p.Call(ctx, "spin"); errors.Is(err, context.DeadlineExceeded) {
		fmt.Println("stopped at the deadline")
	}

	// And the Program is usable afterwards.
	got, err := p.Call(context.Background(), "add", micropython.Int(20), micropython.Int(22))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got)

}
Output:
stopped at the deadline
42
Example (Concurrent)

Calls run in parallel. A Program keeps a pool of interpreters restored from the same snapshot, so callers do not queue behind each other, and no call can see what another one did.

package main

import (
	"context"
	"fmt"
	"log"
	"sort"
	"sync"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	p, err := micropython.CompileSource(ctx, `
_seen = 0

def visit(n):
    global _seen
    _seen += 1
    return [n * 2, _seen]
`)
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	var (
		mu      sync.Mutex
		doubled []int
		wg      sync.WaitGroup
	)
	for i := range 8 {
		wg.Go(func() {
			got, err := p.Call(ctx, "visit", micropython.Int(int64(i)))
			if err != nil {
				return
			}
			out := got.([]any)
			mu.Lock()
			defer mu.Unlock()
			// out[1] is always 1: each call starts from the snapshot, so the
			// increment another call made is not there.
			doubled = append(doubled, int(out[0].(int64))+int(out[1].(int64))-1)
		})
	}
	wg.Wait()

	sort.Ints(doubled)
	fmt.Println(doubled)

}
Output:
[0 2 4 6 8 10 12 14]
Example (Error)

A guest that raises comes back as an ordinary Go error carrying the exception's type and message.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	p, err := micropython.CompileSource(ctx, `
def lookup(key):
    return {"a": 1}[key]
`)
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	// The error carries the exception, so a caller can branch on which one it
	// was rather than reading the message.
	var exc *micropython.PythonError
	if _, err := p.Call(ctx, "lookup", micropython.Str("missing")); errors.As(err, &exc) {
		fmt.Println(exc.Type(), "/", exc.Message())
	}

	// The Program is unharmed: the failure was the guest's, not the
	// interpreter's.
	got, err := p.Call(ctx, "lookup", micropython.Str("a"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got)

}
Output:
KeyError / missing
1

func (*Program) Close

func (p *Program) Close() error

Close releases every interpreter the Program is holding. Calls after it return ErrClosed.

func (*Program) Instance

func (p *Program) Instance(ctx context.Context) (*Instance, error)

Instance spawns a standalone Python interpreter initialized with the compiled source's state.

Unlike a Program, an Instance is stateful: variable mutations and definitions will persist across evaluations. The returned Instance is completely detached from the Program's pool and must be closed by the caller.

type ProgramOption

type ProgramOption interface {
	// contains filtered or unexported methods
}

ProgramOption configures a Program. Every Option is also a ProgramOption.

func WithPoolSize

func WithPoolSize(n int) ProgramOption

WithPoolSize sets how many idle interpreters a Program keeps between calls. Zero takes runtime.NumCPU.

It bounds what is kept, not what exists. A call that finds none idle builds one, and the surplus is closed on release rather than refused, so peak memory follows concurrency rather than n:

WithPoolSize(4)  // a burst of 256 concurrent calls still builds 256

Each interpreter holds its own linear memory, roughly 1.2 MiB plus WithHeapSize. Set n to the concurrency you expect.

type PythonError

type PythonError = value.Exception

PythonError is the error a failing call returns. Unwrap it to read which exception the guest raised, rather than matching on the message:

var exc *micropython.PythonError
if errors.As(err, &exc) && exc.Type() == "KeyError" { ... }

Exception, by contrast, builds one to send.

type Value

type Value struct {
	// contains filtered or unexported fields
}

Value is a Python value the host has built. It wraps the internal one so this package's surface names nothing a caller cannot import.

Example

Arguments are Python values, built rather than guessed at. A Go slice could be a list, a tuple or a set, so the call says which.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	p, err := micropython.CompileSource(ctx, "def kind(v):\n    return type(v).__name__\n")
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	for _, v := range []micropython.Value{
		micropython.Int(1),
		micropython.Str("hello"),
		micropython.Bytes([]byte{1, 2}),
		micropython.None(),
		micropython.List(micropython.Int(1), micropython.Int(2)),
		micropython.Tuple(micropython.Int(1), micropython.Int(2)),
		micropython.Set(micropython.Int(1)),
		micropython.Dict(micropython.Item{Key: micropython.Str("a"), Val: micropython.Int(1)}),
	} {
		got, err := p.Call(ctx, "kind", v)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(got)
	}

}
Output:
int
str
bytes
NoneType
list
tuple
set
dict

func Bool

func Bool(b bool) Value

Bool converts a Go bool to a Python bool.

func Bytes

func Bytes(b []byte) Value

Bytes converts a Go byte slice to a Python bytes object.

func Dict

func Dict(entries ...Item) Value

Dict creates a Python dictionary from the given key-value items.

func Exception

func Exception(typ, msg string) Value

Exception builds a Python exception as a Value, for binding one the guest can raise.

Example

A host can hand the guest an exception to raise, named so that the guest catches it as itself.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	p, err := micropython.CompileSource(ctx, `
def run():
    try:
        raise BAD
    except ValueError as e:
        return "caught " + str(e)
`, micropython.WithGlobals(micropython.Globals{
		"BAD": micropython.Exception("ValueError", "bad input"),
	}))
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	got, err := p.Call(ctx, "run")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(got)

}
Output:
caught bad input

func Float

func Float(f float64) Value

Float converts a Go float64 to a Python float.

func FrozenSet

func FrozenSet(items ...Value) Value

FrozenSet creates an immutable Python frozenset from the given values.

func Int

func Int(n int64) Value

Int converts a Go int64 to a Python int.

func Ints

func Ints(items ...int64) Value

Ints is a convenience function that creates a Python list of integers.

func List

func List(items ...Value) Value

List creates a Python list from the given values.

func None

func None() Value

None returns a Python None value.

func Of

func Of(v any) Value

Of converts an ordinary Go value to its closest resembling Python representation:

Go value                                Python representation
-------------------------------------------------------------
nil                                     None
bool                                    bool
int, int64, other integers              int
float32, float64                        float
string                                  str
[]byte                                  bytes
[]any                                   list
Tuple                                   tuple
Set, FrozenSet                          set, frozenset
map[string]any, map[any]any             dict
anything else (e.g. structs)            JSON round-trip (dict/list)
Example

Of converts Go data you already have. It is the open end of the boundary: convenient, and a guess where Go has one type for two Python ones -- a slice becomes a list, never a tuple.

package main

import (
	"context"
	"fmt"
	"log"

	micropython "github.com/gregfurman/micropython-go"
)

func main() {
	ctx := context.Background()

	p, err := micropython.CompileSource(ctx, "def kind(v):\n    return type(v).__name__\n")
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	for _, v := range []any{
		[]any{1, 2},
		map[string]any{"a": 1},
		struct {
			A int `json:"a"`
		}{1},
		[]string{"x"},
	} {
		got, err := p.Call(ctx, "kind", micropython.Of(v))
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(got)
	}

	// A value with no Python equivalent reports when the call uses it, so Of
	// can be written inline.
	if _, err := p.Call(ctx, "kind", micropython.Of(func() {})); err != nil {
		fmt.Println("error:", err)
	}

}
Output:
list
dict
dict
list
error: micropython: cannot pass func() to Python: json: unsupported type: func()

func Set

func Set(items ...Value) Value

Set creates a mutable Python set from the given values.

func Str

func Str(s string) Value

Str converts a Go string to a Python str.

func Strs

func Strs(items ...string) Value

Strs is a convenience function that creates a Python list of strings.

func Tuple

func Tuple(items ...Value) Value

Tuple creates a Python tuple from the given values.

func (Value) Type

func (v Value) Type() string

Type names the value as Python would.

Directories

Path Synopsis
internal
api

Jump to

Keyboard shortcuts

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