From 97ee211937987bb52c9312389b3b735679c77cf5 Mon Sep 17 00:00:00 2001 From: Drew O'Meara Date: Mon, 7 Feb 2022 13:26:59 -0600 Subject: [PATCH 01/14] added examples and py utils --- examples/embedding/README.md | 102 ++++++++++++ examples/embedding/lib/REPL-startup.py | 8 + examples/embedding/lib/mylib.py | 53 +++++++ examples/embedding/main.go | 51 ++++++ examples/embedding/mylib-demo.py | 15 ++ examples/embedding/mylib.module.go | 175 +++++++++++++++++++++ examples/multi-context/main.go | 138 +++++++++++++++++ py/util.go | 206 +++++++++++++++++++++++++ 8 files changed, 748 insertions(+) create mode 100644 examples/embedding/README.md create mode 100644 examples/embedding/lib/REPL-startup.py create mode 100644 examples/embedding/lib/mylib.py create mode 100644 examples/embedding/main.go create mode 100644 examples/embedding/mylib-demo.py create mode 100644 examples/embedding/mylib.module.go create mode 100644 examples/multi-context/main.go create mode 100644 py/util.go diff --git a/examples/embedding/README.md b/examples/embedding/README.md new file mode 100644 index 00000000..3eb17502 --- /dev/null +++ b/examples/embedding/README.md @@ -0,0 +1,102 @@ +## Embedding gpython + +This is an example demonstrating how to embed gpython into a Go application. + + +### Why embed gpython? + +Embedding a highly capable and familiar "interpreted" language allows your users +to easily augment app behavior, configuration, and customization -- all post-deployment. + +Have you ever discovered an exciting software project but lost interest when you had to also +learn an esoteric language schema? In an era of limited attention span, +most people are generally turned off if they have to learn a new language in addition to learning +to use your app. + +If you consider [why use Python](https://www.stxnext.com/what-is-python-used-for/), then perhaps also +consider that your users will be interested to hear that your software offers +even more value that it can be driven from a scripting language they already know. + +Python is widespread in finance, sciences, hobbyist programming and is often +endearingly regarded as most popular programming language for non-developers. +If your application can be driven by embedded Python, then chances are others will +feel excited and empowered that your project can be used out of the box +and feel like familiar territory. + +### But what about the lack of python modules? + +There are only be a small number of native modules available, but don't forget you have the entire +Go standard library and *any* Go package you can name at your fingertips to expose! +This plus multi-context capability gives gpython enormous potential on how it can +serve you. + +So basically, gpython is only off the table if you need to run python that makes heavy use of +modules that are only available in CPython. + +### Packing List + +| | | +|---------------------- | ------------------------------------------------------------------| +| `main.go` | if no args, runs in REPL mode, otherwise runs the given file | +| `lib/mylib.py` | models a library that your application would expose | +| `lib/REPL-startup.py` | invoked by `main.go` when starting REPL mode | +| `mylib-demo.py` | models a user-authored script that consumes `mylib` | +| `mylib.module.go` | Go implementation of `mylib_go` consumed by `mylib` | + + +### Invoking a Python Script + +```bash +$ cd examples/embedding/ +$ go build . +$ ./embedding mylib-demo.py +``` +``` +Welcome to a gpython embedded example, + where your wildest Go-based python dreams come true! + +========================================================== + Python 3.4 (github.com/go-python/gpython) + go1.17.6 on darwin amd64 +========================================================== + +Spring Break itinerary: + Stop 1: Miami, Florida | 7 nights + Stop 2: Mallorca, Spain | 3 nights + Stop 3: Ibiza, Spain | 14 nights + Stop 4: Monaco | 12 nights +### Made with Vacaton 1.0 by Fletch F. Fletcher + +I bet Monaco will be the best! +``` + +### REPL Mode + +```bash +$ ./embedding +``` +``` +======= Entering REPL mode, press Ctrl+D to exit ======= + +========================================================== + Python 3.4 (github.com/go-python/gpython) + go1.17.6 on darwin amd64 +========================================================== + +>>> v = Vacation("Spring Break", Stop("Florida", 3), Stop("Nice", 7)) +>>> print(str(v)) +Spring Break, 2 stop(s) +>>> v.PrintItinerary() +Spring Break itinerary: + Stop 1: Florida | 3 nights + Stop 2: Nice | 7 nights +### Made with Vacaton 1.0 by Fletch F. Fletcher +``` + +## Takeways + + - `main.go` demonstrates high-level convenience functions such as `py.RunFile()`. + - Embedding a Go `struct` into a Python object only requires that it implements `py.Object`, which is a single function: + `Type() *py.Type` + - See [py/run.go](https://github.com/go-python/gpython/tree/master/py/run.go) for more about interpreter instances and `py.Context` + - Helper functions available in [py/util.go](https://github.com/go-python/gpython/tree/master/py/util.go) and your contributions are welcome! diff --git a/examples/embedding/lib/REPL-startup.py b/examples/embedding/lib/REPL-startup.py new file mode 100644 index 00000000..6b27c415 --- /dev/null +++ b/examples/embedding/lib/REPL-startup.py @@ -0,0 +1,8 @@ + + +# This file is called from main.go when in REPL mode + +# This is here to demonstrate making life easier for your users in REPL mode +# by doing pre-setup here so they don't have to import every time they start. +from mylib import * + diff --git a/examples/embedding/lib/mylib.py b/examples/embedding/lib/mylib.py new file mode 100644 index 00000000..4c279c78 --- /dev/null +++ b/examples/embedding/lib/mylib.py @@ -0,0 +1,53 @@ +import mylib_go as _go + +PY_VERSION = _go.PY_VERSION +GO_VERSION = _go.GO_VERSION + + +print(''' +========================================================== + %s + %s +========================================================== +''' % (PY_VERSION, GO_VERSION)) + + +def Stop(location, num_nights = 2): + return _go.VacationStop_new(location, num_nights) + + +class Vacation: + + def __init__(self, tripName, *stops): + self._v, self._libVers = _go.Vacation_new() + self.tripName = tripName + self.AddStops(*stops) + + def __str__(self): + return "%s, %d stop(s)" % (self.tripName, self.NumStops()) + + def NumStops(self): + return self._v.num_stops() + + def GetStop(self, stop_num): + return self._v.get_stop(stop_num) + + def AddStops(self, *stops): + self._v.add_stops(stops) + + def PrintItinerary(self): + print(self.tripName, "itinerary:") + i = 1 + while 1: + + try: + stop = self.GetStop(i) + except IndexError: + break + + print(" Stop %d: %s" % (i, str(stop))) + i += 1 + + print("### Made with %s " % self._libVers) + + \ No newline at end of file diff --git a/examples/embedding/main.go b/examples/embedding/main.go new file mode 100644 index 00000000..faa5120e --- /dev/null +++ b/examples/embedding/main.go @@ -0,0 +1,51 @@ +// Copyright 2022 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + + // This initializes gpython for runtime execution and is essential. + // It defines forward-declared symbols and registers native built-in modules, such as sys and time. + _ "github.com/go-python/gpython/modules" + + // Commonly consumed gpython + "github.com/go-python/gpython/py" + "github.com/go-python/gpython/repl" + "github.com/go-python/gpython/repl/cli" +) + +func main() { + flag.Parse() + runWithFile(flag.Arg(0)) +} + +func runWithFile(pyFile string) error { + + // See type Context interface and related docs + ctx := py.NewContext(py.DefaultContextOpts()) + + var err error + if len(pyFile) == 0 { + replCtx := repl.New(ctx) + + fmt.Print("\n======= Entering REPL mode, press Ctrl+D to exit =======\n") + + _, err = py.RunFile(ctx, "lib/REPL-startup.py", py.CompileOpts{}, replCtx.Module) + if err == nil { + cli.RunREPL(replCtx) + } + + } else { + _, err = py.RunFile(ctx, pyFile, py.CompileOpts{}, nil) + } + + if err != nil { + py.TracebackDump(err) + } + + return err +} diff --git a/examples/embedding/mylib-demo.py b/examples/embedding/mylib-demo.py new file mode 100644 index 00000000..4a461023 --- /dev/null +++ b/examples/embedding/mylib-demo.py @@ -0,0 +1,15 @@ +print(''' +Welcome to a gpython embedded example, + where your wildest Go-based python dreams come true!''') + +# This is a model for a public/user-side script that you or users would maintain, +# offering an open canvas to drive app behavior, customization, or anything you can dream up. +# +# Modules you offer for consumption can also serve to document such things. +from mylib import * + +springBreak = Vacation("Spring Break", Stop("Miami, Florida", 7), Stop("Mallorca, Spain", 3)) +springBreak.AddStops(Stop("Ibiza, Spain", 14), Stop("Monaco", 12)) +springBreak.PrintItinerary() + +print("\nI bet %s will be the best!\n" % springBreak.GetStop(4).Get()[0]) diff --git a/examples/embedding/mylib.module.go b/examples/embedding/mylib.module.go new file mode 100644 index 00000000..b510277c --- /dev/null +++ b/examples/embedding/mylib.module.go @@ -0,0 +1,175 @@ +// Copyright 2022 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "runtime" + + "github.com/go-python/gpython/py" +) + +// These gpython py.Object type delcarations are the bridge between gpython and embedded Go types. +var ( + PyVacationStopType = py.NewType("Stop", "") + PyVacationType = py.NewType("Vacation", "") +) + +// init is where you register your embedded module and attach methods to your embedded class types. +func init() { + + // For each of your embedded python types, attach instance methods. + // When an instance method is invoked, the "self" py.Object is the instance. + PyVacationStopType.Dict["Set"] = py.MustNewMethod("Set", VacationStop_Set, 0, "") + PyVacationStopType.Dict["Get"] = py.MustNewMethod("Get", VacationStop_Get, 0, "") + PyVacationType.Dict["add_stops"] = py.MustNewMethod("Vacation.add_stops", Vacation_add_stops, 0, "") + PyVacationType.Dict["num_stops"] = py.MustNewMethod("Vacation.num_stops", Vacation_num_stops, 0, "") + PyVacationType.Dict["get_stop"] = py.MustNewMethod("Vacation.get_stop", Vacation_get_stop, 0, "") + + // Bind methods attached at the module (global) level. + // When these are invoked, the first py.Object param (typically "self") is the bound *Module instance. + methods := []*py.Method{ + py.MustNewMethod("VacationStop_new", VacationStop_new, 0, ""), + py.MustNewMethod("Vacation_new", Vacation_new, 0, ""), + } + + // Register a ModuleImpl instance used by the gpython runtime to instantiate new py.Module when first imported. + py.RegisterModule(&py.ModuleImpl{ + Info: py.ModuleInfo{ + Name: "mylib_go", + Doc: "Example embedded python module", + }, + Methods: methods, + Globals: py.StringDict{ + "PY_VERSION": py.String("Python 3.4 (github.com/go-python/gpython)"), + "GO_VERSION": py.String(fmt.Sprintf("%s on %s %s", runtime.Version(), runtime.GOOS, runtime.GOARCH)), + "MYLIB_VERS": py.String("Vacaton 1.0 by Fletch F. Fletcher"), + }, + }) +} + +// VacationStop is an example Go struct to embed. +type VacationStop struct { + Desc py.String + NumNights py.Int +} + +// Type comprises the py.Object interface, allowing a Go struct to be cast as a py.Object. +// Instance methods of an type are then attached to this type object +func (stop *VacationStop) Type() *py.Type { + return PyVacationStopType +} + +func (stop *VacationStop) M__str__() (py.Object, error) { + line := fmt.Sprintf(" %-16v | %2v nights", stop.Desc, stop.NumNights) + return py.String(line), nil +} + +func (stop *VacationStop) M__repr__() (py.Object, error) { + return stop.M__str__() +} + +func VacationStop_new(module py.Object, args py.Tuple) (py.Object, error) { + stop := &VacationStop{} + VacationStop_Set(stop, args) + return stop, nil +} + +// VacationStop_Set is an embedded instance method of VacationStop +func VacationStop_Set(self py.Object, args py.Tuple) (py.Object, error) { + stop := self.(*VacationStop) + + // Check out other convenience functions in py/util.go + // Also available is py.ParseTuple(args, "si", ...) + err := py.LoadTuple(args, []interface{}{&stop.Desc, &stop.NumNights}) + if err != nil { + return nil, err + } + + /* Alternative util func is ParseTuple(): + var desc, nights py.Object + err := py.ParseTuple(args, "si", &desc, &nights) + if err != nil { + return nil, err + } + stop.Desc = desc.(py.String) + stop.NumNights = desc.(py.Int) + */ + + return py.None, nil +} + +// VacationStop_Get is an embedded instance method of VacationStop +func VacationStop_Get(self py.Object, args py.Tuple) (py.Object, error) { + stop := self.(*VacationStop) + + return py.Tuple{ + stop.Desc, + stop.NumNights, + }, nil +} + +type Vacation struct { + Stops []*VacationStop + MadeBy string +} + +func (v *Vacation) Type() *py.Type { + return PyVacationType +} + +func Vacation_new(module py.Object, args py.Tuple) (py.Object, error) { + v := &Vacation{} + + // For Module-bound methods, we have easy access to the parent Module + py.LoadAttr(module, "MYLIB_VERS", &v.MadeBy) + + ret := py.Tuple{ + v, + py.String(v.MadeBy), + } + return ret, nil +} + +func Vacation_num_stops(self py.Object, args py.Tuple) (py.Object, error) { + v := self.(*Vacation) + return py.Int(len(v.Stops)), nil +} + +func Vacation_get_stop(self py.Object, args py.Tuple) (py.Object, error) { + v := self.(*Vacation) + + // Check out other convenience functions in py/util.go + // If you would like to be a contributor for gpython, improving these or adding more is a great place to start! + stopNum, err := py.GetInt(args[0]) + if err != nil { + return nil, err + } + + if stopNum < 1 || int(stopNum) > len(v.Stops) { + return nil, py.ExceptionNewf(py.IndexError, "invalid stop index") + } + + return py.Object(v.Stops[stopNum-1]), nil +} + +func Vacation_add_stops(self py.Object, args py.Tuple) (py.Object, error) { + v := self.(*Vacation) + srcStops, ok := args[0].(py.Tuple) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "expected Tuple, got %T", args[0]) + } + + for _, arg := range srcStops { + stop, ok := arg.(*VacationStop) + if !ok { + return nil, py.ExceptionNewf(py.TypeError, "expected Stop, got %T", arg) + } + + v.Stops = append(v.Stops, stop) + } + + return py.None, nil +} diff --git a/examples/multi-context/main.go b/examples/multi-context/main.go new file mode 100644 index 00000000..e9f72212 --- /dev/null +++ b/examples/multi-context/main.go @@ -0,0 +1,138 @@ +// Copyright 2022 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "log" + "runtime" + "strings" + "sync" + "time" + + // This initializes gpython for runtime execution and is critical. + // It defines forward-declared symbols and registers native built-in modules, such as sys and time. + _ "github.com/go-python/gpython/modules" + + // This is the primary import for gpython. + // It contains all symbols needed to fully compile and run python. + "github.com/go-python/gpython/py" +) + +func main() { + + // The total job count implies a fixed amount of work. + // The number of workers is how many py.Context (in concurrent goroutines) to pull jobs off the queue. + // One worker does all the work serially while N number of workers will (ideally) divides up. + totalJobs := 20 + + for i := 0; i < 10; i++ { + numWorkers := i + 1 + elapsed := RunMultiPi(numWorkers, totalJobs) + fmt.Printf("=====> %2d worker(s): %v\n\n", numWorkers, elapsed) + + // Give each trial a fresh start + runtime.GC() + } + +} + +var jobScript = ` +pi = chud.pi_chudnovsky_bs(numDigits) +last_5 = pi % 100000 +print("%s: last 5 digits of %d is %d (job #%0d)" % (WORKER_ID, numDigits, last_5, jobID)) +` + +var jobSrcTemplate = ` +import pi_chudnovsky_bs as chud + +WORKER_ID = "{{WORKER_ID}}" + +print("%s ready!" % (WORKER_ID)) +` + +type worker struct { + name string + ctx py.Context + main *py.Module + job *py.Code +} + +func (w *worker) compileTemplate(pySrc string) { + pySrc = strings.Replace(pySrc, "{{WORKER_ID}}", w.name, -1) + + mainImpl := py.ModuleImpl{ + CodeSrc: pySrc, + } + + var err error + w.main, err = w.ctx.ModuleInit(&mainImpl) + if err != nil { + log.Fatal(err) + } +} + +func RunMultiPi(numWorkers, numTimes int) time.Duration { + var workersRunning sync.WaitGroup + + fmt.Printf("Starting %d worker(s) to calculate %d jobs...\n", numWorkers, numTimes) + + jobPipe := make(chan int) + go func() { + for i := 0; i < numTimes; i++ { + jobPipe <- i + 1 + } + close(jobPipe) + }() + + // Note that py.Code can be shared (accessed concurrently) since it is an inherently read-only object + jobCode, err := py.Compile(jobScript, "", py.ExecMode, 0, true) + if err != nil { + log.Fatal("jobScript failed to comple") + } + + workers := make([]worker, numWorkers) + for i := 0; i < numWorkers; i++ { + + opts := py.DefaultContextOpts() + + // Make sure our import statement will find pi_chudnovsky_bs + opts.SysPaths = append(opts.SysPaths, "..") + + workers[i] = worker{ + name: fmt.Sprintf("Worker #%d", i+1), + ctx: py.NewContext(opts), + job: jobCode, + } + + workersRunning.Add(1) + } + + startTime := time.Now() + + for i := range workers { + w := workers[i] + go func() { + + // Compiling can be concurrent since there is no associated py.Context + w.compileTemplate(jobSrcTemplate) + + for jobID := range jobPipe { + numDigits := 100000 + if jobID%2 == 0 { + numDigits *= 10 + } + py.SetAttrString(w.main.Globals, "numDigits", py.Int(numDigits)) + py.SetAttrString(w.main.Globals, "jobID", py.Int(jobID)) + w.ctx.RunCode(jobCode, w.main.Globals, w.main.Globals, nil) + } + workersRunning.Done() + }() + } + + workersRunning.Wait() + + return time.Since(startTime) +} diff --git a/py/util.go b/py/util.go new file mode 100644 index 00000000..43028bce --- /dev/null +++ b/py/util.go @@ -0,0 +1,206 @@ +// Copyright 2022 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package py + +import ( + "errors" + "strconv" +) + +var ( + ErrUnsupportedObjType = errors.New("unsupported obj type") +) + +// GetLen is a high-level convenience function that returns the length of the given Object. +func GetLen(obj Object) (Int, error) { + getlen, ok := obj.(I__len__) + if !ok { + return 0, nil + } + + lenObj, err := getlen.M__len__() + if err != nil { + return 0, err + } + + return GetInt(lenObj) +} + +// GetInt is a high-level convenience function that converts the given value to an int. +func GetInt(obj Object) (Int, error) { + toIdx, ok := obj.(I__index__) + if !ok { + _, err := cantConvert(obj, "int") + return 0, err + } + + return toIdx.M__index__() +} + +// LoadTuple attempts to convert each element of the given list and store into each destination value (based on its type). +func LoadTuple(args Tuple, vars []interface{}) error { + + if len(args) > len(vars) { + return ExceptionNewf(RuntimeError, "%d args given, expected %d", len(args), len(vars)) + } + + if len(vars) > len(args) { + vars = vars[:len(args)] + } + + for i, rval := range vars { + err := loadValue(args[i], rval) + if err == ErrUnsupportedObjType { + return ExceptionNewf(TypeError, "arg %d has unsupported object type: %s", i, args[i].Type().Name) + } + } + + return nil +} + +// LoadAttr gets the named attribute and attempts to store it into the given destination value (based on its type). +func LoadAttr(obj Object, attrName string, dst interface{}) error { + attr, err := GetAttrString(obj, attrName) + if err != nil { + return err + } + err = loadValue(attr, dst) + if err == ErrUnsupportedObjType { + return ExceptionNewf(TypeError, "attribute \"%s\" has unsupported object type: %s", attrName, attr.Type().Name) + } + return nil +} + +// LoadIntsFromList extracts a list of ints contained given a py.List or py.Tuple +func LoadIntsFromList(list Object) ([]int64, error) { + N, err := GetLen(list) + if err != nil { + return nil, err + } + + getter, ok := list.(I__getitem__) + if !ok { + return nil, nil + } + + if N <= 0 { + return nil, nil + } + + intList := make([]int64, N) + for i := Int(0); i < N; i++ { + item, err := getter.M__getitem__(i) + if err != nil { + return nil, err + } + + var intVal Int + intVal, err = GetInt(item) + if err != nil { + return nil, err + } + + intList[i] = int64(intVal) + } + + return intList, nil +} + +func loadValue(src Object, data interface{}) error { + var ( + v_str string + v_float float64 + v_int int64 + ) + + haveStr := false + + switch v := src.(type) { + case Bool: + if v { + v_int = 1 + v_float = 1 + v_str = "True" + } else { + v_str = "False" + } + haveStr = true + case Int: + v_int = int64(v) + v_float = float64(v) + case Float: + v_int = int64(v) + v_float = float64(v) + case String: + v_str = string(v) + haveStr = true + case NoneType: + // No-op + default: + return ErrUnsupportedObjType + } + + switch dst := data.(type) { + case *Int: + *dst = Int(v_int) + case *bool: + *dst = v_int != 0 + case *int8: + *dst = int8(v_int) + case *uint8: + *dst = uint8(v_int) + case *int16: + *dst = int16(v_int) + case *uint16: + *dst = uint16(v_int) + case *int32: + *dst = int32(v_int) + case *uint32: + *dst = uint32(v_int) + case *int: + *dst = int(v_int) + case *uint: + *dst = uint(v_int) + case *int64: + *dst = v_int + case *uint64: + *dst = uint64(v_int) + case *float32: + if haveStr { + v_float, _ = strconv.ParseFloat(v_str, 32) + } + *dst = float32(v_float) + case *float64: + if haveStr { + v_float, _ = strconv.ParseFloat(v_str, 64) + } + *dst = v_float + case *Float: + if haveStr { + v_float, _ = strconv.ParseFloat(v_str, 64) + } + *dst = Float(v_float) + case *string: + *dst = v_str + case *String: + *dst = String(v_str) + // case []uint64: + // for i := range data { + // dst[i] = order.Uint64(bs[8*i:]) + // } + // case []float32: + // for i := range data { + // dst[i] = math.Float32frombits(order.Uint32(bs[4*i:])) + // } + // case []float64: + // for i := range data { + // dst[i] = math.Float64frombits(order.Uint64(bs[8*i:])) + // } + + default: + return ExceptionNewf(NotImplementedError, "%s", "unsupported Go data type") + } + return nil +} From 41984ce66bc9fd605e23f0e1df664786a8a92e55 Mon Sep 17 00:00:00 2001 From: Drew O'Meara Date: Tue, 8 Feb 2022 10:38:47 -0600 Subject: [PATCH 02/14] readme tweaks --- examples/embedding/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/embedding/README.md b/examples/embedding/README.md index 3eb17502..9c13d748 100644 --- a/examples/embedding/README.md +++ b/examples/embedding/README.md @@ -9,7 +9,7 @@ Embedding a highly capable and familiar "interpreted" language allows your users to easily augment app behavior, configuration, and customization -- all post-deployment. Have you ever discovered an exciting software project but lost interest when you had to also -learn an esoteric language schema? In an era of limited attention span, +learn its esoteric language schema? In an era of limited attention span, most people are generally turned off if they have to learn a new language in addition to learning to use your app. @@ -38,7 +38,7 @@ modules that are only available in CPython. | | | |---------------------- | ------------------------------------------------------------------| | `main.go` | if no args, runs in REPL mode, otherwise runs the given file | -| `lib/mylib.py` | models a library that your application would expose | +| `lib/mylib.py` | models a library that your application would expose for users | | `lib/REPL-startup.py` | invoked by `main.go` when starting REPL mode | | `mylib-demo.py` | models a user-authored script that consumes `mylib` | | `mylib.module.go` | Go implementation of `mylib_go` consumed by `mylib` | @@ -96,7 +96,7 @@ Spring Break itinerary: ## Takeways - `main.go` demonstrates high-level convenience functions such as `py.RunFile()`. - - Embedding a Go `struct` into a Python object only requires that it implements `py.Object`, which is a single function: + - Embedding a Go `struct` as a Python object only requires that it implement `py.Object`, which is a single function: `Type() *py.Type` - See [py/run.go](https://github.com/go-python/gpython/tree/master/py/run.go) for more about interpreter instances and `py.Context` - - Helper functions available in [py/util.go](https://github.com/go-python/gpython/tree/master/py/util.go) and your contributions are welcome! + - Helper functions are available in [py/util.go](https://github.com/go-python/gpython/tree/master/py/util.go) and your contributions are welcome! From 835e6b0067513db9e95a56fbb92f2ff3c063f566 Mon Sep 17 00:00:00 2001 From: Drew O'Meara Date: Fri, 11 Feb 2022 13:46:25 -0600 Subject: [PATCH 03/14] embedding example now also a test --- .gitignore | 1 + examples/embedding/embedding-test-output.txt | 18 ++++++++ examples/embedding/main_test.go | 45 ++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 examples/embedding/embedding-test-output.txt create mode 100644 examples/embedding/main_test.go diff --git a/.gitignore b/.gitignore index fd546b6e..ff8d3cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ cover.out # tests builtin/testfile +examples/embedding/embedding \ No newline at end of file diff --git a/examples/embedding/embedding-test-output.txt b/examples/embedding/embedding-test-output.txt new file mode 100644 index 00000000..ba610fdd --- /dev/null +++ b/examples/embedding/embedding-test-output.txt @@ -0,0 +1,18 @@ + +Welcome to a gpython embedded example, + where your wildest Go-based python dreams come true! + +========================================================== + Python 3.4 (github.com/go-python/gpython) + go1.17.6 on darwin amd64 +========================================================== + +Spring Break itinerary: + Stop 1: Miami, Florida | 7 nights + Stop 2: Mallorca, Spain | 3 nights + Stop 3: Ibiza, Spain | 14 nights + Stop 4: Monaco | 12 nights +### Made with Vacaton 1.0 by Fletch F. Fletcher + +I bet Monaco will be the best! + diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go new file mode 100644 index 00000000..ce27b441 --- /dev/null +++ b/examples/embedding/main_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "bytes" + "os" + "os/exec" + "testing" +) + +const embeddingTestOutput = "embedding-test-output.txt" + +func TestEmbeddedExample(t *testing.T) { + + cmd := exec.Command("go", "build", ".") + err := cmd.Run() + if err != nil { + t.Fatalf("failed to compile embedding example: %v", err) + } + + out := new(bytes.Buffer) + cmd = exec.Command("./embedding", "mylib-demo.py") + cmd.Stdout = out + + err = cmd.Run() + if err != nil { + t.Fatalf("failed to run embedding binary: %v", err) + } + + resetTest := false // true + testOutput := out.Bytes() + if resetTest { + err = os.WriteFile(embeddingTestOutput, testOutput, 0644) + if err != nil { + t.Fatalf("failed to write test output: %v", err) + } + } else { + mustMatch, err := os.ReadFile(embeddingTestOutput) + if err != nil { + t.Fatalf("failed read %q", embeddingTestOutput) + } + if !bytes.Equal(testOutput, mustMatch) { + t.Fatalf("embedded test output did not match accepted output from %q", embeddingTestOutput) + } + } +} From cdf3d5b44c0349a4218051c557fc1a8b9be34dcf Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 09:58:17 -0600 Subject: [PATCH 04/14] Update examples/embedding/mylib.module.go Co-authored-by: Sebastien Binet --- examples/embedding/mylib.module.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/embedding/mylib.module.go b/examples/embedding/mylib.module.go index b510277c..8a848411 100644 --- a/examples/embedding/mylib.module.go +++ b/examples/embedding/mylib.module.go @@ -45,7 +45,7 @@ func init() { Globals: py.StringDict{ "PY_VERSION": py.String("Python 3.4 (github.com/go-python/gpython)"), "GO_VERSION": py.String(fmt.Sprintf("%s on %s %s", runtime.Version(), runtime.GOOS, runtime.GOARCH)), - "MYLIB_VERS": py.String("Vacaton 1.0 by Fletch F. Fletcher"), + "MYLIB_VERS": py.String("Vacation 1.0 by Fletch F. Fletcher"), }, }) } From 3dff76620d1b3d29f41c060a68ec53c72f2fbdd3 Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 09:58:55 -0600 Subject: [PATCH 05/14] Update examples/embedding/main_test.go Co-authored-by: Sebastien Binet --- examples/embedding/main_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index ce27b441..812082b9 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -7,7 +7,8 @@ import ( "testing" ) -const embeddingTestOutput = "embedding-test-output.txt" +const embeddingTestOutput = "testdata/embedding_golden.txt" +var regen = flag.Bool("regen", false, "regenerate golden files") func TestEmbeddedExample(t *testing.T) { From 249e14e5ee022065e5cd45f1fcee41b6868cfe46 Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 09:59:04 -0600 Subject: [PATCH 06/14] Update examples/embedding/main_test.go Co-authored-by: Sebastien Binet --- examples/embedding/main_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index 812082b9..153fa2ce 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -12,7 +12,10 @@ var regen = flag.Bool("regen", false, "regenerate golden files") func TestEmbeddedExample(t *testing.T) { - cmd := exec.Command("go", "build", ".") + tmp, err := os.MkdirTemp("", "go-python-embedding-") + if err != nil { t.Fatal(err) } + defer os.RemoveAll(tmp) + cmd := exec.Command("go", "build", "-o", filepath.Join(tmp,"exe"), ".") err := cmd.Run() if err != nil { t.Fatalf("failed to compile embedding example: %v", err) From 76c8b51b257c76a8a7037609beeee1bd397e7aef Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 09:59:14 -0600 Subject: [PATCH 07/14] Update examples/embedding/main_test.go Co-authored-by: Sebastien Binet --- examples/embedding/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index 153fa2ce..662d7744 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -22,7 +22,7 @@ func TestEmbeddedExample(t *testing.T) { } out := new(bytes.Buffer) - cmd = exec.Command("./embedding", "mylib-demo.py") + cmd = exec.Command(filepath.Join(tmp,"exe"), "mylib-demo.py") cmd.Stdout = out err = cmd.Run() From 17f269072c3a2d4365b54279cbf53ae22fd7f3e1 Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 09:59:23 -0600 Subject: [PATCH 08/14] Update examples/embedding/main_test.go Co-authored-by: Sebastien Binet --- examples/embedding/main_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index 662d7744..81b86a81 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -30,14 +30,13 @@ func TestEmbeddedExample(t *testing.T) { t.Fatalf("failed to run embedding binary: %v", err) } - resetTest := false // true testOutput := out.Bytes() - if resetTest { + if *regen { err = os.WriteFile(embeddingTestOutput, testOutput, 0644) if err != nil { t.Fatalf("failed to write test output: %v", err) } - } else { + } mustMatch, err := os.ReadFile(embeddingTestOutput) if err != nil { t.Fatalf("failed read %q", embeddingTestOutput) From e0f7a1123426b175d198dbe9d6f48e1fb90bd783 Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 09:59:28 -0600 Subject: [PATCH 09/14] Update examples/embedding/main_test.go Co-authored-by: Sebastien Binet --- examples/embedding/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index 81b86a81..7fc60957 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -44,5 +44,4 @@ func TestEmbeddedExample(t *testing.T) { if !bytes.Equal(testOutput, mustMatch) { t.Fatalf("embedded test output did not match accepted output from %q", embeddingTestOutput) } - } } From 4a49a6d8eed18fa591fd30abe05c0f8046ca4c32 Mon Sep 17 00:00:00 2001 From: Drew O'Meara Date: Sat, 12 Feb 2022 10:08:48 -0600 Subject: [PATCH 10/14] CI-friendly golden testing fixes --- examples/embedding/main_test.go | 33 ++++++++++++------- .../embedding_out_golden.txt} | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) rename examples/embedding/{embedding-test-output.txt => testdata/embedding_out_golden.txt} (91%) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index 7fc60957..61de68a4 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -2,27 +2,33 @@ package main import ( "bytes" + "flag" "os" "os/exec" + "path/filepath" "testing" ) -const embeddingTestOutput = "testdata/embedding_golden.txt" +const embeddingTestOutput = "testdata/embedding_out_golden.txt" + var regen = flag.Bool("regen", false, "regenerate golden files") func TestEmbeddedExample(t *testing.T) { tmp, err := os.MkdirTemp("", "go-python-embedding-") - if err != nil { t.Fatal(err) } + if err != nil { + t.Fatal(err) + } defer os.RemoveAll(tmp) - cmd := exec.Command("go", "build", "-o", filepath.Join(tmp,"exe"), ".") - err := cmd.Run() + + cmd := exec.Command("go", "build", "-o", filepath.Join(tmp, "exe"), ".") + err = cmd.Run() if err != nil { t.Fatalf("failed to compile embedding example: %v", err) } out := new(bytes.Buffer) - cmd = exec.Command(filepath.Join(tmp,"exe"), "mylib-demo.py") + cmd = exec.Command(filepath.Join(tmp, "exe"), "mylib-demo.py") cmd.Stdout = out err = cmd.Run() @@ -31,17 +37,20 @@ func TestEmbeddedExample(t *testing.T) { } testOutput := out.Bytes() + + flag.Parse() if *regen { err = os.WriteFile(embeddingTestOutput, testOutput, 0644) if err != nil { t.Fatalf("failed to write test output: %v", err) } } - mustMatch, err := os.ReadFile(embeddingTestOutput) - if err != nil { - t.Fatalf("failed read %q", embeddingTestOutput) - } - if !bytes.Equal(testOutput, mustMatch) { - t.Fatalf("embedded test output did not match accepted output from %q", embeddingTestOutput) - } + + mustMatch, err := os.ReadFile(embeddingTestOutput) + if err != nil { + t.Fatalf("failed read %q", embeddingTestOutput) + } + if !bytes.Equal(testOutput, mustMatch) { + t.Fatalf("embedded test output did not match accepted output from %q", embeddingTestOutput) + } } diff --git a/examples/embedding/embedding-test-output.txt b/examples/embedding/testdata/embedding_out_golden.txt similarity index 91% rename from examples/embedding/embedding-test-output.txt rename to examples/embedding/testdata/embedding_out_golden.txt index ba610fdd..01ca5e3b 100644 --- a/examples/embedding/embedding-test-output.txt +++ b/examples/embedding/testdata/embedding_out_golden.txt @@ -12,7 +12,7 @@ Spring Break itinerary: Stop 2: Mallorca, Spain | 3 nights Stop 3: Ibiza, Spain | 14 nights Stop 4: Monaco | 12 nights -### Made with Vacaton 1.0 by Fletch F. Fletcher +### Made with Vacation 1.0 by Fletch F. Fletcher I bet Monaco will be the best! From 16ebe644e5d6f2caaebc1503f747037045e44d3c Mon Sep 17 00:00:00 2001 From: Drew O'Meara Date: Sat, 12 Feb 2022 10:13:19 -0600 Subject: [PATCH 11/14] golden test output no longer platform specific --- examples/embedding/lib/mylib.py | 4 +--- examples/embedding/testdata/embedding_out_golden.txt | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/embedding/lib/mylib.py b/examples/embedding/lib/mylib.py index 4c279c78..d74c2f66 100644 --- a/examples/embedding/lib/mylib.py +++ b/examples/embedding/lib/mylib.py @@ -1,15 +1,13 @@ import mylib_go as _go PY_VERSION = _go.PY_VERSION -GO_VERSION = _go.GO_VERSION print(''' ========================================================== %s - %s ========================================================== -''' % (PY_VERSION, GO_VERSION)) +''' % (PY_VERSION)) def Stop(location, num_nights = 2): diff --git a/examples/embedding/testdata/embedding_out_golden.txt b/examples/embedding/testdata/embedding_out_golden.txt index 01ca5e3b..c7443e07 100644 --- a/examples/embedding/testdata/embedding_out_golden.txt +++ b/examples/embedding/testdata/embedding_out_golden.txt @@ -4,7 +4,6 @@ Welcome to a gpython embedded example, ========================================================== Python 3.4 (github.com/go-python/gpython) - go1.17.6 on darwin amd64 ========================================================== Spring Break itinerary: From 3d3738419d510f9bf05ee36c50ed3a48c8a8ac5e Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 10:45:17 -0600 Subject: [PATCH 12/14] Update examples/embedding/main_test.go Co-authored-by: Sebastien Binet --- examples/embedding/main_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index 61de68a4..b20a8514 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -21,7 +21,8 @@ func TestEmbeddedExample(t *testing.T) { } defer os.RemoveAll(tmp) - cmd := exec.Command("go", "build", "-o", filepath.Join(tmp, "exe"), ".") + exe := filepath.Join(tmp, "out.exe") + cmd := exec.Command("go", "build", "-o", exe, ".") err = cmd.Run() if err != nil { t.Fatalf("failed to compile embedding example: %v", err) From ba0f3daa4373f23528667f6e715efcf1647bad7b Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 10:46:07 -0600 Subject: [PATCH 13/14] Update examples/embedding/main_test.go Co-authored-by: Sebastien Binet --- examples/embedding/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/embedding/main_test.go b/examples/embedding/main_test.go index b20a8514..e5287be8 100644 --- a/examples/embedding/main_test.go +++ b/examples/embedding/main_test.go @@ -29,7 +29,7 @@ func TestEmbeddedExample(t *testing.T) { } out := new(bytes.Buffer) - cmd = exec.Command(filepath.Join(tmp, "exe"), "mylib-demo.py") + cmd = exec.Command(exe, "mylib-demo.py") cmd.Stdout = out err = cmd.Run() From c67547f6356979cc7925d43778eb5ddfee1fad3a Mon Sep 17 00:00:00 2001 From: Drew O'Meara <35852900+drew-512@users.noreply.github.com> Date: Sat, 12 Feb 2022 10:47:41 -0600 Subject: [PATCH 14/14] Update examples/embedding/lib/mylib.py Co-authored-by: Sebastien Binet --- examples/embedding/lib/mylib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/embedding/lib/mylib.py b/examples/embedding/lib/mylib.py index d74c2f66..dd5af499 100644 --- a/examples/embedding/lib/mylib.py +++ b/examples/embedding/lib/mylib.py @@ -7,7 +7,7 @@ ========================================================== %s ========================================================== -''' % (PY_VERSION)) +''' % (PY_VERSION, )) def Stop(location, num_nights = 2): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy