rubypython 0.2.11 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +0 -0
- data/{History.txt → History.markdown} +34 -28
- data/Manifest.txt +26 -40
- data/PostInstall.txt +2 -1
- data/README.markdown +103 -0
- data/Rakefile +19 -3
- data/lib/rubypython.rb +118 -114
- data/lib/rubypython/blankobject.rb +21 -0
- data/lib/rubypython/conversion.rb +198 -0
- data/lib/rubypython/core_ext/string.rb +7 -0
- data/lib/rubypython/legacy.rb +15 -0
- data/lib/rubypython/macros.rb +47 -0
- data/lib/rubypython/operators.rb +111 -0
- data/lib/rubypython/pymainclass.rb +51 -0
- data/lib/rubypython/pyobject.rb +203 -0
- data/lib/rubypython/python.rb +111 -0
- data/lib/rubypython/pythonerror.rb +78 -0
- data/lib/rubypython/rubypyproxy.rb +214 -0
- data/lib/rubypython/version.rb +4 -3
- data/spec/conversion_spec.rb +66 -0
- data/spec/legacy_spec.rb +22 -0
- data/spec/pymainclass_spec.rb +26 -0
- data/spec/pyobject_spec.rb +264 -0
- data/spec/python_helpers/objects.py +41 -0
- data/spec/pythonerror_spec.rb +43 -0
- data/spec/refcnt_spec.rb +68 -0
- data/spec/rubypyclass_spec.rb +13 -0
- data/spec/rubypyproxy_spec.rb +249 -0
- data/spec/rubypython_spec.rb +62 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +51 -0
- metadata +79 -73
- metadata.gz.sig +0 -0
- data/README.txt +0 -60
- data/ext/rubypython_bridge/cbridge.c +0 -150
- data/ext/rubypython_bridge/cbridge.h +0 -15
- data/ext/rubypython_bridge/config.h +0 -14
- data/ext/rubypython_bridge/extconf.rb +0 -43
- data/ext/rubypython_bridge/ptor.c +0 -242
- data/ext/rubypython_bridge/ptor.h +0 -15
- data/ext/rubypython_bridge/rp_blankobject.c +0 -42
- data/ext/rubypython_bridge/rp_blankobject.h +0 -11
- data/ext/rubypython_bridge/rp_class.c +0 -56
- data/ext/rubypython_bridge/rp_class.h +0 -7
- data/ext/rubypython_bridge/rp_error.c +0 -34
- data/ext/rubypython_bridge/rp_error.h +0 -11
- data/ext/rubypython_bridge/rp_function.c +0 -31
- data/ext/rubypython_bridge/rp_function.h +0 -7
- data/ext/rubypython_bridge/rp_instance.c +0 -164
- data/ext/rubypython_bridge/rp_instance.h +0 -7
- data/ext/rubypython_bridge/rp_module.c +0 -160
- data/ext/rubypython_bridge/rp_module.h +0 -8
- data/ext/rubypython_bridge/rp_object.c +0 -194
- data/ext/rubypython_bridge/rp_object.h +0 -23
- data/ext/rubypython_bridge/rp_util.c +0 -63
- data/ext/rubypython_bridge/rp_util.h +0 -11
- data/ext/rubypython_bridge/rtop.c +0 -212
- data/ext/rubypython_bridge/rtop.h +0 -17
- data/ext/rubypython_bridge/rubypython_bridge.c +0 -125
- data/ext/rubypython_bridge/rubypython_bridge.h +0 -10
- data/lib/rubypython/session.rb +0 -4
- data/lib/rubypython/wrapper_extensions.rb +0 -83
- data/setup.rb +0 -1585
- data/tasks/environment.rake +0 -7
- data/tasks/extconf.rake +0 -13
- data/tasks/extconf/rubypython_bridge.rake +0 -49
- data/test/python_helpers/objects.py +0 -12
- data/test/test.wav +0 -0
- data/test/test_helper.rb +0 -2
- data/test/test_rubypython.rb +0 -215
- data/test/test_rubypython_bridge_extn.rb +0 -133
- data/test/test_session.rb +0 -6
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'rubypython/python'
|
2
|
+
require 'rubypython/macros'
|
3
|
+
require 'rubypython/conversion'
|
4
|
+
require 'ffi'
|
5
|
+
|
6
|
+
module RubyPython
|
7
|
+
#This object is an opaque wrapper around the C PyObject\* type used by the
|
8
|
+
#python C API. This class **should not** be used by the end user. They
|
9
|
+
#should instead make use of the {RubyPyProxy} class and its
|
10
|
+
#subclasses.
|
11
|
+
class PyObject
|
12
|
+
|
13
|
+
#@private
|
14
|
+
#
|
15
|
+
#This class wraps C PyObject\*s so that the Python reference count is
|
16
|
+
#automatically decreased when the Ruby object referencing them
|
17
|
+
#goes out of scope.
|
18
|
+
class AutoPyPointer < FFI::AutoPointer
|
19
|
+
class << self
|
20
|
+
#Keeps track of which objects are associated with the currently running
|
21
|
+
#Python interpreter, so that RubyPython knows not to try to decrease the
|
22
|
+
#reference counts of the others when garbage collecting.
|
23
|
+
attr_accessor :current_pointers
|
24
|
+
|
25
|
+
#When used along with the FFI Library method is executed whenever a
|
26
|
+
#pointer is garbage collected so that cleanup can be done. In our case
|
27
|
+
#we decrease the reference count of the held pointer as long as the
|
28
|
+
#object is still good. There is really no reason the end-user would need
|
29
|
+
#to the use this method directly.
|
30
|
+
def release(pointer)
|
31
|
+
obj_id = pointer.object_id
|
32
|
+
if (Python.Py_IsInitialized != 0) and @current_pointers.delete(obj_id)
|
33
|
+
Python.Py_DecRef pointer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
self.current_pointers = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
#The FFI::Pointer object which represents the Python PyObject\*.
|
42
|
+
attr_reader :pointer
|
43
|
+
|
44
|
+
#@param [FFI::Pointer, other] pointer objects passed in to the constructor
|
45
|
+
# are just assigned to the pointer attribute of the instance. All other
|
46
|
+
# objects are converted via {Conversion.rtopObject} before being assigned.
|
47
|
+
def initialize(rObject)
|
48
|
+
if rObject.kind_of? FFI::AutoPointer
|
49
|
+
new_pointer = FFI::Pointer.new rObject
|
50
|
+
@pointer = AutoPyPointer.new new_pointer
|
51
|
+
xIncref
|
52
|
+
elsif rObject.kind_of? FFI::Pointer
|
53
|
+
@pointer = AutoPyPointer.new rObject
|
54
|
+
else
|
55
|
+
@pointer = AutoPyPointer.new Conversion.rtopObject(rObject)
|
56
|
+
end
|
57
|
+
AutoPyPointer.current_pointers[@pointer.object_id] = true
|
58
|
+
end
|
59
|
+
|
60
|
+
#Attempts to convert the wrapped object to a native ruby type.
|
61
|
+
#@return a ruby version of the wrapped object
|
62
|
+
def rubify
|
63
|
+
Conversion.ptorObject @pointer
|
64
|
+
end
|
65
|
+
|
66
|
+
#Tests whether the wrapped object has a given attribute
|
67
|
+
#@param [String] the name of the attribute to look up
|
68
|
+
#@return [Boolean]
|
69
|
+
def hasAttr(attrName)
|
70
|
+
Python.PyObject_HasAttrString(@pointer, attrName) == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
#Retrieves an object from the wrapped python object
|
74
|
+
#@param [String] the name of attribute to fetch
|
75
|
+
#@return [PyObject] a Ruby wrapper around the fetched attribute
|
76
|
+
def getAttr(attrName)
|
77
|
+
pyAttr = Python.PyObject_GetAttrString @pointer, attrName
|
78
|
+
self.class.new pyAttr
|
79
|
+
end
|
80
|
+
|
81
|
+
#Sets the an attribute of the wrapped Python object
|
82
|
+
#@param [String] attrName the name of of attribute to set
|
83
|
+
#@param [PyObject] rbPyAttr a {PyObject} wrapper around the value we wish to
|
84
|
+
#set the attribute to.
|
85
|
+
#@return [Boolean] returns true if the attribute is sucessfully set.
|
86
|
+
def setAttr(attrName, rbPyAttr)
|
87
|
+
Python.PyObject_SetAttrString(@pointer, attrName, rbPyAttr.pointer) != -1
|
88
|
+
end
|
89
|
+
|
90
|
+
#Calls the wrapped Python object with the supplied arguments.
|
91
|
+
#@param [PyObject] rbPyArgs a {PyObject} wrapping a tuple of the supplied
|
92
|
+
#arguments
|
93
|
+
#@return [PyObject] a {PyObject} wrapper around the returned
|
94
|
+
#object (this may be NULL).
|
95
|
+
def callObject(rbPyArgs)
|
96
|
+
pyReturn = Python.PyObject_CallObject(@pointer, rbPyArgs.pointer)
|
97
|
+
self.class.new pyReturn
|
98
|
+
end
|
99
|
+
|
100
|
+
#Decrease the reference count of the wrapped object
|
101
|
+
#@return [void]
|
102
|
+
def xDecref
|
103
|
+
AutoPyPointer.release(@pointer)
|
104
|
+
@pointer.free
|
105
|
+
end
|
106
|
+
|
107
|
+
#Increase the reference count of the wrapped object
|
108
|
+
#@return [void]
|
109
|
+
def xIncref
|
110
|
+
Python.Py_IncRef @pointer
|
111
|
+
end
|
112
|
+
|
113
|
+
#Tests whether the wrapped object is NULL.
|
114
|
+
def null?
|
115
|
+
@pointer.null?
|
116
|
+
end
|
117
|
+
|
118
|
+
#@return [Number]
|
119
|
+
def cmp(other)
|
120
|
+
Python.PyObject_Compare @pointer, other.pointer
|
121
|
+
end
|
122
|
+
|
123
|
+
#Tests whether the wrapped object is a function or a method. This is not the
|
124
|
+
#same as {#callable?} as many other Python objects are callable.
|
125
|
+
def function_or_method?
|
126
|
+
isFunc = (Macros.PyObject_TypeCheck(@pointer, Python.PyFunction_Type.to_ptr) != 0)
|
127
|
+
isMethod = (Macros.PyObject_TypeCheck(@pointer, Python.PyMethod_Type.to_ptr) != 0)
|
128
|
+
isFunc or isMethod
|
129
|
+
end
|
130
|
+
|
131
|
+
#Is the wrapped object callable?
|
132
|
+
def callable?
|
133
|
+
Python.PyCallable_Check(@pointer) != 0
|
134
|
+
end
|
135
|
+
|
136
|
+
#Tests whether the wrapped object is a Python class (both new and old
|
137
|
+
#style).
|
138
|
+
def class?
|
139
|
+
isClassObj = (Macros.PyObject_TypeCheck(@pointer, Python.PyClass_Type.to_ptr) == 1)
|
140
|
+
isTypeObj = (Macros.PyObject_TypeCheck(@pointer, Python.PyType_Type.to_ptr) == 1)
|
141
|
+
isTypeObj or isClassObj
|
142
|
+
end
|
143
|
+
|
144
|
+
#Manipulates the supplied {PyObject} instance such that it is suitable to
|
145
|
+
#passed to {#callObject}. If `rbObject` is a tuple then the argument passed
|
146
|
+
#in is returned. If it is a list then the list is converted to a tuple.
|
147
|
+
#Otherwise returns a tuple with one element: `rbObject`.
|
148
|
+
#@param [PyObject] rbObject the argment to be turned into a tuple.
|
149
|
+
#@return [PyObject<tuple>]
|
150
|
+
def self.makeTuple(rbObject)
|
151
|
+
pTuple = nil
|
152
|
+
|
153
|
+
if Macros.PyObject_TypeCheck(rbObject.pointer, Python.PyList_Type.to_ptr) != 0
|
154
|
+
pTuple = Python.PySequence_Tuple(rbObject.pointer)
|
155
|
+
elsif Macros.PyObject_TypeCheck(rbObject.pointer, Python.PyTuple_Type.to_ptr) != 0
|
156
|
+
pTuple = rbObject.pointer
|
157
|
+
else
|
158
|
+
pTuple = Python.PyTuple_Pack(1, :pointer, rbObject.pointer)
|
159
|
+
end
|
160
|
+
|
161
|
+
self.new pTuple
|
162
|
+
end
|
163
|
+
|
164
|
+
#Wraps up the supplied arguments in Python list.
|
165
|
+
#@return [PyObject<list>]
|
166
|
+
def self.newList(*args)
|
167
|
+
rbList = self.new Python.PyList_New(args.length)
|
168
|
+
|
169
|
+
args.each_with_index do |el, i|
|
170
|
+
Python.PyList_SetItem rbList.pointer, i, el.pointer
|
171
|
+
end
|
172
|
+
|
173
|
+
rbList
|
174
|
+
end
|
175
|
+
|
176
|
+
#Converts the supplied arguments to PyObject instances.
|
177
|
+
#@return [Array<PyObject>]
|
178
|
+
def self.convert(*args)
|
179
|
+
args.map! do |arg|
|
180
|
+
if arg.kind_of? PyObject
|
181
|
+
arg
|
182
|
+
elsif arg.kind_of? RubyPyProxy
|
183
|
+
arg.pObject
|
184
|
+
else
|
185
|
+
PyObject.new arg
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
#Takes an array of wrapped Python objects and wraps them in a tuple such
|
191
|
+
#that they may be passed to {#callObject}.
|
192
|
+
#@param [Array<PyObject>] args the arguments to be inserted into the tuple.
|
193
|
+
#@return [PyObject<tuple>]
|
194
|
+
def self.buildArgTuple(*args)
|
195
|
+
pList = PyObject.newList(*args)
|
196
|
+
pTuple = PyObject.makeTuple(pList)
|
197
|
+
pList.xDecref
|
198
|
+
pTuple
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module RubyPython
|
5
|
+
#This module provides access to the Python C API functions via the Ruby ffi
|
6
|
+
#gem. Documentation for these functions may be found [here](http://docs.python.org/c-api/). Likewise the FFI gem documentation may be found [here](http://rdoc.info/projects/ffi/ffi).
|
7
|
+
module Python
|
8
|
+
extend FFI::Library
|
9
|
+
PYTHON_VERSION = Open3.popen3("python --version") { |i,o,e| e.read}.chomp.split[1].to_f
|
10
|
+
PYTHON_NAME = "python#{PYTHON_VERSION}"
|
11
|
+
LIB_NAME = "lib#{PYTHON_NAME}"
|
12
|
+
LIB_EXT = FFI::Platform::LIBSUFFIX
|
13
|
+
LIB = File.join(`python -c "import sys; print(sys.prefix)"`.chomp,
|
14
|
+
"lib", "#{PYTHON_NAME}", "config", "#{LIB_NAME}.#{LIB_EXT}")
|
15
|
+
@ffi_libs = [FFI::DynamicLibrary.open(LIB, FFI::DynamicLibrary::RTLD_LAZY|FFI::DynamicLibrary::RTLD_GLOBAL)]
|
16
|
+
|
17
|
+
#The class is a little bit of a hack to extract the address of global
|
18
|
+
#structs. If someone knows a better way please let me know.
|
19
|
+
class DummyStruct < FFI::Struct
|
20
|
+
layout :dummy_var, :int
|
21
|
+
end
|
22
|
+
|
23
|
+
#Python interpreter startup and shutdown
|
24
|
+
attach_function :Py_IsInitialized, [], :int
|
25
|
+
attach_function :Py_Initialize, [], :void
|
26
|
+
attach_function :Py_Finalize, [], :void
|
27
|
+
|
28
|
+
#Module methods
|
29
|
+
attach_function :PyImport_ImportModule, [:string], :pointer
|
30
|
+
|
31
|
+
#Object Methods
|
32
|
+
attach_function :PyObject_HasAttrString, [:pointer, :string], :int
|
33
|
+
attach_function :PyObject_GetAttrString, [:pointer, :string], :pointer
|
34
|
+
attach_function :PyObject_SetAttrString, [:pointer, :string, :pointer], :int
|
35
|
+
|
36
|
+
attach_function :PyObject_Compare, [:pointer, :pointer], :int
|
37
|
+
|
38
|
+
attach_function :PyObject_CallObject, [:pointer, :pointer], :pointer
|
39
|
+
attach_function :PyCallable_Check, [:pointer], :int
|
40
|
+
|
41
|
+
###Python To Ruby Conversion
|
42
|
+
#String Methods
|
43
|
+
attach_function :PyString_AsString, [:pointer], :string
|
44
|
+
attach_function :PyString_FromString, [:string], :pointer
|
45
|
+
|
46
|
+
#List Methods
|
47
|
+
attach_function :PyList_GetItem, [:pointer, :int], :pointer
|
48
|
+
attach_function :PyList_Size, [:pointer], :int
|
49
|
+
attach_function :PyList_New, [:int], :pointer
|
50
|
+
attach_function :PyList_SetItem, [:pointer, :int, :pointer], :void
|
51
|
+
|
52
|
+
#Integer Methods
|
53
|
+
attach_function :PyInt_AsLong, [:pointer], :long
|
54
|
+
attach_function :PyInt_FromLong, [:long], :pointer
|
55
|
+
|
56
|
+
attach_function :PyLong_AsLong, [:pointer], :long
|
57
|
+
attach_function :PyLong_FromLong, [:pointer], :long
|
58
|
+
|
59
|
+
#Float Methods
|
60
|
+
attach_function :PyFloat_AsDouble, [:pointer], :double
|
61
|
+
attach_function :PyFloat_FromDouble, [:double], :pointer
|
62
|
+
|
63
|
+
#Tuple Methods
|
64
|
+
attach_function :PySequence_List, [:pointer], :pointer
|
65
|
+
attach_function :PySequence_Tuple, [:pointer], :pointer
|
66
|
+
attach_function :PyTuple_Pack, [:int, :varargs], :pointer
|
67
|
+
|
68
|
+
#Dict/Hash Methods
|
69
|
+
attach_function :PyDict_Next, [:pointer, :pointer, :pointer, :pointer], :int
|
70
|
+
attach_function :PyDict_New, [], :pointer
|
71
|
+
attach_function :PyDict_SetItem, [:pointer, :pointer, :pointer], :int
|
72
|
+
attach_function :PyDict_Contains, [:pointer, :pointer], :int
|
73
|
+
attach_function :PyDict_GetItem, [:pointer, :pointer], :pointer
|
74
|
+
|
75
|
+
#Error Methods
|
76
|
+
attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
|
77
|
+
attach_function :PyErr_Occurred, [], :pointer
|
78
|
+
attach_function :PyErr_Clear, [], :void
|
79
|
+
|
80
|
+
#Reference Counting
|
81
|
+
attach_function :Py_IncRef, [:pointer], :void
|
82
|
+
attach_function :Py_DecRef, [:pointer], :void
|
83
|
+
|
84
|
+
#Type Objects
|
85
|
+
attach_variable :PyString_Type, DummyStruct.by_value
|
86
|
+
attach_variable :PyList_Type, DummyStruct.by_value
|
87
|
+
attach_variable :PyInt_Type, DummyStruct.by_value
|
88
|
+
attach_variable :PyLong_Type, DummyStruct.by_value
|
89
|
+
attach_variable :PyFloat_Type, DummyStruct.by_value
|
90
|
+
attach_variable :PyTuple_Type, DummyStruct.by_value
|
91
|
+
attach_variable :PyDict_Type, DummyStruct.by_value
|
92
|
+
attach_variable :PyFunction_Type, DummyStruct.by_value
|
93
|
+
attach_variable :PyMethod_Type, DummyStruct.by_value
|
94
|
+
attach_variable :PyType_Type, DummyStruct.by_value
|
95
|
+
attach_variable :PyClass_Type, DummyStruct.by_value
|
96
|
+
|
97
|
+
attach_variable :Py_TrueStruct, :_Py_TrueStruct, DummyStruct.by_value
|
98
|
+
attach_variable :Py_ZeroStruct, :_Py_ZeroStruct, DummyStruct.by_value
|
99
|
+
attach_variable :Py_NoneStruct, :_Py_NoneStruct, DummyStruct.by_value
|
100
|
+
|
101
|
+
#This is an implementation of the basic structure of a Python PyObject
|
102
|
+
#struct. The C struct is actually much larger, but since we only access
|
103
|
+
#the first two data members via FFI and always deal with struct pointers
|
104
|
+
#there is no need to mess around with the rest of the object.
|
105
|
+
class PyObjectStruct < FFI::Struct
|
106
|
+
layout :ob_refcnt, :int,
|
107
|
+
:ob_type, :pointer
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rubypython/python'
|
2
|
+
require 'rubypython/macros'
|
3
|
+
|
4
|
+
|
5
|
+
module RubyPython
|
6
|
+
|
7
|
+
#Raised when an error occurs in the Python interpreter.
|
8
|
+
class PythonError < Exception
|
9
|
+
|
10
|
+
#@param [String] typeName the class name of the Python error
|
11
|
+
#@param [String] msg the message attached to the Python error
|
12
|
+
def initialize(typeName, msg)
|
13
|
+
@type = typeName
|
14
|
+
super([typeName, msg].join(': '))
|
15
|
+
end
|
16
|
+
|
17
|
+
#This method should be called when an error has occured in the
|
18
|
+
#Python interpreter. This acts as factory function for PythonError
|
19
|
+
#objects. The function fetchs calls {fetch} to get the error
|
20
|
+
#information from the Python interpreter and uses this to build
|
21
|
+
#a PythonError object. It then calls {clear} to clear the error
|
22
|
+
#flag of the python interpreter
|
23
|
+
#@return [PythonError] an error enscapsulating the Python error
|
24
|
+
def self.handle_error
|
25
|
+
rbType, rbValue, rbTraceback = fetch()
|
26
|
+
|
27
|
+
if not rbValue.null?
|
28
|
+
msg = rbValue.getAttr("__str__").callObject PyObject.buildArgTuple
|
29
|
+
msg = msg.rubify
|
30
|
+
else
|
31
|
+
msg = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
#Decrease the reference count. This will happen anyway when they go
|
35
|
+
#out of scope but might as well.
|
36
|
+
rbValue.xDecref
|
37
|
+
rbTraceback.xDecref
|
38
|
+
pyName = rbType.getAttr("__name__")
|
39
|
+
|
40
|
+
rbType.xDecref
|
41
|
+
rbName = pyName.rubify
|
42
|
+
pyName.xDecref
|
43
|
+
|
44
|
+
PythonError.clear
|
45
|
+
|
46
|
+
PythonError.new(rbName, msg)
|
47
|
+
end
|
48
|
+
|
49
|
+
#A wrapper to the Python C API PyErr_Fetch function.
|
50
|
+
#@return [Array<PyObject>] an array containing three {PyObject} instances.
|
51
|
+
# representing the Type, Value, and stacktrace of the python
|
52
|
+
# error respectively.
|
53
|
+
def self.fetch
|
54
|
+
typePointer = FFI::MemoryPointer.new :pointer
|
55
|
+
valuePointer = FFI::MemoryPointer.new :pointer
|
56
|
+
tracebackPointer = FFI::MemoryPointer.new :pointer
|
57
|
+
|
58
|
+
Python.PyErr_Fetch typePointer, valuePointer, tracebackPointer
|
59
|
+
|
60
|
+
rbType = PyObject.new typePointer.read_pointer
|
61
|
+
rbValue = PyObject.new valuePointer.read_pointer
|
62
|
+
rbTraceback = PyObject.new tracebackPointer.read_pointer
|
63
|
+
[rbType, rbValue, rbTraceback]
|
64
|
+
end
|
65
|
+
|
66
|
+
#Determines whether an error has occured in the python interpreter.
|
67
|
+
def self.error?
|
68
|
+
!Python.PyErr_Occurred.null?
|
69
|
+
end
|
70
|
+
|
71
|
+
#Resets the Python interpreter error flag
|
72
|
+
#@return [void]
|
73
|
+
def self.clear
|
74
|
+
Python.PyErr_Clear
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'rubypython/pythonerror'
|
2
|
+
require 'rubypython/pyobject'
|
3
|
+
require 'rubypython/conversion'
|
4
|
+
require 'rubypython/operators'
|
5
|
+
require 'rubypython/blankobject'
|
6
|
+
|
7
|
+
module RubyPython
|
8
|
+
#This is the object that the end user will most often be interacting
|
9
|
+
#with. It holds a reference to an object in the Python VM an delegates
|
10
|
+
#method calls to it, wrapping and returning the results. The user should
|
11
|
+
#not worry about reference counting of this object an instance
|
12
|
+
#will decrement its objects reference count when it is garbage collected.
|
13
|
+
#
|
14
|
+
#Note: All RubyPyProxy objects become invalid when the Python interpreter
|
15
|
+
#is halted.
|
16
|
+
class RubyPyProxy < BlankObject
|
17
|
+
include Operators
|
18
|
+
|
19
|
+
attr_reader :pObject
|
20
|
+
|
21
|
+
def initialize(pObject)
|
22
|
+
if pObject.kind_of? PyObject
|
23
|
+
@pObject = pObject
|
24
|
+
else
|
25
|
+
@pObject = PyObject.new pObject
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#Handles the job of wrapping up anything returned by a {RubyPyProxy}
|
30
|
+
#instance. The behavior differs depending on the value of
|
31
|
+
#{RubyPython.legacy_mode}. If legacy mode is inactive, every returned
|
32
|
+
#object is wrapped by an instance of {RubyPyProxy}. If legacy mode is
|
33
|
+
#active, RubyPython first attempts to convert the returned object to a
|
34
|
+
#native Ruby type, and then only wraps the object if this fails.
|
35
|
+
def _wrap(pyobject)
|
36
|
+
if pyobject.class?
|
37
|
+
RubyPyClass.new(pyobject)
|
38
|
+
elsif RubyPython.legacy_mode
|
39
|
+
pyobject.rubify
|
40
|
+
else
|
41
|
+
RubyPyProxy.new(pyobject)
|
42
|
+
end
|
43
|
+
rescue Conversion::UnsupportedConversion => exc
|
44
|
+
RubyPyProxy.new pyobject
|
45
|
+
end
|
46
|
+
|
47
|
+
reveal(:respond_to?)
|
48
|
+
|
49
|
+
#Moves the old respond_to? method to is_real_method?
|
50
|
+
alias :is_real_method? :respond_to?
|
51
|
+
|
52
|
+
#RubyPython checks the attribute dictionary of the wrapped object
|
53
|
+
#to check whether it will respond to a method call. This should not
|
54
|
+
#return false positives but it may return false negatives. The builitin Ruby
|
55
|
+
#respond_to? method has been aliased to is_real_method?.
|
56
|
+
def respond_to?(mname)
|
57
|
+
return true if is_real_method?(mname)
|
58
|
+
mname = mname.to_s
|
59
|
+
return true if mname.end_with? '='
|
60
|
+
@pObject.hasAttr(mname)
|
61
|
+
end
|
62
|
+
|
63
|
+
#Implements the method call delegation.
|
64
|
+
def method_missing(name, *args, &block)
|
65
|
+
name = name.to_s
|
66
|
+
|
67
|
+
if(name.end_with? "?")
|
68
|
+
begin
|
69
|
+
RubyPyProxy.reveal(name.to_sym)
|
70
|
+
return self.__send__(name.to_sym, *args, &block)
|
71
|
+
rescue RuntimeError => exc
|
72
|
+
raise NoMethodError.new(name) if exc.message =~ /Don't know how to reveal/
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
if(name.end_with? "=")
|
79
|
+
setter = true
|
80
|
+
name.chomp! "="
|
81
|
+
else
|
82
|
+
setter=false
|
83
|
+
end
|
84
|
+
|
85
|
+
if(!@pObject.hasAttr(name) and !setter)
|
86
|
+
raise NoMethodError.new(name)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
args = PyObject.convert(*args)
|
91
|
+
|
92
|
+
if setter
|
93
|
+
return @pObject.setAttr(name, args[0])
|
94
|
+
end
|
95
|
+
|
96
|
+
pFunc = @pObject.getAttr(name)
|
97
|
+
|
98
|
+
if pFunc.callable?
|
99
|
+
if args.empty? and pFunc.class?
|
100
|
+
pReturn = pFunc
|
101
|
+
else
|
102
|
+
pTuple = PyObject.buildArgTuple(*args)
|
103
|
+
pReturn = pFunc.callObject(pTuple)
|
104
|
+
if(PythonError.error?)
|
105
|
+
raise PythonError.handle_error
|
106
|
+
end
|
107
|
+
end
|
108
|
+
else
|
109
|
+
pReturn = pFunc
|
110
|
+
end
|
111
|
+
|
112
|
+
return _wrap(pReturn)
|
113
|
+
end
|
114
|
+
|
115
|
+
#RubyPython will attempt to translate the wrapped object into a native
|
116
|
+
#Ruby object. This will only succeed for simple builtin type.
|
117
|
+
def rubify
|
118
|
+
@pObject.rubify
|
119
|
+
end
|
120
|
+
|
121
|
+
#Returns the string representation of the wrapped object via a call to the
|
122
|
+
#object's \_\_repr\_\_ method. Falls back on the default Ruby behavior when
|
123
|
+
#this method cannot be found.
|
124
|
+
#
|
125
|
+
#@return [String]
|
126
|
+
def inspect
|
127
|
+
self.__repr__.rubify rescue _inspect
|
128
|
+
rescue
|
129
|
+
class << self; define_method :_inspect, RubyPyProxy.find_hidden_method(:inspect); end
|
130
|
+
_inspect
|
131
|
+
end
|
132
|
+
|
133
|
+
#Returns the string representation of the wrapped object via a call to the
|
134
|
+
#object's \_\_str\_\_ method. Falls back on the default Ruby behavior when
|
135
|
+
#this method cannot be found.
|
136
|
+
#
|
137
|
+
#@return [String]
|
138
|
+
def to_s
|
139
|
+
self.__str__.rubify rescue _to_s
|
140
|
+
rescue
|
141
|
+
class << self; define_method :_to_s, RubyPyProxy.find_hidden_method(:to_s); end
|
142
|
+
_to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
#Converts the wrapped Python object to a Ruby Array. Note that this only converts
|
146
|
+
#one level, so a nested array will remain a proxy object. Only wrapped
|
147
|
+
#objects which have an \_\_iter\_\_ method may be converted using to_a.
|
148
|
+
#
|
149
|
+
#Note that for Dict objects, this method returns what you would get in
|
150
|
+
#Python, not in Ruby i.e. a_dict.to_a returns an array of the
|
151
|
+
#dictionary's keys.
|
152
|
+
#@return [Array<RubyPyProxy>]
|
153
|
+
#@example List
|
154
|
+
# irb(main):001:0> RubyPython.start
|
155
|
+
# => true
|
156
|
+
# irb(main):002:0> a_list = RubyPython::RubyPyProxy.new [1, 'a', 2, 'b']
|
157
|
+
# => [1, 'a', 2, 'b']
|
158
|
+
# irb(main):003:0> a_list.kind_of? RubyPython::RubyPyProxy
|
159
|
+
# => true
|
160
|
+
# irb(main):004:0> a_list.to_a
|
161
|
+
# => [1, 'a', 2, 'b']
|
162
|
+
# irb(main):005:0> RubyPython.stop
|
163
|
+
# => true
|
164
|
+
#
|
165
|
+
#@example Dict
|
166
|
+
# irb(main):001:0> RubyPython.start
|
167
|
+
# => true
|
168
|
+
# irb(main):002:0> a_dict = RubyPython::RubyPyProxy.new({1 => '2', :three => [4,5]})
|
169
|
+
# => {1: '2', 'three': [4, 5]}
|
170
|
+
# irb(main):003:0> a_dict.kind_of? RubyPython::RubyPyProxy
|
171
|
+
# => true
|
172
|
+
# irb(main):004:0> a_dict.to_a
|
173
|
+
# => [1, 'three']
|
174
|
+
# irb(main):005:0> RubyPython.stop
|
175
|
+
# => true
|
176
|
+
|
177
|
+
def to_a
|
178
|
+
iter = self.__iter__
|
179
|
+
ary = []
|
180
|
+
loop do
|
181
|
+
ary << iter.next()
|
182
|
+
end
|
183
|
+
rescue PythonError => exc
|
184
|
+
raise if exc.message !~ /StopIteration/
|
185
|
+
ary
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
#A class to wrap Python Modules. It behaves exactly the same as {RubyPyProxy}.
|
191
|
+
#It is just here for Bookkeeping and aesthetics.
|
192
|
+
class RubyPyModule < RubyPyProxy
|
193
|
+
end
|
194
|
+
|
195
|
+
#A class to wrap Python Classes.
|
196
|
+
class RubyPyClass < RubyPyProxy
|
197
|
+
|
198
|
+
#Create an instance of the wrapped class. This is a workaround for the fact
|
199
|
+
#that Python classes are meant to be callable.
|
200
|
+
def new(*args)
|
201
|
+
args = PyObject.convert(*args)
|
202
|
+
pTuple = PyObject.buildArgTuple(*args)
|
203
|
+
pReturn = @pObject.callObject(pTuple)
|
204
|
+
if PythonError.error?
|
205
|
+
raise PythonError.handle_error
|
206
|
+
end
|
207
|
+
RubyPyInstance.new pReturn
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
#An object representing an instance of a Python Class.
|
212
|
+
class RubyPyInstance < RubyPyProxy
|
213
|
+
end
|
214
|
+
end
|