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 """
- 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
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