How to run Ruby code from Python (Python-Ruby bridge)

There are several solutions if you need to run Ruby code from Python. It may be useful when you find an interesting Ruby module which has no equivalent in Python.

Solution 1: run a Ruby script via os.system() or subprocess.Popen()

This is the simplest solution: just run the Ruby script as an external process, using either os.system() or subprocess.Popen(). The major drawback is that it launches a new Ruby interpreter at each call, so performance is very poor if you need to call it several times.

Solution 2: translate Ruby code to Python

Of course the best solution would be to have an automated translation from one language to the other. For now I haven't found any tool for this. Please leave a comment if you know one.

However, a promising alternative is proposed by why: instead on attempting to translate source code, unholy (http://github.com/whymirror/unholy) is able to translate Ruby bytecode to Python bytecode.

Solution 3: use XML-RPC

Both Python and Ruby support XML-RPC natively, so this is an interesting solution to call Ruby functions and classes directly from Python. XML-RPC handles simple data types natively, however more complex classes have to be translated into standard objects. Moreover, the Ruby script must run has a server providing an XML-RPC interface for the Python script running as a client. There are also a few security issues because a XML-RPC server is available to any other process, and exchanged data may be sniffed (unless you use HTTPS and authentication). Finally, XML-RPC is based on XML and HTTP so this may be too much overhead unless the Ruby code is not called too often.

rython (http://pypi.python.org/pypi/rython/) seems very useful to create the XML-RPC interface and to launch the Ruby server automatically. It almost provides a transparent way to call Ruby functions from Python.

Solution 4: use pipes

After some time looking for solutions I figured out that it was also possible to use a brand new technology to interconnect Python and Ruby: pipes! This advanced lightweight and secure exchange protocol should be supported on the latest versions of your favorite operating systems. ;-)

Example

Here is a simple example. First, a Ruby script named "slave.rb" will receive commands using its standard input (STDIN). Each line is executed as Ruby source code using eval(), and the result is printed on the standard output. A special line containing "[end]" is printed afterwards to indicate that the script is ready to receive another command. It is then necessary to "flush" the standard output (STDOUT) to avoid any buffering issue (else the calling script will wait indefinitely).

slave.rb
# read command from standard input:
while cmd = STDIN.gets
  # remove whitespaces:
  cmd.chop!
  # if command is "exit", terminate:
  if cmd == "exit"
    break
  else
    # else evaluate command, send result to standard output:
    print eval(cmd),"\n"
    # and append [end] so that master knows it's the last line:
    print "[end]\n"
    # flush stdout to avoid buffering issues:
    STDOUT.flush
  end
end

Second, the following Python script "master.py" launches the Ruby script, then uses pipes to send data to its standard input and to receive results from its output.

master.py
from subprocess import Popen, PIPE, STDOUT

print 'launching slave process...'
slave = Popen(['ruby', 'slave.rb'], stdin=PIPE, stdout=PIPE, stderr=STDOUT)

while True:
    # read user input, expression to be evaluated:
    line = raw_input('Enter expression or exit:')
    # write that line to slave's stdin
    slave.stdin.write(line+'\n')
    # result will be a list of lines:
    result = []
    # read slave output line by line, until we reach "[end]"
    while True:
        # check if slave has terminated:
        if slave.poll() is not None:
            print 'slave has terminated.'
            exit()
        # read one line, remove newline chars and trailing spaces:
        line = slave.stdout.readline().rstrip()
        #print 'line:', line
        if line == '[end]':
            break
        result.append(line)
    print 'result:'
    print '\n'.join(result)

 

This example has been tested successfully on Windows with Python 2.6 and Ruby 1.8.6, but it should work on other systems as well. Here is some sample output:

C:\test>master.py
launching slave process...
Enter expression or exit:3*4
result:
12
Enter expression or exit:7.type result:
Fixnum
Enter expression or exit:3.times { print "hello from Ruby!\n" }
result:
hello from Ruby!
hello from Ruby!
hello from Ruby!
3
Enter expression or exit:exit slave has terminated.

Of course this example is far from complete because it does not handle any error. However, it shows how easily Python and Ruby can be bridged for simple tasks.

This solution should provide better performance than using XML-RPC or than launching the Ruby interpreter at each call.

And of course you may use the same solution to bridge Python with any other language, as long as you can have a minimal control over standard input and output (e.g. you need at least to flush stdout).

The other way around: running Python code from Ruby

There are at least two existing solutions to run Python code from Ruby, both based on embedding the Python interpreter inside Ruby: