Module: RubyPython::Conversion

Defined in:
lib/rubypython/conversion.rb

Overview

Acts as a namespace for methods to bidirectionally convert between native Ruby types and native Python types. Unsupported conversions raise UnsupportedConversion.

The methods in this module should be considered internal implementation to RubyPython as they all return FFI pointers to Python objects.

Defined Under Namespace

Classes: ConversionError, UnsupportedConversion

Class Method Summary collapse

Class Method Details

.ptorDict(pDict) ⇒ Object

Convert an FFI::Pointer to a Python Dictionary (PyDictObject) to a Ruby Hash.



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/rubypython/conversion.rb', line 277

def self.ptorDict(pDict)
  rb_hash = {}

  pos = ::FFI::MemoryPointer.new :ssize_t
  pos.write_int 0
  key = ::FFI::MemoryPointer.new :pointer
  val = ::FFI::MemoryPointer.new :pointer

  while RubyPython::Python.PyDict_Next(pDict, pos, key, val) != 0
    #PyDict_Next sets key and val to borrowed references. We do not care
    #if we are able to convert them to native ruby types, but if we wind up
    #wrapping either in a proxy we better IncRef it to make sure it stays
    #around.
    pKey = key.read_pointer
    pVal = val.read_pointer
    rKey = ptorObject(pKey)
    rVal = ptorObject(pVal)
    RubyPython.Py_IncRef pKey if rKey.kind_of? ::FFI::Pointer
    RubyPython.Py_IncRef pVal if rVal.kind_of? ::FFI::Pointer
    rb_hash[rKey] = rVal
  end

  rb_hash
end

.ptorFloat(pNum) ⇒ Object

Convert an FFI::Pointer to a Python Float (PyFloatObject) to a Ruby Float.



260
261
262
# File 'lib/rubypython/conversion.rb', line 260

def self.ptorFloat(pNum)
  RubyPython::Python.PyFloat_AsDouble(pNum)
end

.ptorInt(pNum) ⇒ Object

Convert an FFI::Pointer to a Python Int (PyIntObject) to a Ruby Fixnum.



247
248
249
# File 'lib/rubypython/conversion.rb', line 247

def self.ptorInt(pNum)
  RubyPython::Python.PyInt_AsLong(pNum)
end

.ptorList(pList) ⇒ Object

Convert an FFI::Pointer to a Python List (PyListObject) to a Ruby Array.



233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/rubypython/conversion.rb', line 233

def self.ptorList(pList)
  rb_array = []
  list_size = RubyPython::Python.PyList_Size(pList)

  list_size.times do |i|
    element = RubyPython::Python.PyList_GetItem(pList, i)
    rObject = ptorObject(element)
    rb_array.push rObject
  end

  rb_array
end

.ptorLong(pNum) ⇒ Object

Convert an FFI::Pointer to a Python Long (PyLongObject) to a Ruby Fixnum. This version does not do overflow checking, but probably should.



253
254
255
256
# File 'lib/rubypython/conversion.rb', line 253

def self.ptorLong(pNum)
  RubyPython::Python.PyLong_AsLong(pNum)
  # TODO Overflow Checking
end

.ptorObject(pObj) ⇒ Object

Converts a pointer to a Python object into a native Ruby type, if possible. If the conversion cannot be done, the Python object will be returned unmodified.

pObj

An FFI::Pointer to a Python object.



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/rubypython/conversion.rb', line 307

def self.ptorObject(pObj)
  if RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyString_Type.to_ptr) != 0
    ptorString pObj
  elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyList_Type.to_ptr) != 0
    ptorList pObj
  elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyInt_Type.to_ptr) != 0
    ptorInt pObj
  elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyLong_Type.to_ptr) != 0
    ptorLong pObj
  elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyFloat_Type.to_ptr) != 0
    ptorFloat pObj
  elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyTuple_Type.to_ptr) != 0
    ptorTuple pObj
  elsif RubyPython::Macros.PyObject_TypeCheck(pObj, RubyPython::Python.PyDict_Type.to_ptr) != 0
    ptorDict pObj
  elsif pObj == RubyPython::Macros.Py_True
    true
  elsif pObj == RubyPython::Macros.Py_False
    false
  elsif pObj == RubyPython::Macros.Py_None
    nil
  else
    pObj
  end
end

.ptorString(pString) ⇒ Object

Convert an FFI::Pointer to a Python String (PyStringObject) to a Ruby String.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/rubypython/conversion.rb', line 208

def self.ptorString(pString)
  #strPtr is a pointer to a pointer to the internal character array.
  #FFI will free strPtr when we are done but the internal array MUST
  #not be modified
  strPtr  = ::FFI::MemoryPointer.new(:pointer)
  sizePtr = ::FFI::MemoryPointer.new(:ssize_t)

  RubyPython::Python.PyString_AsStringAndSize(pString, strPtr, sizePtr)

  size = case ::FFI.find_type(:ssize_t)
         when ::FFI.find_type(:long)
           sizePtr.read_long
         when ::FFI.find_type(:int)
           sizePtr.read_int
         when ::FFI.find_type(:long_long)
           sizePtr.read_long_long
         else
           nil
         end

  strPtr.read_pointer.read_string(size)
end

.ptorTuple(pTuple) ⇒ Object

Convert an FFI::Pointer to a Python Tuple (PyTupleObject) to an instance of RubyPython::Tuple, a subclass of the Ruby Array class.



266
267
268
269
270
271
272
273
# File 'lib/rubypython/conversion.rb', line 266

def self.ptorTuple(pTuple)
  #PySequence_List returns a new list. Since we are only using it as a temporary
  #here, we will have to DecRef it once we are done.
  pList = RubyPython::Python.PySequence_List pTuple
  rArray = ptorList pList
  RubyPython::Python.Py_DecRef pList
  RubyPython::Tuple.tuple(rArray)
end

.rtopArrayToList(rArray) ⇒ Object

Convert a Ruby Array to Python List. Returns an FFI::Pointer to a PyListObject.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rubypython/conversion.rb', line 31

def self.rtopArrayToList(rArray)
  size = rArray.length
  pList = RubyPython::Python.PyList_New size
  if pList.null?
    raise ConversionError.new "Python failed to create list of size #{size}"
  end
  rArray.each_with_index do |el, i|
    # PyList_SetItem steals a reference, but rtopObject creates a new reference
    # So we wind up with giving a new reference to the Python interpreter for every
    # object
    ret = RubyPython::Python.PyList_SetItem pList, i, rtopObject(el)
    raise ConversionError.new "Failed to set item #{el} in array conversion" if ret == -1
  end
  pList
end

.rtopArrayToTuple(rArray) ⇒ Object

Convert a Ruby Array (including the subclass RubyPython::Tuple) to Python tuple. Returns an FFI::Pointer to a PyTupleObject.



49
50
51
52
53
54
55
56
57
# File 'lib/rubypython/conversion.rb', line 49

def self.rtopArrayToTuple(rArray)
  pList = rtopArrayToList(rArray)
  pTuple = RubyPython::Python.PyList_AsTuple(pList)
  RubyPython::Python.Py_DecRef(pList)
  if pTuple.null?
    raise Conversion.new "Python failed to convert an intermediate list of #{rArray} to a tuple"
  end
  pTuple
end

.rtopBigNum(rNum) ⇒ Object

Convert a Ruby Bignum to a Python Long. Returns an FFI::Pointer to a PyLongObject.



94
95
96
97
98
# File 'lib/rubypython/conversion.rb', line 94

def self.rtopBigNum(rNum)
  num = RubyPython::Python.PyLong_FromLongLong(rNum)
  raise ConversionError.new "Failed to convert #{rNum}" if num.null?
  num
end

.rtopFalseObject

Returns a Python False value (equivalent to Ruby’s false). Returns an FFI::Pointer to Py_ZeroStruct.



110
111
112
# File 'lib/rubypython/conversion.rb', line 110

def self.rtopFalse
  RubyPython::Macros.Py_RETURN_FALSE
end

.rtopFixnum(rNum) ⇒ Object

Convert a Ruby Fixnum to a Python Int. Returns an FFI::Pointer to a PyIntObject.



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

def self.rtopFixnum(rNum)
  num = RubyPython::Python.PyInt_FromLong(rNum)
  raise ConversionError.new "Failed to convert #{rNum}" if num.null?
  num
end

.rtopFloat(rNum) ⇒ Object

Convert a Ruby float to a Python Float. Returns an FFI::Pointer to a PyFloatObject.



102
103
104
105
106
# File 'lib/rubypython/conversion.rb', line 102

def self.rtopFloat(rNum)
  num = RubyPython::Python.PyFloat_FromDouble(rNum)
  raise ConversionError.new "Failed to convert #{rNum}" if num.null?
  num
end

.rtopFunction(rObj) ⇒ Object

Convert a Ruby Proc to a Python Function. Returns an FFI::Pointer to a PyCFunction.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/rubypython/conversion.rb', line 134

def self.rtopFunction(rObj)
  proc = ::FFI::Function.new(:pointer, [:pointer, :pointer]) do |p_self, p_args|
    retval = rObj.call(*ptorTuple(p_args))
    pObject = retval.is_a?(RubyPython::RubyPyProxy) ? retval.pObject : RubyPython::PyObject.new(retval)

    # make sure the refcount is >1 when pObject is destroyed
    pObject.xIncref
    pObject.pointer
  end

  defn = RubyPython::Python::PyMethodDef.new
  defn[:ml_name] = ::FFI::MemoryPointer.from_string("RubyPython::Proc::%s" % rObj.object_id)
  defn[:ml_meth] = proc
  defn[:ml_flags] = RubyPython::Python::METH_VARARGS
  defn[:ml_doc] = nil

  return RubyPython::Python.PyCFunction_New(defn, nil)
end

.rtopHash(rHash) ⇒ Object

Convert a Ruby Hash to a Python Dict. Returns an FFI::Pointer to a PyDictObject.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rubypython/conversion.rb', line 61

def self.rtopHash(rHash)
  pDict = RubyPython::Python.PyDict_New
  if pDict.null?
    raise ConversionError.new "Python failed to create new dict"
  end
  rHash.each do |k,v|
    key = rtopObject(k, :key => true)
    value = rtopObject(v)

    # PyDict_SetItem INCREFS both the key and the value passed to it.
    # Since rtopObject already gives us a new reference, this is not necessary.
    # Thus, we decref the passed in objects to balancy things out
    if RubyPython::Python.PyDict_SetItem(pDict, key, value) == -1
      raise ConversionError.new "Python failed to set #{key}, #{value} in dict conversion"
    end

    RubyPython::Python.Py_DecRef key
    RubyPython::Python.Py_DecRef value
  end

  pDict
end

.rtopNoneObject

Returns a Python None value (equivalent to Ruby’s nil). Returns an FFI::Pointer to Py_NoneStruct.



122
123
124
# File 'lib/rubypython/conversion.rb', line 122

def self.rtopNone
  RubyPython::Macros.Py_RETURN_NONE
end

.rtopObject(rObj, is_key = false) ⇒ Object

This will attempt to convert a Ruby object to an equivalent Python native type. Returns an FFI::Pointer to a Python object (the appropriate Py_Object C structure). If the conversion is unsuccessful, will raise UnsupportedConversion.

rObj

A native Ruby object.

is_key

Set to true if the provided Ruby object will be used as a key in a Python dict. (This primarily matters for Array conversion.)



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/rubypython/conversion.rb', line 162

def self.rtopObject(rObj, is_key = false)
  case rObj
  when String
    rtopString rObj
  when RubyPython::Tuple
    rtopArrayToTuple rObj
  when Array
    # If this object is going to be used as a hash key we should make it a
    # tuple instead of a list
    if is_key
      rtopArrayToTuple rObj
    else
      rtopArrayToList rObj
    end
  when Hash
    rtopHash rObj
  when Fixnum
    rtopFixnum rObj
  when Bignum
    rtopBignum rObj
  when Float
    rtopFloat rObj
  when true
    rtopTrue
  when false
    rtopFalse
  when Symbol
    rtopSymbol rObj
  when Proc, Method
    rtopFunction rObj
  when nil
    rtopNone
  when RubyPython::PyObject
    rObj.xIncref
    rObj.pointer
  when RubyPython::RubyPyProxy
    rtopObject(rObj.pObject, is_key)
  when NumRu
    rtopObject(rObj.np_obj)
  else
    raise UnsupportedConversion.new("Unsupported type #{rObj.class} for conversion.")
  end
end

.rtopString(rString) ⇒ Object

Convert a Ruby string to a Python string. Returns an FFI::Pointer to a PyStringObject.



19
20
21
22
23
24
25
26
27
# File 'lib/rubypython/conversion.rb', line 19

def self.rtopString(rString)
  size = rString.respond_to?(:bytesize) ? rString.bytesize : rString.size
  ptr = RubyPython::Python.PyString_FromStringAndSize(rString, size)
  if ptr.null?
    raise ConversionError.new "Python failed to create a string with contents #{rString}"
  else
    ptr
  end
end

.rtopSymbol(rSymbol) ⇒ Object

Convert a Ruby Symbol to a Python String. Returns an FFI::Pointer to a PyStringObject.



128
129
130
# File 'lib/rubypython/conversion.rb', line 128

def self.rtopSymbol(rSymbol)
  rtopString rSymbol.to_s
end

.rtopTrueObject

Returns a Python True value (equivalent to Ruby’s true). Returns an FFI::Pointer to Py_TrueStruct.



116
117
118
# File 'lib/rubypython/conversion.rb', line 116

def self.rtopTrue
  RubyPython::Macros.Py_RETURN_TRUE
end