class RubyPython::Interpreter

An instance of this class represents information about a particular Python interpreter.

This class represents the current Python interpreter. A class that represents a Python executable.

End users may get the instance that represents the current running Python interpreter (from RubyPython.python), but should not directly instantiate this class.

Attributes

library[R]

The Python library.

python[R]

The name of the Python executable that is used. This is the value of 'sys.executable' for the Python interpreter provided in :python_exe or 'python' if it is not provided.

On Mac OS X Lion (10.7), this value is '/usr/bin/python' for 'python'.

sys_prefix[R]

The system prefix for the Python interpreter. This is the value of 'sys.prefix'.

version[R]

The version of the Python interpreter. This is a decimalized version of 'sys.version_info' (such that Python 2.7.1 is reported as '2.7').

version_name[R]

The basename of the Python interpreter with a version number. This is mostly an intermediate value used to find the shared Python library, but /usr/bin/python is often a link to /usr/bin/python2.7 so it may be of value. Note that this does not include the full path to the interpreter.

Public Class Methods

new(options = {}) click to toggle source

Create a new instance of an Interpreter instance describing a particular Python executable and shared library.

Expects a hash that matches the configuration options provided to RubyPython.start; currently only one value is recognized in that hash:

  • :python_exe: Specifies the name of the python executable to run.

# File lib/rubypython/interpreter.rb, line 38
def initialize(options = {})
  @python_exe = options[:python_exe]
  # Windows: 'C:\\Python27\python.exe'
  # Mac OS X: '/usr/bin/

  # The default interpreter might be python3 on some systems
  rc, majorversion = runpy "import sys; print(sys.version_info[0])"
  if majorversion == "3"
    warn "The python interpreter is python 3, switching to python2"
    @python_exe = "python2"
  end

  rc, @python     = runpy "import sys; print sys.executable"
  if rc.exitstatus.nonzero?
    raise RubyPython::InvalidInterpreter, "An invalid interpreter was specified."
  end
  rc, @version    = runpy "import sys; print '%d.%d' % sys.version_info[:2]"
  rc, @sys_prefix = runpy "import sys; print sys.prefix"

  if ::FFI::Platform.windows?
    flat_version  = @version.tr('.', '')
    basename      = File.basename(@python, '.exe')

    if basename =~ /(?:#{@version}|#{flat_version})$/
      @version_name = basename
    else
      @version_name = "#{basename}#{flat_version}"
    end
  else
    basename = File.basename(@python)
    if basename =~ /#{@version}/
      @version_name = basename
    elsif basename.end_with?("2")
      @version_name = "#{basename[0..-2]}#{@version}"
    else
      @version_name = "#{basename}#{@version}"
    end
  end

  @library = find_python_lib
end

Public Instance Methods

==(other) click to toggle source

Compare the current Interpreter to the provided Interpreter or configuration hash. A configuration hash will be converted to an Interpreter object before being compared. :python_exe basename is used. If comparing against another Interpreter object, the Interpreter basename and version are used.

# File lib/rubypython/interpreter.rb, line 23
def ==(other)
  other = self.class.new(other) if other.kind_of? Hash
  return false unless other.kind_of? self.class
  (self.version == other.version) && (self.version_name == other.version_name)
end
debug_s(format = nil) click to toggle source
# File lib/rubypython/interpreter.rb, line 222
  def debug_s(format = nil)
    system = ""
    system << "windows " if ::FFI::Platform.windows?
    system << "mac " if ::FFI::Platform.mac?
    system << "unix " if ::FFI::Platform.unix?
    system << "unknown " if system.empty?

    case format
    when :report
      s = <<-EOS
python_exe:   #{@python_exe}
python:       #{@python}
version:      #{@version}
sys_prefix:   #{@sys_prefix}
version_name: #{@version_name}
platform:     #{system.chomp}
library:      #{@library.inspect}
  libbase:    #{@libbase}
  libext:     #{@libext}
  libname:    #{@libname}
  locations:  #{@locations.inspect}
      EOS
    else
      s = "#<#{self.class}: "
      s << "python_exe=#{@python_exe.inspect} "
      s << "python=#{@python.inspect} "
      s << "version=#{@version.inspect} "
      s << "sys_prefix=#{@sys_prefix.inspect} "
      s << "version_name=#{@version_name.inspect} "
      s << system
      s << "library=#{@library.inspect} "
      s << "libbase=#{@libbase.inspect} "
      s << "libext=#{@libext.inspect} "
      s << "libname=#{@libname.inspect} "
      s << "locations=#{@locations.inspect}"
    end

    s
  end
inspect(debug = false) click to toggle source
# File lib/rubypython/interpreter.rb, line 212
def inspect(debug = false)
  if debug
    debug_s
  elsif @python
    "#<#{self.class}: #{python} v#{version} #{sys_prefix} #{version_name}>"
  else
    "#<#{self.class}: invalid interpreter>"
  end
end
valid?() click to toggle source
# File lib/rubypython/interpreter.rb, line 163
def valid?
  if @python.nil? or @python.empty?
    false
  elsif @library.nil? or @library.empty?
    false
  else
    true
  end
end

Private Instance Methods

find_python_lib() click to toggle source
# File lib/rubypython/interpreter.rb, line 80
def find_python_lib
  # By default, the library name will be something like
  # libpython2.6.so, but that won't always work.
  @libbase = "#{::FFI::Platform::LIBPREFIX}#{@version_name}"
  @libext = ::FFI::Platform::LIBSUFFIX
  @libname = "#{@libbase}.#{@libext}"

  # We may need to look in multiple locations for Python, so let's
  # build this as an array.
  @locations = [ File.join(@sys_prefix, "lib", @libname) ]

  if ::FFI::Platform.mac?
    # On the Mac, let's add a special case that has even a different
    # @libname. This may not be fully useful on future versions of OS
    # X, but it should work on 10.5 and 10.6. Even if it doesn't, the
    # next step will (/usr/lib/libpython<version>.dylib is a symlink
    # to the correct location).
    @locations << File.join(@sys_prefix, "Python")
    # Let's also look in the location that was originally set in this
    # library:
    File.join(@sys_prefix, "lib", "#{@realname}", "config", @libname)
  end

  if ::FFI::Platform.unix?
    # On Unixes, let's look in some standard alternative places, too.
    # Just in case. Some Unixes don't include a .so symlink when they
    # should, so let's look for the base cases of .so.1 and .so.1.0, too.
    [ @libname, "#{@libname}.1", "#{@libname}.1.0" ].each do |name|
      if ::FFI::Platform::ARCH != 'i386'
        @locations << File.join("/opt/local/lib64", name)
        @locations << File.join("/opt/lib64", name)
        @locations << File.join("/usr/local/lib64", name)
        @locations << File.join("/usr/lib64", name)
        @locations << File.join("/usr/lib/x86_64-linux-gnu", name)
      end
      @locations << File.join("/opt/local/lib", name)
      @locations << File.join("/opt/lib", name)
      @locations << File.join("/usr/local/lib", name)
      @locations << File.join("/usr/lib", name)
    end
  end

  if ::FFI::Platform.windows?
    # On Windows, the appropriate DLL is usually be found in
    # %SYSTEMROOT%\system or %SYSTEMROOT%\system32; as a fallback we'll
    # use C:\Windows\system{,32} as well as the install directory and the
    # install directory + libs.
    system_root = File.expand_path(ENV['SYSTEMROOT']).gsub(/\\/, '/')
    @locations << File.join(system_root, 'system', @libname)
    @locations << File.join(system_root, 'system32', @libname)
    @locations << File.join("C:/WINDOWS", "System", @libname)
    @locations << File.join("C:/WINDOWS", "System32", @libname)
    @locations << File.join(sys_prefix, @libname)
    @locations << File.join(sys_prefix, 'libs', @libname)
    @locations << File.join(system_root, "SysWOW64", @libname)
    @locations << File.join("C:/WINDOWS", "SysWOW64", @libname)
  end

  # Let's add alternative extensions; again, just in case.
  @locations.dup.each do |location|
    path = File.dirname(location)
    base = File.basename(location, ".#{@libext}")
    @locations << File.join(path, "#{base}.so")    # Standard Unix
    @locations << File.join(path, "#{base}.dylib") # Mac OS X
    @locations << File.join(path, "#{base}.dll")   # Windows
  end

  # Remove redundant locations
  @locations.uniq!

  library = nil

  @locations.each do |location|
    if File.exists? location
      library = location
      break
    end
  end

  library
end
infect!(mod) click to toggle source

Infects the provided module with the Python FFI. Once a single module has been infected, the infect! method is removed from RubyPython::Interpreter.

# File lib/rubypython/python.rb, line 20
def infect!(mod)
  self.class.class_eval do
    undef :infect!
  end

  mod.extend ::FFI::Library
  # FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL
  mod.ffi_lib_flags :lazy, :global
  mod.ffi_lib self.library

  # This class is a little bit of a hack to extract the address of
  # global structs. If someone knows a better way please let me know.
  mod.module_eval do
    self.const_set :DummyStruct, Class.new(::FFI::Struct)
    self::DummyStruct.layout :dummy_var, :int

    self.const_set(:PY_FILE_INPUT, 257)
    self.const_set(:PY_EVAL_INPUT, 258)
    self.const_set(:METH_VARARGS, 0x0001)

    # Function methods & constants
    attach_function :PyCFunction_New, [:pointer, :pointer], :pointer
    callback :PyCFunction, [:pointer, :pointer], :pointer

    attach_function :PyRun_String, [:string, :int, :pointer, :pointer], :pointer
    attach_function :PyRun_SimpleString, [:string], :pointer
    attach_function :Py_CompileString, [:string, :string, :int], :pointer
    attach_function :PyEval_EvalCode, [:pointer, :pointer, :pointer], :pointer
    attach_function :PyErr_SetString, [:pointer, :string], :void

    # Python interpreter startup and shutdown
    attach_function :Py_IsInitialized, [], :int
    attach_function :Py_Initialize, [], :void
    attach_function :Py_Finalize, [], :void

    # Module methods
    attach_function :PyImport_ImportModule, [:string], :pointer

    # Object Methods
    attach_function :PyObject_HasAttrString, [:pointer, :string], :int
    attach_function :PyObject_GetAttrString, [:pointer, :string], :pointer
    attach_function :PyObject_SetAttrString, [:pointer, :string, :pointer], :int
    attach_function :PyObject_Dir, [:pointer], :pointer

    attach_function :PyObject_Compare, [:pointer, :pointer], :int

    attach_function :PyObject_Call, [:pointer, :pointer, :pointer], :pointer
    attach_function :PyObject_CallObject, [:pointer, :pointer], :pointer
    attach_function :PyCallable_Check, [:pointer], :int

    ### Python To Ruby Conversion
    # String Methods
    attach_function :PyString_AsString, [:pointer], :string
    attach_function :PyString_FromString, [:string], :pointer
    attach_function :PyString_AsStringAndSize, [:pointer, :pointer, :pointer], :int
    attach_function :PyString_FromStringAndSize, [:buffer_in, :ssize_t], :pointer

    # List Methods
    attach_function :PyList_GetItem, [:pointer, :int], :pointer
    attach_function :PyList_Size, [:pointer], :int
    attach_function :PyList_New, [:int], :pointer
    attach_function :PyList_SetItem, [:pointer, :int, :pointer], :void

    # Integer Methods
    attach_function :PyInt_AsLong, [:pointer], :long
    attach_function :PyInt_FromLong, [:long], :pointer

    attach_function :PyLong_AsLong, [:pointer], :long
    attach_function :PyLong_FromLongLong, [:long_long], :pointer

    # Float Methods
    attach_function :PyFloat_AsDouble, [:pointer], :double
    attach_function :PyFloat_FromDouble, [:double], :pointer

    # Tuple Methods
    attach_function :PySequence_List, [:pointer], :pointer
    attach_function :PyList_AsTuple, [:pointer], :pointer
    attach_function :PyTuple_Pack, [:int, :varargs], :pointer

    # Dict/Hash Methods
    attach_function :PyDict_Next, [:pointer, :pointer, :pointer, :pointer], :int
    attach_function :PyDict_New, [], :pointer
    attach_function :PyDict_SetItem, [:pointer, :pointer, :pointer], :int
    attach_function :PyDict_Contains, [:pointer, :pointer], :int
    attach_function :PyDict_GetItem, [:pointer, :pointer], :pointer

    # Error Methods
    attach_variable :PyExc_Exception, self::DummyStruct.by_ref
    attach_variable :PyExc_StopIteration, self::DummyStruct.by_ref
    attach_function :PyErr_SetNone, [:pointer], :void
    attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
    attach_function :PyErr_Occurred, [], :pointer
    attach_function :PyErr_Clear, [], :void

    # Reference Counting
    attach_function :Py_IncRef, [:pointer], :void
    attach_function :Py_DecRef, [:pointer], :void

    # Type Objects
    # attach_variable :PyBaseObject_Type, self::DummyStruct.by_value # built-in 'object'
    # attach_variable :PyBaseString_Type, self::DummyStruct.by_value
    # attach_variable :PyBool_Type, self::DummyStruct.by_value
    # attach_variable :PyBuffer_Type, self::DummyStruct.by_value
    # attach_variable :PyByteArrayIter_Type, self::DummyStruct.by_value
    # attach_variable :PyByteArray_Type, self::DummyStruct.by_value
    attach_variable :PyCFunction_Type, self::DummyStruct.by_value
    # attach_variable :PyCObject_Type, self::DummyStruct.by_value
    # attach_variable :PyCallIter_Type, self::DummyStruct.by_value
    # attach_variable :PyCapsule_Type, self::DummyStruct.by_value
    # attach_variable :PyCell_Type, self::DummyStruct.by_value
    # attach_variable :PyClassMethod_Type, self::DummyStruct.by_value
    attach_variable :PyClass_Type, self::DummyStruct.by_value
    # attach_variable :PyCode_Type, self::DummyStruct.by_value
    # attach_variable :PyComplex_Type, self::DummyStruct.by_value
    # attach_variable :PyDictItems_Type, self::DummyStruct.by_value
    # attach_variable :PyDictIterItem_Type, self::DummyStruct.by_value
    # attach_variable :PyDictIterKey_Type, self::DummyStruct.by_value
    # attach_variable :PyDictIterValue_Type, self::DummyStruct.by_value
    # attach_variable :PyDictKeys_Type, self::DummyStruct.by_value
    # attach_variable :PyDictProxy_Type, self::DummyStruct.by_value
    # attach_variable :PyDictValues_Type, self::DummyStruct.by_value
    attach_variable :PyDict_Type, self::DummyStruct.by_value
    # attach_variable :PyEllipsis_Type, self::DummyStruct.by_value
    # attach_variable :PyEnum_Type, self::DummyStruct.by_value
    # attach_variable :PyFile_Type, self::DummyStruct.by_value
    attach_variable :PyFloat_Type, self::DummyStruct.by_value
    # attach_variable :PyFrame_Type, self::DummyStruct.by_value
    # attach_variable :PyFrozenSet_Type, self::DummyStruct.by_value
    attach_variable :PyFunction_Type, self::DummyStruct.by_value
    # attach_variable :PyGen_Type, self::DummyStruct.by_value
    # attach_variable :PyGetSetDescr_Type, self::DummyStruct.by_value
    # attach_variable :PyInstance_Type, self::DummyStruct.by_value
    attach_variable :PyInt_Type, self::DummyStruct.by_value
    attach_variable :PyList_Type, self::DummyStruct.by_value
    attach_variable :PyLong_Type, self::DummyStruct.by_value
    # attach_variable :PyMemberDescr_Type, self::DummyStruct.by_value
    # attach_variable :PyMemoryView_Type, self::DummyStruct.by_value
    attach_variable :PyMethod_Type, self::DummyStruct.by_value
    # attach_variable :PyModule_Type, self::DummyStruct.by_value
    # attach_variable :PyNullImporter_Type, self::DummyStruct.by_value
    # attach_variable :PyProperty_Type, self::DummyStruct.by_value
    # attach_variable :PyRange_Type, self::DummyStruct.by_value
    # attach_variable :PyReversed_Type, self::DummyStruct.by_value
    # attach_variable :PySTEntry_Type, self::DummyStruct.by_value
    # attach_variable :PySeqIter_Type, self::DummyStruct.by_value
    # attach_variable :PySet_Type, self::DummyStruct.by_value
    # attach_variable :PySlice_Type, self::DummyStruct.by_value
    # attach_variable :PyStaticMethod_Type, self::DummyStruct.by_value
    attach_variable :PyString_Type, self::DummyStruct.by_value
    # attach_variable :PySuper_Type, self::DummyStruct.by_value # built-in 'super'
    # attach_variable :PyTraceBack_Type, self::DummyStruct.by_value
    attach_variable :PyTuple_Type, self::DummyStruct.by_value
    attach_variable :PyType_Type, self::DummyStruct.by_value
    # attach_variable :PyUnicode_Type, self::DummyStruct.by_value
    # attach_variable :PyWrapperDescr_Type, self::DummyStruct.by_value

    attach_variable :Py_TrueStruct, :_Py_TrueStruct, self::DummyStruct.by_value
    attach_variable :Py_ZeroStruct, :_Py_ZeroStruct, self::DummyStruct.by_value
    attach_variable :Py_NoneStruct, :_Py_NoneStruct, self::DummyStruct.by_value

    # This is an implementation of the basic structure of a Python PyObject
    # struct. The C struct is actually much larger, but since we only access
    # the first two data members via FFI and always deal with struct
    # pointers there is no need to mess around with the rest of the object.
    self.const_set :PyObjectStruct, Class.new(::FFI::Struct)
    self::PyObjectStruct.layout :ob_refcnt, :ssize_t,
                                :ob_type, :pointer

    # This struct is used when defining Python methods.
    self.const_set :PyMethodDef, Class.new(::FFI::Struct)
    self::PyMethodDef.layout :ml_name, :pointer,
                             :ml_meth, :PyCFunction,
                             :ml_flags, :int,
                             :ml_doc, :pointer
  end

end
runpy(command) click to toggle source

Run a Python command-line command.

# File lib/rubypython/interpreter.rb, line 200
def runpy(command)
  i = @python || @python_exe || 'python'
  if ::FFI::Platform.windows?
    o = %x(#{i} -c "#{command}" 2> NUL:)
  else
    o = %x(#{i} -c "#{command}" 2> /dev/null)
  end

  [ $?, o.chomp ]
end