Asynchronous communication : Read of this post before continuing. Important Part I In we looked at how to call Python functions from Elixir using Erlport! Part I, is an Elixir library which makes it easier to connect Erlang to a number of other programming languages using the Erlang port protocol. ErlPort However, as you may have noticed, the calling process had to wait for Python to finish processing and return the results. This works for operations that are instant and doesn’t take much time. Otherwise, it’s not much fun when you are running an intensive Python function. Because your Elixir program is gonna hang waiting for the Python code to finish. For operations that take a while to complete we need a way to call the Python function and get notified in our elixir code when the function is complete — similar to how we write elixir program with processes talking to each asynchronously. This is useful, especially in cases when you want an Elixir process to control/supervise several Python operations. We can run the Python functions concurrently which makes life a little better. In this post, we’ll explore how to achieve this — Asynchronous communication between Python and Elixir! We can reason about the challenge the same way we do in communicating between Elixir processes — by using — which allows us to communicate between processes asynchronously. cast Previously, we used mostly when calling functions from Python. This is synchronous and the elixir program had to wait for the python function to finish and send response. erlport’s [:python.call/4](http://erlport.org/docs/python.html#python-call-4) In this post, we will use [:python.cast/2](http://erlport.org/docs/python.html#python-cast-2) Understanding :python.cast/2 works the same as . You give it process id (or registered process name) and the message to send to that process. Unlike variant, is used to send message instead of calling a Python function. Same way your Elixir processes are sent messages. Erlport provides a mechanism for us to receive messages sent to the python instance. :python.cast/2 [GenServer.cast/2](https://hexdocs.pm/elixir/GenServer.html#cast/2) :python.call :python.cast Erlport Python Module Erlport offers a that helps with working with elixir data types and processes. You can install erlport using . python library pip pip install erlport #don't run this yet! However, the Erlport Elixir repo already comes with the Python library which are automatically loaded at runtime. So you have to install the Erlport Python library if you are using Erlport through Elixir. don’t Below is how data is mapped between Erlang/Elixir and Python Erlang/Elixir data types mapped to Python data types ( ) http://erlport.org/docs/python.html Python data types mapped to Erlang/Elixir data types ( ) http://erlport.org/docs/python.html Handling Message Sent from Elixir to Python For Elixir processes you have a receive block in which all messages are handled. Or the , and functions if you are using GenServer. Erlport provides a similar mechanism for handling messages sent to the Python process. is the function. It’s in the module. It takes a Python function with one argument-message i.e the incoming message. Erlport will then call the function passed to whenever a message is received. It’s like a callback function. handle_call handle_cast handle_info _set_message_handler_ erlport.erlang set_message_handler Sending Message from Python to Elixir Since an Elixir process isn’t aware of who sent a message, we have to find a way to know which process to send the result to once the python function completes. One way is to register the process id, of the Elixir process that you want the result sent to. cast The Erlport python module provides function for sending a message to Elixir process. With this we can send asynchronously message back to the elixir process when the function is done! [cast](http://erlport.org/docs/python.html#erlport-erlang-cast) Putting it all together Open Terminal and create a new Elixir project using mix $ mix new elixir_python Add dependency Add to your dependencies erlport mix.exs defp deps do[ ,]end {:erlport, "~> 0.9"} Then install project dependencies. $ cd elixir_python$ mix deps.get Create directory where you’ll keep our python modules priv/python $ mkdir -p priv/python Create an elixir module wrapper for Erlport related functions. #lib/python.ex ElixirPython.Python defmodule do @doc """ Start python instance with custom modules dir priv/python """ **def** start() **do** path = \[ :code.priv\_dir(:elixir\_python), "python" \]|> Path.join() {:ok, pid} = :python.start(\[ {:python\_path, to\_charlist(path)} \]) pid **end** **def** call(pid, m, f, a \\\\ \[\]) **do** :python.call(pid, m, f, a) **end** **def** cast(pid, message) **do** :python.cast(pid, message) **end** **def** stop(pid) **do** :python.stop(pid) **end** end In order for Elixir process to receive message asynchronously, we will create a simple module GenServer #lib/python_server.ex ElixirPython.PythonServer GenServeralias ElixirPython.Python defmodule douse start_link() GenServer.start_link(__MODULE__, []) def do end init(args) #start the python session and keep pid in statepython_session = Python.start()#register this process as the message handlerPython.call(python_session, :test, :register_handler, [self()]){:ok, python_session} def do end cast_count(count) {:ok, pid} = start_link()GenServer.cast(pid, {:count, count})**end def do def** call_count(count) {:ok, pid} = start_link()# :infinity timeout only for demo purposesGenServer.call(pid, {:count, count}, :infinity)**end do def** handle_call({:count, count}, from, session) result = Python.call(session, :test, :long_counter, [count]){:reply, result, session} do end handle_cast({:count, count}, session) Python.cast(session, count){:noreply, session} def do end handle_info({:python, message}, session) IO.puts("Received message from python: #{inspect message}") def do #stop elixir process {:stop, :normal, session} end terminate(_reason, session) Python.stop(session):ok def do end end Now lets create the python module priv/python/test.py #priv/python/test.py time sys#import erlport modules and functions erlport.erlang set_message_handler, cast erlport.erlterms Atom import import from import from import message_handler = #reference to the elixir process to send result to None cast_message(pid, message):cast(pid, message) def register_handler(pid):#save message handler pid message_handlermessage_handler = pid def global handle_message(count): :print "Received message from Elixir"print countresult = long_counter(count)if message_handler:#build a tuple to atom {:python, result}cast_message(message_handler, (Atom('python'), result)) Exception, e:# you can send error to elixir process here too# print e long_counter(count=100):#simluate a time consuming python functioni = 0data = [] i < count:time.sleep(1) #sleep for 1 secdata.append(i+1)i = i + 1 data def try except pass def while return set_message_handler(handle_message) #set handle_message to receive all messages sent to this python instance Compile! $ mix compile Run! $ iex -S mixiex(1)> ElixirPython.PythonServer.call_count(4)[1, 2, 3, 4] #printed after waiting 4seciex(2)> ElixirPython.PythonServer.call_count(10)[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] #printed after waiting 10sec! Also notice that causes to hang till we get the back result from python call_count iex Now let’s call same python function asynchronously! iex(3)> ElixirPython.PythonServer.cast_count(4):okReceived message from Elixir4Received message from python: [1, 2, 3, 4] # printed after 4sec, no waitiex(4)> ElixirPython.PythonServer.cast_count(10):okReceived message from Elixir10iex(5)>nilReceived message from python: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] #printed after 10sec without blocking Notice how we can continue to work in iex and get the result once our python function completes. Try calling with different numbers — large and small. cast_count That is it! Now you can communicate between Elixir and Python asynchronously. Happy coding!