micropython

package module
v0.0.0-...-6ac5839 Latest Latest
Warning

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

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

README

Embedded MicroPython for Go

micropython-go

Go Reference

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.

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.

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

	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

Examples

Run the examples with go run. Their output is checked by go test ./examples/....

Example Shows
basic Exec, Call, Eval and Get against one Instance
program Initializing once and running callbacks in parallel
values Choosing which Python type an argument arrives as
hostfunc Calling back into Go, and raising a chosen exception
channel Backing a host function with a Go channel
callable Holding a Python function in Go and passing it back
iterator Pulling values out of a generator one at a time
stdout Collecting print() output, or streaming it
errors Exceptions as Go errors, deadlines, and Cancel
filesystem Mounting an fs.FS, read-only and writable
network Granting outbound TCP to one address and port
memory Sizing the Python heap, and MemoryError
go run ./examples/basic

Instances

An Instance keeps Python state between calls. Use Exec for statements, Eval for expressions, Call for global functions, and Get or Set for global variables. Close the instance when you are done.

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.

Programs

A Program initializes Python once and saves that state. Each Run borrows an interpreter from a pool and starts from the saved state, without rerunning the initialization script. Operations within one callback share Python state; separate runs do not retain each other's changes.

Use WithSource for initialization code and WithGlobals for its initial values.

The snippets below assume an existing ctx. See the examples above for complete programs and imports.

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"]) // r-1 13

Do not copy or retain the borrowed instance. Any goroutines using it must finish before the callback returns. Copied data remains usable afterwards, but Python object handles do not. Export() can still contain handles inside collections; it does not detach those objects from the interpreter.

Use Program.Instance for a standalone interpreter that keeps state between calls. You must close it separately.

WithMaxIdle limits idle interpreters, not concurrent runs. Limit concurrency in your application to control peak memory use.

Files, directory iterators, and sockets must be closed before initialization finishes. Resources opened during a run are closed during cleanup, and cleanup errors are returned by Run. File changes, output, and other external effects are not undone.

Sandboxing

An interpreter starts with no filesystem access, an empty environment, no networking or DNS, and discarded Python output. Built-in modules are available without a filesystem. Options control host access, not hard CPU or total-memory limits. Filesystem backends and Go callbacks must enforce their own access restrictions.

For example, run untrusted code with one environment variable, a 256 KiB Python heap, and a deadline of 1s:

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) // 5050

Filesystem and network access remain denied. The Python heap defaults to 128 KiB when WithHeapSize is omitted. The deadline provides best-effort cancellation. The same access options work with NewProgram.

Access Default Option
Files No access WithFS
Environment Empty; no process inheritance WithEnv
Network connections Denied WithTCPAccess, WithUDPAccess
DNS Denied WithDNSResolver
Output Discarded WithStdout
Go callbacks None registered WithHostFunc
Files

WithFS exposes an fs.FS, such as embed.FS, at Python's root for open() and file-based imports. Backends can grant writes through additional interfaces:

Interface Grants
OpenFileFS open() in a writing mode
MkdirFS os.mkdir
UnlinkFS os.remove
RmdirFS os.rmdir
RenameFS os.rename

ReadOnly hides these write capabilities but does not add path confinement. Backends must confine symlinks themselves; os.DirFS alone does not do this. See the filesystem example for embedded files and its writable adapter for wrapping os.Root.

Environment

WithEnv("STAGE", "prod") sets a variable for os.getenv. Missing variables return None or the supplied default. Python can change its own environment with os.putenv and os.unsetenv; Programs restore the initialized environment after each run.

Network

WithTCPAccess and WithUDPAccess permit outbound connections to an IPv4 address, CIDR block, or AnyAddress, on one port. Grants are additive. Hostnames and IPv6 are not supported in access rules. DNS is enabled separately:

in, err := micropython.NewInstance(ctx,
	micropython.WithTCPAccess(micropython.AnyAddress, 443), // AnyAddress == "*"
	micropython.WithDNSResolver(net.DefaultResolver),
)
if err != nil {
	log.Fatal(err)
}
defer in.Close()

AnyAddress includes private and loopback addresses. Port 443 permits TCP traffic, not just HTTPS, and DNS lookups are independent of connection grants. Sockets are outbound only; UDP requires a connected peer. See the network example for a narrower grant.

Output

WithStdout sends print() output to an io.Writer owned by the caller. Programs and clones share it, so concurrent runs need a writer that supports concurrent writes; bytes.Buffer does not. See the stdout example for buffering output or streaming it through a pipe.

Values

Pass ordinary Go values to Call and Set; Call, Eval, and Get return micropython.Values. Use WithGlobals to supply configuration without inserting it into Python source.

Go to Python

Arguments are converted recursively:

Go value Python value
nil None
bool bool
Signed integers int
Unsigned integers int; values above math.MaxInt64 fail
*big.Int int; nil becomes zero
float32, float64 float
string str
[]byte bytes
Other slices and arrays list
map[string]struct{} set of keys
Other maps dict
Other pointers and interfaces The contained value, or None when nil
Structs and other types JSON conversion; unsupported values fail

Use builders such as Tuple and Set when you need a specific Python type. Of converts a Go value to a Value using the same rules. Use BigInt for integers outside the int64 range.

Python to Go

Export() returns ordinary Go data where possible. The As methods check the Python type and return an error on mismatch.

Python value Export() result Checked access
None nil IsNone
bool bool AsBool
int int64 or *big.Int AsInt, AsBigInt
float float64 AsFloat
str string AsString
bytes []byte AsBytes
list, tuple []any AsList, AsTuple
set, frozenset []any AsSet, AsFrozenSet
dict with only string keys map[string]any AsDict
Other dictionaries map[any]any AsDict
Objects returned as handles Value AsObject

AsInt reports overflow; AsFloat requires a Python float, not an integer. Export() stringifies dictionary keys that Go maps cannot hold, such as tuples. Use AsDict to preserve the original keys. Exported collections can still contain interpreter-backed handles.

Functions, iterators, and other Python objects may be returned as handles. Use Instance.AsCallable, Instance.AsIterator, or Instance.Resolve to work with them. Handles belong to their original interpreter. Instance.Release optionally releases them early; copied data needs no release.

See the values, callable, and iterator examples for conversions and object lifetimes.

Calling Go from Python

DefineFunction binds a Go function to a Python global. Use WithHostFunc to register it before initialization, including when creating a Program.

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

Use Raise to return a Python exception. Ordinary Go errors and recovered panics become HostError, a subclass of RuntimeError.

Programs and clones share Go callbacks and their captured state. Callbacks must support concurrent calls, and their Go state is not reset between runs. A callback must not synchronously call or close the interpreter that invoked it.

Errors and cancellation

A Python exception returns a Go error and normally leaves the interpreter usable. Use errors.As to inspect it:

var exc *micropython.PythonError
if _, err := in.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
}

Pass a context with a deadline to interpreter operations to limit execution. Cancellation returns the operation context's error. Instance.Cancel requests a KeyboardInterrupt in the current Python operation.

Canceling a Run context also requests interruption of the current operation. Pass that context to the borrowed methods as well so later calls observe its cancellation. The Go callback must handle cancellation of its own work.

Cancellation is best effort. Long C operations, blocking filesystem calls, and stdout writes can delay it. Host callbacks must cooperate with their context.

Limitations

This build includes a subset of MicroPython's features and standard library. It is not a drop-in replacement for CPython.

  • No async or await. Both are a SyntaxError in this build.
  • Recursion is bounded. Exceeding the limit raises RuntimeError.
  • The heap limit is not a total memory limit. WithHeapSize controls the Python heap, not all interpreter and host allocations.

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

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.

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

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

	var exc *micropython.PythonError
	err = p.Run(ctx, func(in *micropython.BorrowedInstance) error {
		_, err := in.Call(ctx, "lookup", "missing")
		return err
	})
	if errors.As(err, &exc) {
		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.

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