Skip to content

added examples and py utils #159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Feb 12, 2022
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ cover.out

# tests
builtin/testfile
examples/embedding/embedding
102 changes: 102 additions & 0 deletions examples/embedding/README.md
Original file line number Diff line number Diff line change
@@ -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 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.

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 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` |


### 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` 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 are available in [py/util.go](https://github.com/go-python/gpython/tree/master/py/util.go) and your contributions are welcome!
8 changes: 8 additions & 0 deletions examples/embedding/lib/REPL-startup.py
Original file line number Diff line number Diff line change
@@ -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 *

51 changes: 51 additions & 0 deletions examples/embedding/lib/mylib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import mylib_go as _go

PY_VERSION = _go.PY_VERSION


print('''
==========================================================
%s
==========================================================
''' % (PY_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)


51 changes: 51 additions & 0 deletions examples/embedding/main.go
Original file line number Diff line number Diff line change
@@ -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
}
57 changes: 57 additions & 0 deletions examples/embedding/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"bytes"
"flag"
"os"
"os/exec"
"path/filepath"
"testing"
)

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)
}
defer os.RemoveAll(tmp)

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

out := new(bytes.Buffer)
cmd = exec.Command(exe, "mylib-demo.py")
cmd.Stdout = out

err = cmd.Run()
if err != nil {
t.Fatalf("failed to run embedding binary: %v", err)
}

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)
}
}
15 changes: 15 additions & 0 deletions examples/embedding/mylib-demo.py
Original file line number Diff line number Diff line change
@@ -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])
Loading