Mixing Python with Elixir

Written by badu_bizzle | Published 2017/09/09
Tech Story Tags: elixir | python | erlang | qr-code | programming

TLDRvia the TL;DR App

Generating and Decoding QRCodes using Python modules from Elixir app

The Erlang VM, on which Elixir runs, is efficient for applications in particular domains but falls short when it comes to other operations like data processing and computation-heavy stuff. Python on the other hand, has a rich set of packages for tackling data processing and scientific calculations.

Luckily the Erlang VM allows interoperability with other languages using the Erlang port protocol.

ErlPort is a library for Elixir which makes it easier to connect Erlang to a number of other programming languages using the Erlang port protocol. Currently Erlport supports Python and Ruby.

In this post we’ll see how we can call Python module to generate QRCode right from our Elixir app.

**Erlport Python Primer**To use Erlport, just add to your dependencies

#mix.exs{:erlport, "~> 0.9"}

Once Erlport is added to your project, you have access to :_python_ module in your elixir code. This Erlport python module allows you to start an Elixir process to execute python code.

#creates and Elixir process for calling python functions{:ok, pid} = :python.start()

#get the current python version:python.call(pid, :sys, :'version.__str__', [])

Even better, Erlport allows you to configure python path so you canload custom python modules from a specific directory!

#Automatically load all modules in directory with path /custom/modules/directory

{:ok, pid} = :python.start([{:python_path, '/custom/modules/directory'}])

With the process returned from calling start/1 , you can call functions in your python module using the familiar MFA - module, function, argument format

{:ok, pid} = :python.start([{:python_path, 'custom_modules_directory'}])module = :test #python module to callfunction = :hello # function in modulearguments = ["World"] # list of arguments pass to function the functionresult = :python.call(p, module, function, arguments)

If the python function returns data, it will be bound to result variable in the code above. Otherwise :python.call/4 returns :undefined for python functions that don’t return any data.

Let Create The Elixir ProjectIn this project, we will create a simple app that encode string to QRCode images. Then decode the QRCode image file to get the string back. Instead of using native Elixir/Erlang lib we will call Python module (qrtools) from our app to do the encoding and decoding. Check here to setup qrtools.

1. Create the Elixir OTP app

$ mix new elixir_python_qrcode

2. Add dependency

Add erlport to your dependencies mix.exs

defp deps do[# {:dep_from_hexpm, "~> 0.3.0"},# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},{:erlport, "~> 0.9"},]end

Then install project dependencies.

$ mix deps.get

3. Lets add wrapper Elixir module for Erlport

Create lib/helper.ex , and add Elixir module called ElixirPythonQrcode.Helper and add these helper functions.

defmodule ElixirPythonQrcode.Helper do@doc """

Parameters

- path: directory to include in python path (charlist)  

"""def python_instance(path) when is_list(path) do{:ok, pid} = :python.start([{:python_path, to_charlist(path)}])pidend

def python_instance(_) do{:ok, pid} = :python.start()pidend

@doc """Call python function using MFA format"""def call_python(pid, module, function, arguments \\ []) dopid|>:python.call(module, function, arguments)

endend

4. Add Module code for the main.

We will have two functions encode/2 — which takes in some string and file path. It encodes the data and write the QRCode image to the given file path, and decode/1 — which takes in file path to QRCode image and decodes to get original data.

Let’s edit lib/elixir_python_qrcode.ex

#lib/elixir_python_qrcode.ex

defmodule ElixirPythonQrcode do@moduledoc """Documentation for ElixirPythonQrcode."""alias ElixirPythonQrcode.Helper

def encode(data, file_path) docall_python(:qrcode, :encode, [data, file_path])end

def decode(file_path) docall_python(:qrcode, :decode, [file_path])end

defp default_instance() do#Load all modules in our priv/python directorypath = [:code.priv_dir(:elixir_python_qrcode), "python"]|> Path.join()Helper.python_instance(to_charlist(path))end

wrapper function to call python functions using

default python instance

defp call_python(module, function, args \\ []) dodefault_instance()|> Helper.call_python(module, function, args)end

end

5. Now let’s create our python module

Create priv/python/qrcode.py module that will contain our python functions. These functions handle the actual QRCode generation and decoding.

#priv/python/qrcode.py

def decode(file_path):import qrtoolsqr = qrtools.QR()qr.decode(file_path)return qr.data

def encode(data, file_path):import qrtoolsqr = qrtools.QR(data.encode("utf-8"))return qr.encode(filename=file_path)

6. Let’s test our code.

$ iex -S mixiex(1)> ElixirPythonQrcode.encode("Some test to encode", "qrcode.png")0iex(2)> ElixirPythonQrcode.decode("qrcode.png")'Some test to encode'iex(3)> ElixirPythonQrcode.decode("qrcode.png") |> to_string()"Some test to encode"

That’s it. Check out the github repo.

Happy coding!

Check out Part II for Asynchronous communication between Elixir and Python


Published by HackerNoon on 2017/09/09