Skip to content

Latest commit

 

History

History
98 lines (83 loc) · 3.46 KB

README.md

File metadata and controls

98 lines (83 loc) · 3.46 KB

Erlastic

Installation

pip3 install git+https://github.com/skosch/python-erlastic

Usage

Erlastic allows you to serialize/deserialize python objects into erlang binary term.

Basic usage is :

import erlastic
py_struct = erlastic.decode(binary_term)
binary = erlastic.encode(py_struct)

Sending and receiving from Python

The library contains also a function to use python with erlastic in an erlang port to communicate erlang binary term : port_communication() which return (mailbox,port). They are both python coroutines (executed generator) so you can communicate with erlang coroutine using python abstractions :

So for instance, if you want to create a Python script which receives a 2-tuple of numbers {A, B} and returns {ok, A/B} or {error, divisionbyzero}, then you can use:

from erlastic import port_connection, Atom as A
mailbox, port = port_connection()

for (a, b) in mailbox:
  if b != 0:
    port.send((A("ok"), a / b))
  else:
    port.send((A("error"), A("divisionbyzero")))

Converting between bytes and strings

Strings are sent as raw bytes with unknown character encoding. To recursively convert data from raw bytes to Python strings, you can pass received data through a function like

def convert_to_string(data):
    if isinstance(data, bytes):  return data.decode('ascii') # or 'utf-8' ...
    if isinstance(data, dict):   return dict(map(convert_to_string, data.items()))
    if isinstance(data, tuple):  return tuple(map(convert_to_string, data))
    if isinstance(data, list):   return list(map(convert_to_string, data))
    return data

Printing to the terminal from iex

If the output of Python's print statements aren't showing up in your terminal, try printing to stderr instead, using a function like eprint below:

import sys
import pprint

pprinter = pprint.PrettyPrinter(indent=2, stream=sys.stderr, width=80)
def eprint(*args):
    pprinter.pprint(args)

Sending and receiving from Erlang or Elixir

and at the erlang side, use -u python parameter to prevent python output buffering, use 4 bytes packet length because it is the configuration used by the python generators.

Port = open_port({spawn,"python3 -u division.py"},[binary,{packet,4}]),
Div = fun(A,B)->
  Port ! {self(),{command,term_to_binary({A,B})}},
  receive {Port,{data,Bin}}->binary_to_term(Bin) after 1000->{error,timeout} end
end,
io:format("send {A,B}=~p, python result : ~p~n",[{32,10},Div(32,10)]),
io:format("send {A,B}=~p, python result : ~p~n",[{2,0},Div(2,0)]),
io:format("send {A,B}=~p, python result : ~p~n",[{1,1},Div(1,1)])

or in Elixir:

port = Port.open({:spawn, "python3 -u division.py"}, [:binary, :exit_status, packet: 4])
div = fn (a, b) ->
  send port, {self(), {:command, :erlang.term_to_binary({a, b})}}

  receive do
    {^port, {_, b}} -> binary_to_term(b)
  after
    100 -> {:error, :timeout}
  end
end

IO.puts "send {a, b}={32, 10}, python result: #{inspect div.(32, 10)}"
IO.puts "send {a, b}={2, 0}, python result: #{inspect div.(2, 0)}"
IO.puts "send {a, b}={1, 1}, python result: #{inspect div.(1, 1)}"