micropython

package module
v0.0.0-...-f701c09 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

Embedded MicroPython for Go

micropython-go

Go Reference Test Build

micropython-go embeds MicroPython in Go applications without CGO. Run Python scripts, exchange values, and call Go functions from Python.

It uses a custom WebAssembly build of MicroPython, translated to native Go code with wasm2go.

[!IMPORTANT] This project is experimental. The API may change before a stable release.

Installation

Requires Go 1.27 or later. You do not need the WebAssembly build tools to use the SDK.

go get github.com/gregfurman/micropython-go

Quick start

package main

import (
	"context"
	"fmt"
	"log"

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

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

	// Create an interpreter.
	in, err := micropython.NewInstance(ctx)
	if err != nil {
		log.Fatal(err)
	}
	// Release resources when done.
	defer in.Close()

	// Define a Python function.
	if err := in.Exec(ctx, "double = lambda x: x * 2"); err != nil {
		log.Fatal(err)
	}

	// Call it from Go.
	got, err := in.Call(ctx, "double", 10)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(got.Export())
}

// Output:
// 20

Execution

Use an Instance for a persistent Python session, or a Program to run repeatedly from the same initialized state.

Snippets below assume an existing ctx and the relevant imports. Error handling is omitted for brevity except inside callbacks; check errors before using returned resources in your application.

Instance

An Instance keeps Python state between calls. Create one with NewInstance(ctx) and close it when done. Calls are serialized; use separate instances for parallel execution.

in, _ := micropython.NewInstance(ctx,
	micropython.WithSource("def double(x): return x * 2"),
)
defer in.Close()

got, _ := in.Call(ctx, "double", 10)
fmt.Println(got.Export()) // 20
Program

A Program initializes Python once and saves its state. Each Run borrows an interpreter starting from that state, then resets it afterwards. Runs can execute concurrently.

p, _ := micropython.NewProgram(ctx,
	micropython.WithSource("def double(x): return x * 2"),
)
defer p.Close()

err := p.Run(ctx, func(in *micropython.BorrowedInstance) error {
	got, err := in.Call(ctx, "double", 10)
	if err != nil {
		return err
	}
	fmt.Println(got.Export()) // 20
	return nil
})

Do not retain the borrowed instance or Python object handles after the callback returns. External effects, such as file writes, are not undone. See Program lifetimes and pooling.

Configuration

Initialization

Use options to set globals, register Go callbacks, and run initialization code. The same options work with NewInstance and NewProgram.

// Define your Go callback
louder := func(_ context.Context, args []micropython.Value) (micropython.Value, error) {
	if len(args) != 1 {
		return micropython.Value{}, micropython.Raise("ValueError", "expected a single arg")
	}
	msg, err := args[0].AsString()
	if err != nil {
		return micropython.Value{}, err
	}
	return micropython.Str(strings.ToUpper(msg)), nil
}

// Create an Instance
in, _ := micropython.NewInstance(ctx,
	micropython.WithGlobals(micropython.Globals{"LOCATION": "New York"}), // define a global
	micropython.WithHostFunc("host_louder", louder),                      // expose a Go callback
	micropython.WithSource(`def loud_greeting(): return host_louder("hello from " + LOCATION)`),
)
defer in.Close()

got, _ := in.Call(ctx, "loud_greeting")
fmt.Println(got.Export()) // HELLO FROM NEW YORK

See initialization and calling Go from Python.

Using as a sandbox

By default, Python has no access to host files, environment variables, output, or networking. Grant only the capabilities your code needs:

root, _ := os.OpenRoot("./sandbox") // directory must already exist
defer root.Close()

in, _ := micropython.NewInstance(ctx,
	micropython.WithStdout(os.Stdout),                      // send print() to host stdout
	micropython.WithEnv("ENV", "dev"),                      // set a Python environment variable
	micropython.WithFS(root.FS()),                          // read-only access to ./sandbox at /
	micropython.WithTCPAccess("127.0.0.1", 8000),           // allow outbound TCP to this address and port
	micropython.WithTCPAccess(micropython.AnyAddress, 443), // allow outbound TCP to any IPv4 address on port 443
	micropython.WithDNSResolver(net.DefaultResolver),       // enable DNS resolution
	micropython.WithHeapSize(256*1024),                     // Python heap size in bytes
)
defer in.Close()

These options also work with NewProgram. AnyAddress includes private networks; port 443 is not an HTTPS-only restriction. The heap size is not a total-memory limit, and cancellation is best effort. See the sandboxing guide for access rules and resource limits.

Go and Python values

Pass ordinary Go values to Python; results come back as micropython.Value. For example, using an existing instance in:

in.Set(ctx, "numbers", []int{1, 2, 3}) // Go slice → Python list
got, _ := in.Eval(ctx, "sum(numbers)")
n, _ := got.AsInt()                  // Python int → Go int64
fmt.Println(n)                      // 6

Use Export() for ordinary Go data, or methods such as AsInt() and AsString() for checked conversions. See the conversion tables for supported types and object handles.

Documentation and examples

Example Shows
Basics Execute Python and exchange values
Programs Initialize once and run concurrently
Values Convert between Go and Python types
Go callbacks Call Go functions from Python
Files Expose a filesystem to Python
Networking Grant outbound connections

See all examples for output, cancellation, object handles, and memory sizing.

go run ./examples/basic

This build supports a subset of MicroPython, not full CPython. See limitations.

Why this project?

As the old programming adage goes: "Never trust user input.", the obvious corollary being "Never trust arbitrary executable code supplied by anyone, ever, AT ANY TIME." While the latter doesn't roll off the tongue quite as nicely, this is a real issue for projects wanting to let users supply code or custom scripts without compromising the host application.

I wanted a sandboxed way for users to execute Python(-ic) code within a Go application, without the overhead of embedding CPython—and so micropython-go was born 🐍🦫 [^1]

  • Why MicroPython? Designed initially for embedded systems and microcontrollers, MicroPython strikes a brilliant balance between resource efficiency and feature richness. While CPython could do the job, I wanted a smaller dependency for my Go applications.

  • Why Python rather than another scripting language? Projects like Common Expression Language (CEL) offer Go-native ways to evaluate user-supplied expressions. I wanted Python-like syntax because it's familiar to many engineers, which means one less documentation site to frequent. Transpiling MicroPython to Go also lets me build on an existing language rather than maintain a new one.

  • Why not just use a WASM runtime? I wanted the interpreter to fit naturally into a Go application, including how it exposes host functionality. Transpiling to Go lets me do that without shipping a separate WASM runtime.

  • Why this project versus the alternatives? I've yet to see another project maintainer spend their entire weekend trying to get a gopher snake onto a gopher's head… If that didn't convince you, hopefully the small memory footprint, Go/Python value conversions, and sandboxing functionality will.

Contributing

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

Go changes need nothing beyond go test ./.... Changing the C sources or the build configuration means recompiling the WebAssembly module and regenerating its Go translation, which needs wasi-sdk and Binaryen.

git submodule update --init
export WASI_SDK=/path/to/wasi-sdk
export BINARYEN=/path/to/binaryen
./build/build.sh                       # regenerates internal/micropython
go test ./...

Both variables default to tools/wasi-sdk and tools/binaryen.

License

Apache 2.0. MicroPython is MIT licensed; see the micropython submodule.

[^1]: I'm aware this is a beaver and not a gopher, but an artist is limited by their medium.

Documentation

Overview

Package micropython embeds MicroPython in Go without CGO.

Use NewInstance for an interpreter that keeps state between calls, or NewProgram to start each run from the same initialized Python state. Close instances and programs when done.

Calls accept Go values and return Value results. Use Value.Export for ordinary Go data or the As methods for checked, type-specific access.

Host access is opt-in. Use WithFS for files, WithEnv for environment variables, WithStdout for output, and WithHostFunc for Go callbacks. WithTCPAccess and WithUDPAccess grant outbound connections; WithDNSResolver separately enables DNS. These options work with both instances and programs. They do not impose a hard CPU or total-memory limit.

Example

The README's quick start: one interpreter, a script, and a call.

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, "double = lambda x: x * 2"); err != nil {
		log.Fatal(err)
	}

	got, err := in.Call(ctx, "double", 10)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(got.Export())

}
Output:
20

Index

Examples

Constants

View Source
const AnyAddress = network.AnyAddress

AnyAddress matches all supported destination addresses, including loopback and private networks.

Variables

View Source
var (
	// ErrClosed reports a closed interpreter or pool.
	ErrClosed = api.ErrClosed

	// ErrInterrupted identifies an interrupted call.
	ErrInterrupted = api.ErrInterrupted

	// ErrInstanceNotInitialised reports an Instance that was not initialized.
	ErrInstanceNotInitialised = errors.New("cannot perform operation on Instance that has not been initialised")
)
View Source
var ErrRunReturned = errors.New("micropython: BorrowedInstance used after Run returned")

ErrRunReturned is reported by a BorrowedInstance used after its Program.Run callback returned.

Functions

func Raise

func Raise(typ, msg string) error

Raise creates an error for a HostFunc to raise as a Python exception. Unknown exception classes and ordinary Go errors become HostError, a subclass of RuntimeError.

func ReadOnly

func ReadOnly(filesystem fs.FS) fs.FS

ReadOnly hides filesystem's write capabilities from WithFS. Nil stays nil.

Types

type BorrowedInstance

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

BorrowedInstance provides interpreter operations during Program.Run. It must not be copied or used after the callback returns. All goroutines using it must finish before the callback returns.

func (*BorrowedInstance) Call

func (o *BorrowedInstance) Call(ctx context.Context, name string, args ...any) (Value, error)

Call invokes a Python global function; see Instance.Call.

func (*BorrowedInstance) Eval

func (o *BorrowedInstance) Eval(ctx context.Context, expr string) (Value, error)

Eval evaluates a Python expression; see Instance.Eval.

func (*BorrowedInstance) Exec

func (o *BorrowedInstance) Exec(ctx context.Context, src string) error

Exec runs Python statements; see Instance.Exec.

func (*BorrowedInstance) Get

func (o *BorrowedInstance) Get(ctx context.Context, name string) (Value, error)

Get reads a Python global; see Instance.Get.

func (*BorrowedInstance) Set

func (o *BorrowedInstance) Set(ctx context.Context, name string, v any) error

Set binds a Python global; see Instance.Set.

type Func

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

Func is a Python callable bound by Instance.AsCallable. It can be invoked in Go or passed back to the same interpreter.

func (Func) Call

func (f Func) Call(ctx context.Context, args ...any) (Value, error)

Call invokes the bound function with Instance.Call argument conversions. It must not be called from a HostFunc running on the same Instance.

func (Func) Value

func (f Func) Value() Value

Value returns the function's handle as a Value.

type Globals

type Globals = map[string]any

Globals maps Python global names to initial Go values for WithGlobals.

type HostFunc

type HostFunc func(ctx context.Context, args []Value) (Value, error)

HostFunc is a Go function callable from Python through WithHostFunc or Instance.DefineFunction. Errors and panics become Python exceptions; use Raise to choose the exception class. A callback must not synchronously call or close its own Instance.

type Instance

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

Instance is a MicroPython interpreter whose state persists between calls. It is safe for concurrent use, but executes one operation at a time. Use Program or Instance.Clone for parallel execution, and call Close when done.

func NewInstance

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

NewInstance creates an interpreter and applies its initialization options. The caller must close it when done.

Example (ReadOnlyFiles)
package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"testing/fstest"

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

func main() {
	ctx := context.Background()
	files := fstest.MapFS{
		"message.txt": &fstest.MapFile{Data: []byte("hello")},
	}

	var output bytes.Buffer

	in, err := micropython.NewInstance(ctx,
		micropython.WithFS(micropython.ReadOnly(files)),
		micropython.WithEnv("STAGE", "sandbox"),
		micropython.WithStdout(&output),
		micropython.WithHeapSize(256*1024),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()

	if err := in.Exec(ctx, `
import os
with open('message.txt') as f:
    print(os.getenv('STAGE'), f.read())
`); err != nil {
		log.Fatal(err)
	}

	fmt.Print(output.String())
}
Output:
sandbox hello
Example (Sandbox)
package main

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

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

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

	deadline, cancel := context.WithTimeout(ctx, time.Second)
	defer cancel()

	in, err := micropython.NewInstance(deadline,
		micropython.WithEnv("APP_MODE", "agent"),
		micropython.WithHeapSize(256*1024),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()

	result, err := in.Eval(deadline, "sum(range(101))")
	if err != nil {
		log.Fatal(err)
	}

	n, err := result.AsInt()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(n)
}
Output:
5050
Example (Startup)
package main

import (
	"context"
	"fmt"
	"log"

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

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

	in, err := micropython.NewInstance(ctx,
		micropython.WithGlobals(micropython.Globals{"LOCATION": "New York"}),
		micropython.WithSource(`greeting = "Hello from " + LOCATION`),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()

	got, err := in.Get(ctx, "greeting")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(got.Export())
}
Output:
Hello from New York

func (*Instance) AsCallable

func (i *Instance) AsCallable(v Value) (Func, error)

AsCallable binds a callable Value to this Instance for invocation through Func.Call. The value must belong to this interpreter and remain live when called.

func (*Instance) AsIterator

func (i *Instance) AsIterator(v Value) (Iterator, error)

AsIterator binds a guest iterator value to this Instance for lazy iteration.

func (*Instance) Call

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

Call invokes a Python global function. Arguments may be Go values or Value builders; the result is a Value. Python state changes persist after the call.

func (*Instance) Cancel

func (i *Instance) Cancel() error

Cancel requests a KeyboardInterrupt in the current Python execution. It is safe from any goroutine and does not affect the next operation. Interruption is best effort: long C operations and blocking host I/O may delay it.

func (*Instance) Clone

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

Clone copies the current Python state into a new, caller-owned Instance. It locks the source while copying. Go callbacks, output writers, and filesystem backends are shared; guest handles cannot be transferred between instances. Close Python sockets, files, and directory iterators before cloning.

func (*Instance) Close

func (i *Instance) Close() error

Close interrupts active execution and closes the interpreter. Later execution attempts return ErrClosed.

func (*Instance) DefineFunction

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

DefineFunction binds fn to a Python global, replacing any existing binding. The binding persists across calls and is inherited by clones. See HostFunc for callback behavior.

Example

Raise picks the exception class the guest catches.

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()

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

	err = in.DefineFunction(ctx, "usd", func(_ context.Context, args []micropython.Value) (micropython.Value, error) {
		if len(args) != 1 {
			return micropython.Value{}, micropython.Raise("TypeError", "usd expects one currency code")
		}

		code, err := args[0].AsString()
		if err != nil {
			return micropython.Value{}, err
		}

		rate, ok := rates[code]
		if !ok {
			return micropython.Value{}, micropython.Raise("KeyError", code)
		}

		return micropython.Float(rate), nil
	})
	if err != nil {
		log.Fatal(err)
	}

	got, err := in.Eval(ctx, `usd("EUR")`)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(got)

	var exc *micropython.PythonError
	if _, err := in.Eval(ctx, `usd("JPY")`); errors.As(err, &exc) {
		fmt.Println(exc.Type(), "/", exc.Message())
	}

}
Output:
1.09
KeyError / JPY

func (*Instance) Err

func (i *Instance) Err() error

Err returns the interpreter's fatal error or ErrClosed, or nil if healthy.

func (*Instance) Eval

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

Eval evaluates a Python expression and returns its Value. Use Instance.Exec for statements and assignments.

func (*Instance) Exec

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

Exec runs Python statements, preserving their state changes. Output goes to WithStdout, or is discarded by default.

func (*Instance) Get

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

Get reads a Python global without evaluating an expression. It returns a Python NameError if the name is unbound.

func (*Instance) Release

func (i *Instance) Release(ctx context.Context, vals ...Value) error

Release unpins guest objects, including handles nested in containers. It invalidates all handles to each object, even separately acquired ones. Non-handles, already-released handles, and foreign handles are ignored.

Release does not run GC or remove Python-owned references. It is optional: Go cleanup also queues releases, applied by subsequent interpreter operations.

func (*Instance) Resolve

func (i *Instance) Resolve(ctx context.Context, v Value) (Value, error)

Resolve reads an object handle using the same conversion rules as Eval. Containers are copied; cyclic or deeply nested parts remain handles. Other objects return another handle to the same object.

func (*Instance) Set

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

Set binds v to a Python global. It accepts Go values and Value builders, using the same conversion rules as Instance.Call.

type Item

type Item struct {
	Key Value
	Val Value
}

Item is one entry of a Dict.

type Iterator

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

Iterator is a guest iterator bound to an Instance by Instance.AsIterator.

func (Iterator) Iter

func (i Iterator) Iter(ctx context.Context) iter.Seq2[Value, error]

Iter yields values until exhaustion or the first error. Stopping early leaves the iterator positioned for a later call to Iter.

type MkdirFS

type MkdirFS = vfs.MkdirFS

MkdirFS optionally enables creation of a single directory for WithFS.

type Object

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

Object is an opaque guest handle with cached type and capability information. Passing it back to its interpreter refers to the original Python object.

func (Object) IsCallable

func (o Object) IsCallable() bool

IsCallable reports whether the guest object can be called.

func (Object) IsIterable

func (o Object) IsIterable() bool

IsIterable reports whether the guest object is an iterator.

func (Object) Type

func (o Object) Type() string

Type is the handle's Python class, or "object" when no name crossed with it.

type OpenFileFS

type OpenFileFS = vfs.OpenFileFS

OpenFileFS optionally enables writable open calls for WithFS.

type Option

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

Option configures an Instance or a Program. Access is opt-in: use WithFS, WithEnv, WithTCPAccess, WithUDPAccess, WithDNSResolver, WithStdout, and WithHostFunc to expose host resources. WithSource, WithGlobals, and WithHeapSize configure execution.

Example (From_host)
package main

import (
	"context"
	"fmt"
	"log"
	"strings"

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

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

	src := `def is_louder(greeting): return greeting.isupper()`

	instance, err := micropython.NewInstance(ctx,
		micropython.WithSource(src),
		micropython.WithGlobals(micropython.Globals{
			"LOCATION":      micropython.Str("New York"),
			"SERVICE_COUNT": micropython.Int(42),
		}),
		micropython.WithHostFunc("louder",
			func(ctx context.Context, args []micropython.Value) (micropython.Value, error) {
				greet, err := args[0].AsString()
				if err != nil {
					return micropython.Value{}, err
				}

				return micropython.Str(strings.ToUpper(greet)), nil
			}),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer instance.Close()

	result, err := instance.Eval(ctx, `(
    f"{louder('hello from ' + LOCATION)} "
    f"({SERVICE_COUNT} services), "
    f"loud: {is_louder(louder('hi'))}"
)`)
	if err != nil {
		log.Fatal(err)
	}

	out, err := result.AsString()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(out)

}
Output:
HELLO FROM NEW YORK (42 services), loud: True
Example (Sandbox)
package main

import (
	"context"
	"log"
	"net"
	"os"

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

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

	root, _ := os.OpenRoot("./testdata/filesystem")

	instance, err := micropython.NewInstance(ctx,
		micropython.WithStdout(os.Stdout),                      // redirect all print() to host's STDOUT
		micropython.WithEnv("ENV", "dev"),                      // the only variable os.getenv can see
		micropython.WithFS(root.FS()),                          // provide read-only access to in-memory FS
		micropython.WithTCPAccess("127.0.0.1", 8000),           // this address and port, outbound
		micropython.WithTCPAccess(micropython.AnyAddress, 443), // any address, but only port 443
		micropython.WithDNSResolver(net.DefaultResolver),       // needed for DNS resolution
	)
	if err != nil {
		log.Fatal(err)
	}
	defer instance.Close()

	script := `
import os

print("env:", os.getenv("ENV"))
print("mounted:", os.listdir("/"))

with open("hello.txt") as f:
    print("file:", f.read().strip())
`

	if err := instance.Exec(ctx, script); err != nil {
		log.Fatal(err)
	}

}
Output:
env: dev
mounted: ['hello.txt']
file: hello from the sandbox

func WithDNSResolver

func WithDNSResolver(r *net.Resolver) Option

WithDNSResolver supplies and enables name resolution for socket.getaddrinfo. Resolution is denied by default. Nil fails at construction; use net.DefaultResolver explicitly to use the host resolver. Last call wins.

WithTCPAccess(AnyAddress, 443)
WithDNSResolver(net.DefaultResolver)

Lookups are independent of TCP/UDP grants and can contact nameservers outside those grants. Resolved destinations still need connection permission. Programs and clones share r; do not modify it while they are in use.

func WithEnv

func WithEnv(name, value string) Option

WithEnv sets a variable for Python's os.getenv. The environment starts empty. Calls are additive; the last value for a name wins.

The Go process environment is never inherited. Python's os.putenv and os.unsetenv affect only this instance. Clones copy the current variables; Program.Run restores the variables captured after initialization.

Names must be nonempty, at most 1024 bytes, and contain neither "=" nor NUL. Values must be at most 65536 bytes and contain no NUL. Invalid pairs fail construction. Guest writes use the same limits and cannot add a new name when 256 variables already exist. This host memory is outside WithHeapSize.

func WithFS

func WithFS(filesystem fs.FS) Option

WithFS exposes filesystem at Python's root for files and imports. Access is denied by default or with nil. Repeated use replaces the filesystem. Open files are closed on rewind or instance close, but the caller owns the filesystem. Programs and clones share it; filesystem changes are not rewound.

The fs.FS interface provides read access. Backends can grant writes with OpenFileFS, MkdirFS, UnlinkFS, RmdirFS, and RenameFS. Use ReadOnly to hide those write capabilities.

The backend must confine symlinks and support concurrent use by independent instances. os.DirFS alone does not prevent symlinks escaping its directory.

func WithGlobals

func WithGlobals(g Globals) Option

WithGlobals sets initial Python globals using Instance.Set conversion rules. No caller-supplied globals are set by default. Repeated use replaces the map.

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.NewProgram(ctx, micropython.WithSource(`
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()

	var got micropython.Value

	if err := p.Run(ctx, func(in *micropython.BorrowedInstance) error {
		v, err := in.Call(ctx, "describe")
		got = v

		return err
	}); err != nil {
		log.Fatal(err)
	}

	fmt.Println(got)

}
Output:
service allows 3 retries

func WithHeapSize

func WithHeapSize(bytes int) Option

WithHeapSize sets the Python heap size in bytes. The default and zero use 128 KiB. This is not a limit on total interpreter or host memory. Invalid sizes fail at construction. Exhausting the heap raises MemoryError.

func WithHostFunc

func WithHostFunc(name string, fn HostFunc) Option

WithHostFunc binds fn to a Python global before source execution. No Go callbacks are registered by default. The callback controls what host resources Python can access through it. Repeated names use the last binding. Programs and clones share the Go closure, which must support concurrent calls; its state is not rewound. See HostFunc.

func WithSource

func WithSource(src string) Option

WithSource runs src after binding globals and host functions at initialization. By default, no initialization script runs. Repeated use replaces the script. For a Program, the resulting Python state is the starting point for each run.

func WithStdout

func WithStdout(w io.Writer) Option

WithStdout sends Python's print output to w. By default, or with nil, it is discarded. Repeated use replaces the writer. The caller owns w. Programs and clones share it without synchronization, so it must support concurrent writes when used concurrently.

func WithTCPAccess

func WithTCPAccess(address string, port int) Option

WithTCPAccess permits outbound TCP to an IPv4 address, CIDR block, or AnyAddress, on port 1-65535. Hostnames and IPv6 are not supported. Connections are denied by default. Grants are additive and order-independent.

WithTCPAccess("192.0.2.10", 443)
WithTCPAccess("10.0.0.0/8", 5432)
WithTCPAccess(AnyAddress, 443)

DNS requires WithDNSResolver. Invalid grants fail at construction. This opens no sockets; port 443 permits TCP traffic, not just HTTPS.

func WithUDPAccess

func WithUDPAccess(address string, port int) Option

WithUDPAccess permits outbound UDP on the same terms as WithTCPAccess. Connections are denied by default. Repeated use adds grants. Python must connect the socket first. sendto may only name the connected peer; recvfrom returns that peer. Unconnected datagrams are unsupported.

type Program

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

Program pools interpreters initialized from a common Python state. Runs are safe to execute concurrently and do not retain each other's Python state changes. Call Program.Close when done.

func NewProgram

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

NewProgram initializes Python using opts and saves the state for later runs. Initialization must close files, directory iterators, and sockets before the state is saved. The caller must close the Program when done.

func (*Program) Close

func (p *Program) Close() error

Close closes idle interpreters and rejects new work with ErrClosed. Active runs finish normally; their interpreters are closed on return.

func (*Program) Instance

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

Instance creates a standalone interpreter from the Program's initialized state. It keeps state between calls. The caller must close it separately.

func (*Program) Run

func (p *Program) Run(ctx context.Context, fn func(in *BorrowedInstance) error) (err error)

Run calls fn with a borrowed interpreter, then resets or closes it. It returns callback and cleanup errors. External effects are not undone.

Canceling ctx requests interruption of the current Python operation; see Instance.Cancel. Pass ctx to borrowed methods so later operations also observe cancellation. The callback must handle cancellation of its own Go work. The BorrowedInstance is valid only until fn returns.

Example
package main

import (
	"context"
	"fmt"
	"log"

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

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

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

	var out map[string]any

	err = p.Run(ctx, func(in *micropython.BorrowedInstance) error {
		got, err := in.Call(ctx, "score", map[string]any{"id": "r-1", "a": 4, "b": 5})
		if err != nil {
			return err
		}

		out = got.Export().(map[string]any)

		return nil
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(out["id"], out["total"])
}
Output:
r-1 13
Example (Basic)
package main

import (
	"context"
	"fmt"
	"log"

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

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

	p, err := micropython.NewProgram(ctx,
		micropython.WithSource("def double(x): return x * 2"),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer p.Close()

	err = p.Run(ctx, func(in *micropython.BorrowedInstance) error {
		got, err := in.Call(ctx, "double", 10)
		if err != nil {
			return err
		}

		fmt.Println(got.Export())

		return nil
	})
	if err != nil {
		log.Fatal(err)
	}
}
Output:
20

type ProgramOption

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

ProgramOption configures a Program. Every Option is also a ProgramOption. WithMaxIdle controls the pool; the other options also apply to instances.

func WithMaxIdle

func WithMaxIdle(n int) ProgramOption

WithMaxIdle limits idle interpreters retained by a Program, not active runs. The default and zero use runtime.NumCPU; negative values fail at construction.

type PythonError

type PythonError = value.Exception

PythonError describes a guest exception. Use errors.As with *PythonError to inspect its Type, Message, and Raw traceback.

Example

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

package main

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

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

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

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

	err = p.Run(ctx, func(in *micropython.BorrowedInstance) error {
		_, err := in.Call(ctx, "lookup", "missing")
		return err
	})
	if exc, ok := errors.AsType[*micropython.PythonError](err); ok {
		fmt.Println(exc.Type(), "/", exc.Message())
	}

	var got micropython.Value

	if err := p.Run(ctx, func(in *micropython.BorrowedInstance) error {
		v, err := in.Call(ctx, "lookup", "a")
		got = v

		return err
	}); err != nil {
		log.Fatal(err)
	}

	fmt.Println(got)

}
Output:
KeyError / missing
1

type RenameFS

type RenameFS = vfs.RenameFS

RenameFS optionally enables renaming within the mounted filesystem for WithFS.

type RmdirFS

type RmdirFS = vfs.RmdirFS

RmdirFS optionally enables removal of an empty directory for WithFS. It must atomically reject files and symlinks, and must not remove recursively.

type UnlinkFS

type UnlinkFS = vfs.UnlinkFS

UnlinkFS optionally enables removal of a file or symlink for WithFS. It must not follow the final symlink and must atomically reject directories. Go's os.Remove does not satisfy this contract.

type Value

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

Value represents copied Python data or a handle owned by an interpreter. Use Export for Go data or the As methods for checked, type-specific access. The zero Value is invalid; use None for Python None. Data copied from a Program.Run survives the run; handles returned by that run, including those nested in collections, do not.

Example

A Go slice could be a list, a tuple or a set, so the builders say which.

package main

import (
	"context"
	"fmt"
	"log"

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

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

	p, err := micropython.NewProgram(ctx, micropython.WithSource("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},
		micropython.Tuple(micropython.Int(1), micropython.Int(2)),
	} {
		var got micropython.Value

		if err := p.Run(ctx, func(in *micropython.BorrowedInstance) error {
			v, err := in.Call(ctx, "kind", v)
			got = v

			return err
		}); err != nil {
			log.Fatal(err)
		}

		fmt.Println(got)
	}

}
Output:
list
tuple

func BigInt

func BigInt(n *big.Int) Value

BigInt copies n into a Python int Value; nil becomes zero.

func Bool

func Bool(b bool) Value

Bool converts a Go bool to a Python bool.

func Bytes

func Bytes(b []byte) Value

Bytes copies b into a Python bytes Value.

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 an exception Value. Returning it from a HostFunc raises it; use Raise to return an exception through the error result instead.

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 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 Go data to a Value. Nil becomes None; []byte becomes bytes. Scalars and collections use native conversions; other types use JSON. Unsigned integers must fit in int64; use BigInt for larger integers. Use builders such as Tuple when the Python type matters. Conversion failures produce an invalid Value, reported when passed to Python.

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 Tuple

func Tuple(items ...Value) Value

Tuple creates a Python tuple from the given values.

func (Value) AsBigInt

func (v Value) AsBigInt() (*big.Int, error)

AsBigInt returns a Python integer, or an error for other types. The result may share storage with v; copy it before modifying it.

func (Value) AsBool

func (v Value) AsBool() (bool, error)

AsBool returns a Python bool as a Go bool, or an error for other types.

func (Value) AsBytes

func (v Value) AsBytes() ([]byte, error)

AsBytes copies Python bytes into a Go slice, or returns an error for other types.

func (Value) AsDict

func (v Value) AsDict() ([]Item, error)

AsDict returns a new slice of dictionary entries without converting keys. It preserves received order and returns an error for other types.

func (Value) AsFloat

func (v Value) AsFloat() (float64, error)

AsFloat returns a Python float as a float64, or an error for other types. It does not convert integers to floats.

func (Value) AsFrozenSet

func (v Value) AsFrozenSet() ([]Value, error)

AsFrozenSet returns a new slice of a Python frozenset's elements. It returns an error for other types. Element order is unspecified.

func (Value) AsInt

func (v Value) AsInt() (int64, error)

AsInt returns an int64, reporting type mismatch or overflow. Use Value.AsBigInt for larger integers.

Example
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.Set(ctx, "numbers", []int{1, 2, 3}); err != nil {
		log.Fatal(err)
	}

	got, err := in.Eval(ctx, "sum(numbers)")
	if err != nil {
		log.Fatal(err)
	}

	n, err := got.AsInt()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(n)
}
Output:
6

func (Value) AsList

func (v Value) AsList() ([]Value, error)

AsList returns a new slice of a Python list's elements, or an error for other types.

func (Value) AsObject

func (v Value) AsObject() (Object, error)

AsObject returns the opaque handle, or an error for copied data.

func (Value) AsSet

func (v Value) AsSet() ([]Value, error)

AsSet returns a new slice of a Python set's elements, or an error for other types. Element order is unspecified.

func (Value) AsString

func (v Value) AsString() (string, error)

AsString returns a Python str as a Go string, or an error for other types.

func (Value) AsTuple

func (v Value) AsTuple() ([]Value, error)

AsTuple returns a new slice of a Python tuple's elements, or an error for other types.

func (Value) Export

func (v Value) Export() any

Export converts data to Go scalars, slices, and maps. Handles remain Values, including inside containers. None and the zero Value export as nil. Container kinds are flattened and non-comparable dictionary keys stringified; use the As methods when those distinctions matter.

func (Value) IsCallable

func (v Value) IsCallable() bool

IsCallable reports whether v holds a Python callable.

func (Value) IsIterator

func (v Value) IsIterator() bool

IsIterator reports whether v holds a guest iterator, not a copied collection.

func (Value) IsNone

func (v Value) IsNone() bool

IsNone reports whether the value is Python's None.

func (Value) String

func (v Value) String() string

String formats v for display; it is not Python repr or serialization.

func (Value) Type

func (v Value) Type() string

Type returns the Python type name, or "invalid" for a zero Value.

Directories

Path Synopsis
examples
basic command
Run a script in a stateful interpreter, then read values back out.
Run a script in a stateful interpreter, then read values back out.
callable command
Hold a Python function in Go, call it, and pass it back to Python.
Hold a Python function in Go, call it, and pass it back to Python.
channel command
Back a host function with a Go channel.
Back a host function with a Go channel.
errors command
Handle a guest that raises, and stop one that will not finish.
Handle a guest that raises, and stop one that will not finish.
filesystem command
Give the guest files to read.
Give the guest files to read.
hostfunc command
Let Python call back into Go, and control which exception the guest sees when the Go side fails.
Let Python call back into Go, and control which exception the guest sees when the Go side fails.
iterator command
Pull values from a Python generator one at a time, without materialising the whole sequence.
Pull values from a Python generator one at a time, without materialising the whole sequence.
memory command
Size the Python heap to what a script allocates.
Size the Python heap to what a script allocates.
network command
Let the guest make outbound connections.
Let the guest make outbound connections.
program command
Compile a script once and serve concurrent calls from a pool of interpreters, each rewound to the compiled state before it is reused.
Compile a script once and serve concurrent calls from a pool of interpreters, each rewound to the compiled state before it is reused.
stdout command
Read what the guest prints, either collected afterwards or streamed as it runs.
Read what the guest prints, either collected afterwards or streamed as it runs.
values command
Choose the Python type an argument arrives as, and read a result back without guessing what it is.
Choose the Python type an argument arrives as, and read a result back without guessing what it is.
internal
api
host/codec
Package codec translates between the host's value model and the records MicroPython reads and writes.
Package codec translates between the host's value model and the records MicroPython reads and writes.
host/env
Package env implements the guest's os environment imports.
Package env implements the guest's os environment imports.
host/vfs
Package vfs implements the guest's fs imports over an io/fs filesystem.
Package vfs implements the guest's fs imports over an io/fs filesystem.

Jump to

Keyboard shortcuts

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