pythonrt

package
v0.6.10 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2024 License: Apache-2.0 Imports: 23 Imported by: 0

README

Python Runtime

Implementation of Python runtime. See Python runtime for list of issues.

Currently, we don't support 3rd party packages (e.g. pip install) for the user code. See ENG-538 for more details. For a realistic POC/demo, we'll pre-install the packages the user code requires (e.g. slack-sdk).

Python Handler Function

Python's handler functions should receive a single dict value which is the triggering event. This event has the following keys:

  • event_type (str): The event type
  • event_id (str): The event ID
  • integration_id (str): The ID of the integration that fired the event
  • data (dict): The event payload (integration specific)

The return value from Python entry points is ignored and is not passed back to autokitteh.

Limitations

You can't issue function calls at module level (e.g. TOKEN = os.getenv('TOKEN'))

Python serializes function calls using pickle, some callables can't be pickled:

  • lambda
  • dynamically generate functions (notably os.environ.get)

Patching User Code

The Python code (ak_runner.py) loads the user code and patches every function call. It does so by hooking into the regular import hook. When the user module is loaded, we transform the AST to change function calls from:

urlopen(url)

to:

_ak_call(urlopen, url)

ak_call will call the Go Python runtime which will start an activity.

Detecting External Function Calls

When the user module is loaded, we set MODULE_NAME to the name of the loaded module. When ak_call is invoked with a function it compares the function module name with MODULE_NAME, if they are the same, it's an internal function and ak_call will return the invocation of the function. Otherwise, ak_call will call the Go process that will invoke an activity.

See ENG-495.

Go ↔ Python Communication Flow

A run calls start a Python server with:

  • Tar file containing user code
  • Entry point (e.g. review.py:on_github_pull_request)

It will also inject vars definition from the manifest to the Python process environment.

The Python server returns a list of exported symbols from the user code.

Communication Sequence

A call with function and payload:

sequenceDiagram
    Python-->>Go: Module loaded (exports)
    Go->>Python: Run(function, payload)
    loop
        Python-->>Go: Activity request (function name, args, payload)
        Go ->>Python: Activity call (payload, skipped in reply)
        Python-->>Go: Activity value (skipped in reply)
        Go ->>Python: Activity Value
    end
    Python-->>Go: Run return (None)

Other messages are:

  • log from Python to Go
  • sleep from Python to Go
Communication Protocol

We're using JSON over Unix domain socket, one JSON object per line. The reason do this is that ak_runner.py should not have any external dependencies outside the standard library. Once we introduce an external dependency, it will conflict with the user dependencies.

All messages have top level type and payload, the payload changes depending on the type.

run

  • func_name: Function name (string)
  • event: Data payload (map)

module

  • entries: List of exported functions (array of strings)

callback

  • name: Function name (string)
  • args: Function args (array of strings)
  • kw: Mapping of name → value (both strings)
  • data: Pickled (protocol 0) tuple of (func, args, kw) encoded in base64

response

  • value: Return type from Python (base64 of Python's pickle protocol 0)

done

  • No fields
Integration Testing

Make sure that the py-sdk directory is in your PYTHONPATH.

If you run ak with a database, then run make create-workflow once. Otherwise run it every time. This will create a deployment for testdata/simple/

Then run make run-workflow.

ak with database

Look for the config.yaml in ak config where directory. Then add the following

db:
  dsn: /tmp/ak.db  # Pick any other location
  type: sqlite

Hacking

Since ak_runner.py is embedded in ak, you'll need to build it (make bin from root of project) every time you change Python code and want to run workflows.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Runtime = &sdkruntimes.Runtime{
		Desc: kittehs.Must1(sdktypes.StrictRuntimeFromProto(&sdktypes.RuntimePB{
			Name:           "python",
			FileExtensions: []string{"py"},
		})),
		New: New,
	}
)

Functions

func New

func New() (sdkservices.Runtime, error)

Types

type CallbackMessage added in v0.4.6

type CallbackMessage struct {
	Name string            `json:"name"`
	Args []string          `json:"args"`
	Kw   map[string]string `json:"kw"`
	Data []byte            `json:"data"`
}

py <-> go

func (CallbackMessage) Type added in v0.4.6

func (CallbackMessage) Type() string

type Comm added in v0.4.6

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

func NewComm added in v0.4.6

func NewComm(conn net.Conn) *Comm

func (*Comm) Close added in v0.4.6

func (c *Comm) Close() error

func (*Comm) Recv added in v0.4.6

func (c *Comm) Recv() (Message, error)

func (*Comm) Send added in v0.4.6

func (c *Comm) Send(msg Typed) error

type DoneMessage added in v0.5.7

type DoneMessage struct{}

func (DoneMessage) Type added in v0.5.7

func (DoneMessage) Type() string

type Export added in v0.6.5

type Export struct {
	Name string
	File string
	Line int
}

type LogMessage added in v0.6.3

type LogMessage struct {
	Level   string `json:"level"`
	Message string `json:"message"`
}

func (LogMessage) Type added in v0.6.3

func (LogMessage) Type() string

type Message added in v0.4.6

type Message struct {
	Type    string          `json:"type"`
	Payload json.RawMessage `json:"payload"`
}

type ModuleMessage added in v0.4.6

type ModuleMessage struct {
	Entries []string `json:"entries"`
}

py -> go

func (ModuleMessage) Type added in v0.4.6

func (ModuleMessage) Type() string

type ResponseMessage added in v0.4.6

type ResponseMessage struct {
	Value []byte `json:"value"`
}

py <-> go

func (ResponseMessage) Type added in v0.4.6

func (ResponseMessage) Type() string

type RunMessage added in v0.4.6

type RunMessage struct {
	FuncName string         `json:"func_name"`
	Event    map[string]any `json:"event"`
}

go -> python

func (RunMessage) Type added in v0.4.6

func (RunMessage) Type() string

type SleepMessage added in v0.6.3

type SleepMessage struct {
	Seconds float64 `json:"seconds"`
}

func (SleepMessage) Type added in v0.6.3

func (SleepMessage) Type() string

type SubMessage added in v0.4.6

type Typed added in v0.4.6

type Typed interface {
	Type() string
}

type Version added in v0.4.6

type Version struct {
	Major int
	Minor int
}

Jump to

Keyboard shortcuts

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