Client Wrappers
When a plugin registers a function or class, the host needs Scriptling code to call it. By default the host auto-generates a proxy. You can replace this with custom Scriptling source using Wrapper().
Auto-Generated Proxies
Functions and classes registered with RegisterFunc or RegisterClass get an auto-generated proxy on the host. The proxy forwards calls to the plugin over JSON-RPC:
fb := object.NewFunctionBuilder()
fb.Function(func(name string) string {
return "Hello, " + name
})
server.RegisterFunc("greet", fb)The host generates something equivalent to:
import scriptling.plugin
def greet(*args, **kwargs):
return scriptling.plugin.call_function("plugin.hello", "greet", *args, **kwargs)For classes, the auto-generated proxy handles __init__, method calls, and cleanup:
class Counter:
def __init__(self, *args, **kwargs):
self._plugin_remote = scriptling.plugin._new_object("plugin.counter", "Counter", *args, **kwargs)
def inc(self, *args, **kwargs):
return scriptling.plugin.call_method(self._plugin_remote, "inc", *args, **kwargs)
def __del__(self):
scriptling.plugin.release(self._plugin_remote)The __del__ method sends object.destroy to the plugin, which calls the plugin-side __del__ to free resources (file handles, connections, etc.). The evaluator also installs a GC finalizer on the proxy instance, so cleanup happens automatically when the object becomes unreachable — even if __del__ is never called explicitly.
Custom Wrappers
Wrapper() replaces the auto-generated proxy with custom Scriptling source. The name must match the registered function or class name. The underlying RPC function or class is still callable via scriptling.plugin.call_function, scriptling.plugin._new_object, and scriptling.plugin.call_method.
Wrapping a Function
fb := object.NewFunctionBuilder()
fb.Function(func(name string) string {
return "wrapped:" + name
})
server.RegisterFunc("decorate", fb)
server.Wrapper("decorate", `
import scriptling.plugin
def decorate(name):
return scriptling.plugin.call_function("plugin.mixed", "decorate", name) + "!"
`)Wrapping a Class
cb := object.NewClassBuilder("Config").
Constructor(func(name string) *configData {
return &configData{name: name}
}).
Method("get", func(self *configData, key string) string {
return self.values[key]
})
server.RegisterClass(cb)
server.Wrapper("Config", `
import scriptling.plugin
class Config:
def __init__(self, name):
self._plugin_remote = scriptling.plugin._new_object("plugin.mixed", "Config", name)
def get(self, key):
return scriptling.plugin.call_method(self._plugin_remote, "get", key)
def __del__(self):
scriptling.plugin.release(self._plugin_remote)
`)The host uses the supplied source instead of auto-generating a proxy. The underlying RPC function or class remains callable.
Custom class wrappers must include __del__ that calls scriptling.plugin.release(self._plugin_remote) — without it, the plugin-side object is never destroyed and resources leak.
When to Use Custom Wrappers
| Need | Example |
|---|---|
| Default parameter values | def search(query, limit=10) |
| Argument transformation | Convert a dict to positional args |
| Convenience methods | Combine multiple RPC calls into one function |
| Rename or hide internals | Expose a cleaner API than the raw RPC |
Mixed Example
A single plugin can use both auto-generated proxies and custom wrappers:
package main
import (
"github.com/paularlott/scriptling/object"
"github.com/paularlott/scriptling/plugin"
)
type configData struct {
name string
values map[string]string
}
func main() {
server := plugin.NewServer("mixed", "1.0.0", "Mixed demo")
// Auto-generated proxy — no Wrapper() call
fb := object.NewFunctionBuilder()
fb.Function(func(name string) string {
return "generated:" + name
})
server.RegisterFunc("generated", fb)
// Custom wrapper — Wrapper() replaces the auto-proxy
fbWrap := object.NewFunctionBuilder()
fbWrap.Function(func(name string) string {
return "wrapped:" + name
})
server.RegisterFunc("decorate", fbWrap)
server.Wrapper("decorate", `
import scriptling.plugin
def decorate(name):
return scriptling.plugin.call_function("plugin.mixed", "decorate", name) + "!"
`)
// Custom class wrapper
cb := object.NewClassBuilder("Config").
Constructor(func(name string) *configData {
return &configData{name: name, values: map[string]string{}}
}).
Method("get", func(self *configData, key string) string {
return self.values[key]
}).
Method("set", func(self *configData, key, val string) {
self.values[key] = val
})
server.RegisterClass(cb)
server.Wrapper("Config", `
import scriptling.plugin
class Config:
def __init__(self, name):
self._plugin_remote = scriptling.plugin._new_object("plugin.mixed", "Config", name)
def get(self, key):
return scriptling.plugin.call_method(self._plugin_remote, "get", key)
def set(self, key, val):
scriptling.plugin.call_method(self._plugin_remote, "set", key, val)
def __del__(self):
scriptling.plugin.release(self._plugin_remote)
`)
if err := server.Run(); err != nil {
panic(err)
}
}Host usage:
import plugin.mixed
print(plugin.mixed.generated("Ada")) # auto-generated proxy
print(plugin.mixed.decorate("Ada")) # custom wrapper
cfg = plugin.mixed.Config("Ada") # custom class wrapper
cfg.set("host", "localhost")
print(cfg.get("host"))