Skip to content

atsaero/pygoridge

Repository files navigation

Pygoridge: Python-to-Golang IPC bridge, python client for Goridge

GoDoc

Pygoridge is a Python-to-Golang codec library which works over sockets and Golang net/rpc package. This is a python port of php client for an excellent Goridge library. The library allows you to call Go service methods from Python with minimal footprint, structures and []byte support.

Also Pygoridge includes Worker class to use in worker processes with https://github.com/spiral/roadrunner - high-performance application server, load-balancer and process manager written in Golang.

Features

  • no external dependencies
  • can be used with RoadRunner to create CPU-intensive servers with simple sequential python workers (no multiprocessing module required). This is really helpful to overcome GIL.

Installation

$ go get "github.com/spiral/goridge"
$ pip install pygoridge

Example: python client calls go server methods

from pygoridge import create_relay, RPC, SocketRelay

rpc = RPC(SocketRelay("127.0.0.1", 6001))

# or, using factory
tcp_relay = create_relay("tcp://127.0.0.1:6001")
unix_relay = create_relay("unix:///tmp/rpc.sock")
stream_relay = create_relay("pipes")

print(rpc("App.Hi", "Antony"))
rpc.close()     # close underlying socket connection

# or using as a context manager
with RPC(tcp_relay) as rpc:
    print(rpc("App.Hi", "Antony, again"))
package main

import (
    "fmt"
    "github.com/spiral/goridge"
    "net"
    "net/rpc"
)

type App struct{}

func (s *App) Hi(name string, r *string) error {
    *r = fmt.Sprintf("Hello, %s!", name)
    return nil
}

func main() {
    ln, err := net.Listen("tcp", ":6001")
    if err != nil {
        panic(err)
    }

    rpc.Register(new(App))

    for {
        conn, err := ln.Accept()
        if err != nil {
            continue
        }
        go rpc.ServeCodec(goridge.NewCodec(conn))
    }
}

Example: go http server (RoadRunner) with python workers

You can download latest RoadRunner binary from releases page.

See also examples.

cd examples/roadrunner/http_server/
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Worker class

from functools import partial

import ujson
from pygoridge import create_relay, Worker


json_dumps = partial(
    ujson.dumps, ensure_ascii=False,
    escape_forward_slashes=False)
json_loads = ujson.loads


class HTTPWorker(Worker):

    def hello(self, headers):
        return headers, {"X-Server": "RoadRunner with python workers"}


if __name__ == "__main__":
    rl = create_relay("pipes")
    worker = HTTPWorker(rl, json_encoder=json_dumps, json_decoder=json_loads)

    while True:
        context, body = worker.receive()
        if context is None:
            continue
        http_headers = json_loads(context.tobytes())
        response, response_headers = worker.hello(http_headers)
        worker.send(
           json_dumps(response).encode("utf-8"),
           response_headers
        )

Run RoadRunner server

cd examples/roadrunner/http_server/
./rr serve -d -v

Make http request to get request headers back as a response body

curl 'http://localhost:8080/' --compressed

RoadRunner is highly customizable and extendable so you can even write your own plugin for it with required API protocol (see for example php-grpc server).

Custom encoders/decoders for faster json processing

from pygoridge.json import json_dumps, json_loads


# you can also provide custom json encoder for faster marshalling
rpc = RPC(tcp_relay, json_encoder=json_dumps, json_decoder=json_loads)

License

The MIT License (MIT). Please see LICENSE for more information.

Development

Run tests

docker-compose -f ./goridge/tests/docker-compose.yml up -d
docker-compose -f tests/rr_test_app/docker-compose.yml up -d
python3 -m unittest discover -s tests

Run linter

pip install flake8
flake8 pygoridge