# 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 module RubyPython VERSION = '0.6' end require 'rubypython/blankobject' require 'rubypython/interpreter' require 'rubypython/python' require 'rubypython/pythonerror' require 'rubypython/pyobject' require 'rubypython/rubypyproxy' require 'rubypython/pymainclass' require 'rubypython/pygenerator' require 'rubypython/tuple' require 'thread' module RubyPython class << self ## # :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 def legacy_mode=(value) warn_legacy_mode_deprecation unless defined? @legacy_mode @legacy_mode = value end def legacy_mode unless defined? @legacy_mode warn_legacy_mode_deprecation @legacy_mode = nil end @legacy_mode end def legacy_mode? @legacy_mode = nil unless defined? @legacy_mode @legacy_mode end private :legacy_mode? def warn_legacy_mode_deprecation warn "RubyPython's Legacy Mode is deprecated and will be removed after version #{VERSION}." end private :warn_legacy_mode_deprecation ## 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 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 # 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. def stop if defined? Python.Py_IsInitialized and Python.Py_IsInitialized != 0 Python.Py_Finalize notify :stop true else false end end # 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. 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 # 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 } def session(options = {}) start(options) yield ensure stop end # 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 } def run(options = {}, &block) start(options) self.module_eval(&block) ensure stop end # Starts the \Python interpreter for a # {virtualenv}[http://pypi.python.org/pypi/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. def start_from_virtualenv(virtualenv) result = start(:python_exe => File.join(virtualenv, "bin", "python")) activate_virtualenv result end # Returns an object describing the active Python interpreter. Returns # +nil+ if there is no active interpreter. def python if self.const_defined? :Runtime self::Runtime else nil end end # Used to activate the virtualenv. def activate_virtualenv imp = import("imp") imp.load_source("activate_this", File.join(File.dirname(RubyPython::Runtime.python), "activate_this.py")) end private :activate_virtualenv def add_observer(object) @observers ||= [] @observers << object true end private :add_observer def notify(status) @observers ||= [] @observers.each do |o| next if nil === o o.__send__ :python_interpreter_update, status end end private :notify end add_observer PyMain add_observer Operators add_observer PyObject::AutoPyPointer end