Module: RubyPython

Defined in:
lib/rubypython.rb,
lib/rubypython.rb,
lib/rubypython/type.rb,
lib/rubypython/tuple.rb,
lib/rubypython/python.rb,
lib/rubypython/pygenerator.rb,
lib/rubypython/pymainclass.rb,
lib/rubypython/rubypyproxy.rb

Overview

RubyPython is a bridge between the Ruby and Python interpreters. It embeds a Python interpreter in the Ruby application’s process using FFI and provides a means for wrapping, converting, and calling Python objects and methods.

Usage

The Python interpreter must be started before the RubyPython bridge is functional. The user can either manually manage the running of the interpreter as shown below, or use the RubyPython.run or RubyPython.session methods to automatically start and stop the interpreter.

RubyPython.start
cPickle = RubyPython.import "cPickle"
puts cPickle.dumps("RubyPython is awesome!").rubify
RubyPython.stop

Defined Under Namespace

Modules: Conversion, LegacyMode, Macros, Operators, Python Classes: BlankObject, Interpreter, InvalidInterpreter, PyEnumerable, PyMainClass, PyObject, PythonError, RubyPyClass, RubyPyInstance, RubyPyModule, RubyPyProxy, Tuple

Constant Summary collapse

VERSION =
'0.6.1'
PyMain =

The accessible instance of PyMainClass.

RubyPython::PyMainClass.instance

Class Method Summary collapse

Class Method Details

.activate_virtualenvObject

Used to activate the virtualenv.



256
257
258
259
260
261
# File 'lib/rubypython.rb', line 256

def activate_virtualenv
  imp = import("imp")
  imp.load_source("activate_this",
                  File.join(File.dirname(RubyPython::Runtime.python),
                  "activate_this.py"))
end

.add_observer(object) ⇒ Object



264
265
266
267
268
# File 'lib/rubypython.rb', line 264

def add_observer(object)
  @observers ||= []
  @observers << object
  true
end

.generatorObject

Creates a Ruby lambda that acts like a Python generator. Uses RubyPython.generator_type and Fiber to work the generator as a coroutine.

Note: This method only exists in the RubyPython if the Fiber exists.



40
41
42
43
44
45
46
47
48
49
# File 'lib/rubypython/pygenerator.rb', line 40

def generator
  return lambda do |*args|
    fib = Fiber.new do
      yield *args
      Python.PyErr_SetNone(Python.PyExc_StopIteration)
      FFI::Pointer::NULL
    end
    generator_type.__call__(lambda { fib.resume })
  end
end

.generator_typeObject

Creates a Python generator object called rubypython_generator that accepts a callback and yields to it.

Note: This method only exists in the RubyPython if the Fiber exists.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/rubypython/pygenerator.rb', line 17

def generator_type
  @generator_type ||= lambda do
    code = <<-EOM
def rubypython_generator(callback):
  while True:
    yield callback()
    EOM

    globals = PyObject.new({ "__builtins__" => PyMain.builtin.pObject, })
    empty_hash = PyObject.new({})
    ptr = Python.PyRun_String(code, Python::PY_FILE_INPUT, globals.pointer, empty_hash.pointer)
    ptr = Python.PyRun_String("rubypython_generator", Python::PY_EVAL_INPUT, globals.pointer, empty_hash.pointer)
    raise PythonError.handle_error if PythonError.error?
    RubyPyProxy.new(PyObject.new(ptr))
  end.call
end

.import(mod_name) ⇒ Object

Import a Python module into the interpreter and return a proxy object for it.

This is the preferred way to gain access to Python objects.

mod_name

The name of the module to import.



159
160
161
162
163
164
165
166
167
168
# File 'lib/rubypython.rb', line 159

def import(mod_name)
  if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
    pModule = Python.PyImport_ImportModule mod_name
    raise PythonError.handle_error if PythonError.error?
    pymod = PyObject.new pModule
    RubyPyModule.new(pymod)
  else
    raise "Python has not been started."
  end
end

.legacy_modeObject



72
73
74
75
76
77
78
# File 'lib/rubypython.rb', line 72

def legacy_mode
  unless defined? @legacy_mode
    warn_legacy_mode_deprecation
    @legacy_mode = nil
  end
  @legacy_mode
end

.legacy_mode=(value) ⇒ Object

:attr_accessor: Controls whether RubyPython is operating in Proxy Mode or Legacy Mode. This behavioural difference is deprecated as of RubyPython 0.6 and will be removed in a subsequent release.

Proxy Mode

By default, legacy_mode is false, meaning that any object returned from a Python function call will be wrapped in a Ruby-Python proxy (an instance of RubyPyProxy or one of its subclasses). This allows Python method calls to be forwarded to the Python object, even if it would otherwise be a native Ruby object.

RubyPython.session do
  string = RubyPython.import 'string'
  ascii_letters = string.ascii_letters
  puts ascii_letters.isalpha # => True
  puts ascii_letters.rubify.isalpha # throws NoMethodError
end

Legacy Mode

If legacy_mode is true, RubyPython automatically tries to convert returned objects to native Ruby object types. If there is no such conversion, the object remains wrapped in RubyPyProxy. This behaviour is the same as RubyPython 0.2 and earlier. This mode is deprecated as of RubyPython 0.6 and will be removed.

RubyPython.legacy_mode = true
RubyPython.session do
  string = RubyPython.import 'string'
  ascii_letters = string.ascii_letters
  puts ascii_letters.isalpha # throws NoMethodError
end


67
68
69
70
# File 'lib/rubypython.rb', line 67

def legacy_mode=(value)
  warn_legacy_mode_deprecation unless defined? @legacy_mode
  @legacy_mode = value
end

.legacy_mode?Boolean

Returns:

  • (Boolean)


80
81
82
83
# File 'lib/rubypython.rb', line 80

def legacy_mode?
  @legacy_mode = nil unless defined? @legacy_mode
  @legacy_mode
end

.notify(status) ⇒ Object



271
272
273
274
275
276
277
# File 'lib/rubypython.rb', line 271

def notify(status)
  @observers ||= []
  @observers.each do |o|
    next if nil === o
    o.__send__ :python_interpreter_update, status
  end
end

.pythonObject

Returns an object describing the active Python interpreter. Returns nil if there is no active interpreter.



247
248
249
250
251
252
253
# File 'lib/rubypython.rb', line 247

def python
  if self.const_defined? :Runtime
    self::Runtime
  else
    nil
  end
end

.run(options = {}, &block) ⇒ Object

Starts the Python interpreter (optionally with options) and executes the provided block in the RubyPython module scope. When the block exits for any reason, the Python interpreter is stopped automatically.

The last executed expression of the block is returned. Be careful that the last expression of the block does not return a RubyPyProxy object, because the proxy object will be invalidated when the interpreter is stopped.

options

Configures the interpreter prior to starting it. Principally used to provide an alternative Python interpreter to start.

NOTE: In the current version of RubyPython, it is possible to change Python interpreters in a single Ruby process execution, but it is strongly discouraged as this may lead to segmentation faults. This feature is highly experimental and may be disabled in the future.

:call-seq: run(options = {}) { block to execute in RubyPython context }



216
217
218
219
220
221
# File 'lib/rubypython.rb', line 216

def run(options = {}, &block)
  start(options)
  self.module_eval(&block)
ensure
  stop
end

.session(options = {}) ⇒ Object

Starts the Python interpreter (optionally with options) and yields to the provided block. When the block exits for any reason, the Python interpreter is stopped automatically.

The last executed expression of the block is returned. Be careful that the last expression of the block does not return a RubyPyProxy object, because the proxy object will be invalidated when the interpreter is stopped.

options

Configures the interpreter prior to starting it. Principally used to provide an alternative Python interpreter to start.

NOTE: In the current version of RubyPython, it is possible to change Python interpreters in a single Ruby process execution, but it is strongly discouraged as this may lead to segmentation faults. This feature is highly experimental and may be disabled in the future.

:call-seq: session(options = {}) { block to execute }



189
190
191
192
193
194
# File 'lib/rubypython.rb', line 189

def session(options = {})
  start(options)
  yield
ensure
  stop
end

.start(options = {}) ⇒ Object

Starts the Python interpreter. One of RubyPython.start, RubyPython.session+, or RubyPython.run must be run before using any Python code. Returns true if the interpreter was started; false otherwise.

options

Configures the interpreter prior to starting it. Principally used to provide an alternative Python interpreter to start.

With no options provided:

RubyPython.start
sys = RubyPython.import 'sys'
p sys.version # => "2.6.6"
RubyPython.stop

With an alternative Python executable:

RubyPython.start(:python_exe => 'python2.7')
sys = RubyPython.import 'sys'
p sys.version # => "2.7.1"
RubyPython.stop


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/rubypython.rb', line 110

def start(options = {})
  Mutex.new.synchronize do
    # Has the Runtime interpreter been defined?
    if self.const_defined?(:Runtime)
      # If this constant is defined, then yes it is. Since it is, let's
      # see if we should print a warning to the user.
      unless Runtime == options
        warn "The Python interpreter has already been loaded from #{Runtime.python} and cannot be changed in this process. Continuing with the current runtime."
      end
    else
      interp = RubyPython::Interpreter.new(options)
      if interp.valid?
        self.const_set(:Runtime, interp)
      else
        raise RubyPython::InvalidInterpreter, "An invalid interpreter was specified."
      end
    end

    unless defined? RubyPython::Python.ffi_libraries
      Runtime.__send__(:infect!, RubyPython::Python)
    end
  end

  return false if RubyPython::Python.Py_IsInitialized != 0
  RubyPython::Python.Py_Initialize
  notify :start
  true
end

.start_from_virtualenv(virtualenv) ⇒ Object

Starts the Python interpreter for a virtualenv virtual environment. Returns true if the interpreter was started.

virtualenv

The root path to the virtualenv-installed Python interpreter.

RubyPython.start_from_virtualenv('/path/to/virtualenv')
sys = RubyPython.import 'sys'
p sys.version # => "2.7.1"
RubyPython.stop

NOTE: In the current version of RubyPython, it is possible to change Python interpreters in a single Ruby process execution, but it is strongly discouraged as this may lead to segmentation faults. This feature is highly experimental and may be disabled in the future.



239
240
241
242
243
# File 'lib/rubypython.rb', line 239

def start_from_virtualenv(virtualenv)
  result = start(:python_exe => File.join(virtualenv, "bin", "python"))
  activate_virtualenv
  result
end

.stopObject

Stops the Python interpreter if it is running. Returns true if the intepreter is stopped. All wrapped Python objects are invalid after invocation of this method. If you need the values within the Python proxy objects, be sure to call RubyPyProxy#rubify on them.



143
144
145
146
147
148
149
150
151
# File 'lib/rubypython.rb', line 143

def stop
  if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0
    Python.Py_Finalize
    notify :stop
    true
  else
    false
  end
end

.Type(name) ⇒ Object

Creates a Ruby class that inherits from a proxied Python object.



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/rubypython/type.rb', line 3

def self.Type(name)
  mod, match, klass = name.rpartition(".")
  pymod = RubyPython.import(mod)
  pyclass = pymod.pObject.getAttr(klass)
  rclass = Class.new(RubyPyProxy) do
    define_method(:initialize) do |*args|
      args = PyObject.convert(*args)
      pTuple = PyObject.buildArgTuple(*args)
      pReturn = pyclass.callObject(pTuple)
      if PythonError.error?
        raise PythonError.handle_error
      end
      @pObject = pReturn
    end
  end
  return rclass
end

.warn_legacy_mode_deprecationObject



86
87
88
# File 'lib/rubypython.rb', line 86

def warn_legacy_mode_deprecation
  warn "RubyPython's Legacy Mode is deprecated and will be removed after version #{VERSION}."
end

.yield(*args) ⇒ Object

Performs a Fiber.yield with the provided arguments, continuing the coroutine execution of the generator.

Note: This method only exists in the RubyPython if the Fiber exists.



56
57
58
# File 'lib/rubypython/pygenerator.rb', line 56

def yield(*args)
  Fiber.yield(*args)
end