rubypython-raspi 0.1.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/.autotest +3 -0
- data/.gitignore +18 -0
- data/.hgignore +20 -0
- data/.hgtags +10 -0
- data/.rspec +2 -0
- data/Contributors.rdoc +10 -0
- data/History.rdoc +192 -0
- data/License.rdoc +26 -0
- data/Manifest.txt +43 -0
- data/PostInstall.txt +16 -0
- data/README.rdoc +272 -0
- data/Rakefile +108 -0
- data/autotest/discover.rb +1 -0
- data/lib/rubypython.rb +284 -0
- data/lib/rubypython/blankobject.rb +23 -0
- data/lib/rubypython/conversion.rb +286 -0
- data/lib/rubypython/legacy.rb +18 -0
- data/lib/rubypython/macros.rb +56 -0
- data/lib/rubypython/operators.rb +124 -0
- data/lib/rubypython/pygenerator.rb +61 -0
- data/lib/rubypython/pymainclass.rb +80 -0
- data/lib/rubypython/pyobject.rb +232 -0
- data/lib/rubypython/python.rb +195 -0
- data/lib/rubypython/pythonerror.rb +80 -0
- data/lib/rubypython/rubypyproxy.rb +336 -0
- data/lib/rubypython/type.rb +20 -0
- data/spec/basic_spec.rb +50 -0
- data/spec/callback_spec.rb +53 -0
- data/spec/conversion_spec.rb +68 -0
- data/spec/legacy_spec.rb +46 -0
- data/spec/pymainclass_spec.rb +24 -0
- data/spec/pyobject_spec.rb +246 -0
- data/spec/python_helpers/basics.py +23 -0
- data/spec/python_helpers/errors.py +2 -0
- data/spec/python_helpers/objects.py +48 -0
- data/spec/pythonerror_spec.rb +52 -0
- data/spec/refcnt_spec.rb +62 -0
- data/spec/rubypyclass_spec.rb +10 -0
- data/spec/rubypyproxy_spec.rb +261 -0
- data/spec/rubypython_spec.rb +59 -0
- data/spec/spec_helper.rb +67 -0
- metadata +200 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'thread'
|
3
|
+
require 'rubypython/interpreter'
|
4
|
+
|
5
|
+
module RubyPython
|
6
|
+
# This module will hold the loaded RubyPython interpreter.
|
7
|
+
module Python #:nodoc: all
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class RubyPython::Interpreter
|
12
|
+
# Infects the provided module with the Python FFI. Once a single module
|
13
|
+
# has been infected, the #infect! method is removed from
|
14
|
+
# RubyPython::Interpreter.
|
15
|
+
def infect!(mod)
|
16
|
+
Mutex.new.synchronize do
|
17
|
+
self.class.class_eval do
|
18
|
+
undef :infect!
|
19
|
+
end
|
20
|
+
|
21
|
+
mod.extend FFI::Library
|
22
|
+
# FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL
|
23
|
+
mod.ffi_lib_flags :lazy, :global
|
24
|
+
mod.ffi_lib self.library
|
25
|
+
|
26
|
+
# This class is a little bit of a hack to extract the address of
|
27
|
+
# global structs. If someone knows a better way please let me know.
|
28
|
+
mod.module_eval do
|
29
|
+
self.const_set :DummyStruct, Class.new(FFI::Struct)
|
30
|
+
self::DummyStruct.layout :dummy_var, :int
|
31
|
+
|
32
|
+
self.const_set(:PY_FILE_INPUT, 257)
|
33
|
+
self.const_set(:PY_EVAL_INPUT, 258)
|
34
|
+
self.const_set(:METH_VARARGS, 0x0001)
|
35
|
+
|
36
|
+
# Function methods & constants
|
37
|
+
attach_function :PyCFunction_New, [:pointer, :pointer], :pointer
|
38
|
+
callback :PyCFunction, [:pointer, :pointer], :pointer
|
39
|
+
|
40
|
+
attach_function :PyRun_String, [:string, :int, :pointer, :pointer], :pointer
|
41
|
+
attach_function :PyRun_SimpleString, [:string], :pointer
|
42
|
+
attach_function :Py_CompileString, [:string, :string, :int], :pointer
|
43
|
+
attach_function :PyEval_EvalCode, [:pointer, :pointer, :pointer], :pointer
|
44
|
+
attach_function :PyErr_SetString, [:pointer, :string], :void
|
45
|
+
|
46
|
+
# Python interpreter startup and shutdown
|
47
|
+
attach_function :Py_IsInitialized, [], :int
|
48
|
+
attach_function :Py_Initialize, [], :void
|
49
|
+
attach_function :Py_Finalize, [], :void
|
50
|
+
|
51
|
+
# Module methods
|
52
|
+
attach_function :PyImport_ImportModule, [:string], :pointer
|
53
|
+
|
54
|
+
# Object Methods
|
55
|
+
attach_function :PyObject_HasAttrString, [:pointer, :string], :int
|
56
|
+
attach_function :PyObject_GetAttrString, [:pointer, :string], :pointer
|
57
|
+
attach_function :PyObject_SetAttrString, [:pointer, :string, :pointer], :int
|
58
|
+
attach_function :PyObject_Dir, [:pointer], :pointer
|
59
|
+
|
60
|
+
attach_function :PyObject_Compare, [:pointer, :pointer], :int
|
61
|
+
|
62
|
+
attach_function :PyObject_Call, [:pointer, :pointer, :pointer], :pointer
|
63
|
+
attach_function :PyObject_CallObject, [:pointer, :pointer], :pointer
|
64
|
+
attach_function :PyCallable_Check, [:pointer], :int
|
65
|
+
|
66
|
+
### Python To Ruby Conversion
|
67
|
+
# String Methods
|
68
|
+
attach_function :PyString_AsString, [:pointer], :string
|
69
|
+
attach_function :PyString_FromString, [:string], :pointer
|
70
|
+
attach_function :PyString_AsStringAndSize, [:pointer, :pointer, :pointer], :int
|
71
|
+
attach_function :PyString_FromStringAndSize, [:buffer_in, :ssize_t], :pointer
|
72
|
+
|
73
|
+
# List Methods
|
74
|
+
attach_function :PyList_GetItem, [:pointer, :int], :pointer
|
75
|
+
attach_function :PyList_Size, [:pointer], :int
|
76
|
+
attach_function :PyList_New, [:int], :pointer
|
77
|
+
attach_function :PyList_SetItem, [:pointer, :int, :pointer], :void
|
78
|
+
|
79
|
+
# Integer Methods
|
80
|
+
attach_function :PyInt_AsLong, [:pointer], :long
|
81
|
+
attach_function :PyInt_FromLong, [:long], :pointer
|
82
|
+
|
83
|
+
attach_function :PyLong_AsLong, [:pointer], :long
|
84
|
+
attach_function :PyLong_FromLong, [:pointer], :long
|
85
|
+
|
86
|
+
# Float Methods
|
87
|
+
attach_function :PyFloat_AsDouble, [:pointer], :double
|
88
|
+
attach_function :PyFloat_FromDouble, [:double], :pointer
|
89
|
+
|
90
|
+
# Tuple Methods
|
91
|
+
attach_function :PySequence_List, [:pointer], :pointer
|
92
|
+
attach_function :PySequence_Tuple, [:pointer], :pointer
|
93
|
+
attach_function :PyTuple_Pack, [:int, :varargs], :pointer
|
94
|
+
|
95
|
+
# Dict/Hash Methods
|
96
|
+
attach_function :PyDict_Next, [:pointer, :pointer, :pointer, :pointer], :int
|
97
|
+
attach_function :PyDict_New, [], :pointer
|
98
|
+
attach_function :PyDict_SetItem, [:pointer, :pointer, :pointer], :int
|
99
|
+
attach_function :PyDict_Contains, [:pointer, :pointer], :int
|
100
|
+
attach_function :PyDict_GetItem, [:pointer, :pointer], :pointer
|
101
|
+
|
102
|
+
# Error Methods
|
103
|
+
attach_variable :PyExc_Exception, self::DummyStruct.by_ref
|
104
|
+
attach_variable :PyExc_StopIteration, self::DummyStruct.by_ref
|
105
|
+
attach_function :PyErr_SetNone, [:pointer], :void
|
106
|
+
attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
|
107
|
+
attach_function :PyErr_Occurred, [], :pointer
|
108
|
+
attach_function :PyErr_Clear, [], :void
|
109
|
+
|
110
|
+
# Reference Counting
|
111
|
+
attach_function :Py_IncRef, [:pointer], :void
|
112
|
+
attach_function :Py_DecRef, [:pointer], :void
|
113
|
+
|
114
|
+
# Type Objects
|
115
|
+
# attach_variable :PyBaseObject_Type, self::DummyStruct.by_value # built-in 'object'
|
116
|
+
# attach_variable :PyBaseString_Type, self::DummyStruct.by_value
|
117
|
+
# attach_variable :PyBool_Type, self::DummyStruct.by_value
|
118
|
+
# attach_variable :PyBuffer_Type, self::DummyStruct.by_value
|
119
|
+
# attach_variable :PyByteArrayIter_Type, self::DummyStruct.by_value
|
120
|
+
# attach_variable :PyByteArray_Type, self::DummyStruct.by_value
|
121
|
+
attach_variable :PyCFunction_Type, self::DummyStruct.by_value
|
122
|
+
# attach_variable :PyCObject_Type, self::DummyStruct.by_value
|
123
|
+
# attach_variable :PyCallIter_Type, self::DummyStruct.by_value
|
124
|
+
# attach_variable :PyCapsule_Type, self::DummyStruct.by_value
|
125
|
+
# attach_variable :PyCell_Type, self::DummyStruct.by_value
|
126
|
+
# attach_variable :PyClassMethod_Type, self::DummyStruct.by_value
|
127
|
+
attach_variable :PyClass_Type, self::DummyStruct.by_value
|
128
|
+
# attach_variable :PyCode_Type, self::DummyStruct.by_value
|
129
|
+
# attach_variable :PyComplex_Type, self::DummyStruct.by_value
|
130
|
+
# attach_variable :PyDictItems_Type, self::DummyStruct.by_value
|
131
|
+
# attach_variable :PyDictIterItem_Type, self::DummyStruct.by_value
|
132
|
+
# attach_variable :PyDictIterKey_Type, self::DummyStruct.by_value
|
133
|
+
# attach_variable :PyDictIterValue_Type, self::DummyStruct.by_value
|
134
|
+
# attach_variable :PyDictKeys_Type, self::DummyStruct.by_value
|
135
|
+
# attach_variable :PyDictProxy_Type, self::DummyStruct.by_value
|
136
|
+
# attach_variable :PyDictValues_Type, self::DummyStruct.by_value
|
137
|
+
attach_variable :PyDict_Type, self::DummyStruct.by_value
|
138
|
+
# attach_variable :PyEllipsis_Type, self::DummyStruct.by_value
|
139
|
+
# attach_variable :PyEnum_Type, self::DummyStruct.by_value
|
140
|
+
# attach_variable :PyFile_Type, self::DummyStruct.by_value
|
141
|
+
attach_variable :PyFloat_Type, self::DummyStruct.by_value
|
142
|
+
# attach_variable :PyFrame_Type, self::DummyStruct.by_value
|
143
|
+
# attach_variable :PyFrozenSet_Type, self::DummyStruct.by_value
|
144
|
+
attach_variable :PyFunction_Type, self::DummyStruct.by_value
|
145
|
+
# attach_variable :PyGen_Type, self::DummyStruct.by_value
|
146
|
+
# attach_variable :PyGetSetDescr_Type, self::DummyStruct.by_value
|
147
|
+
# attach_variable :PyInstance_Type, self::DummyStruct.by_value
|
148
|
+
attach_variable :PyInt_Type, self::DummyStruct.by_value
|
149
|
+
attach_variable :PyList_Type, self::DummyStruct.by_value
|
150
|
+
attach_variable :PyLong_Type, self::DummyStruct.by_value
|
151
|
+
# attach_variable :PyMemberDescr_Type, self::DummyStruct.by_value
|
152
|
+
# attach_variable :PyMemoryView_Type, self::DummyStruct.by_value
|
153
|
+
attach_variable :PyMethod_Type, self::DummyStruct.by_value
|
154
|
+
# attach_variable :PyModule_Type, self::DummyStruct.by_value
|
155
|
+
# attach_variable :PyNullImporter_Type, self::DummyStruct.by_value
|
156
|
+
# attach_variable :PyProperty_Type, self::DummyStruct.by_value
|
157
|
+
# attach_variable :PyRange_Type, self::DummyStruct.by_value
|
158
|
+
# attach_variable :PyReversed_Type, self::DummyStruct.by_value
|
159
|
+
# attach_variable :PySTEntry_Type, self::DummyStruct.by_value
|
160
|
+
# attach_variable :PySeqIter_Type, self::DummyStruct.by_value
|
161
|
+
# attach_variable :PySet_Type, self::DummyStruct.by_value
|
162
|
+
# attach_variable :PySlice_Type, self::DummyStruct.by_value
|
163
|
+
# attach_variable :PyStaticMethod_Type, self::DummyStruct.by_value
|
164
|
+
attach_variable :PyString_Type, self::DummyStruct.by_value
|
165
|
+
# attach_variable :PySuper_Type, self::DummyStruct.by_value # built-in 'super'
|
166
|
+
# attach_variable :PyTraceBack_Type, self::DummyStruct.by_value
|
167
|
+
attach_variable :PyTuple_Type, self::DummyStruct.by_value
|
168
|
+
attach_variable :PyType_Type, self::DummyStruct.by_value
|
169
|
+
# attach_variable :PyUnicode_Type, self::DummyStruct.by_value
|
170
|
+
# attach_variable :PyWrapperDescr_Type, self::DummyStruct.by_value
|
171
|
+
|
172
|
+
attach_variable :Py_TrueStruct, :_Py_TrueStruct, self::DummyStruct.by_value
|
173
|
+
attach_variable :Py_ZeroStruct, :_Py_ZeroStruct, self::DummyStruct.by_value
|
174
|
+
attach_variable :Py_NoneStruct, :_Py_NoneStruct, self::DummyStruct.by_value
|
175
|
+
|
176
|
+
# This is an implementation of the basic structure of a Python PyObject
|
177
|
+
# struct. The C struct is actually much larger, but since we only access
|
178
|
+
# the first two data members via FFI and always deal with struct
|
179
|
+
# pointers there is no need to mess around with the rest of the object.
|
180
|
+
self.const_set :PyObjectStruct, Class.new(FFI::Struct)
|
181
|
+
self::PyObjectStruct.layout :ob_refcnt, :ssize_t,
|
182
|
+
:ob_type, :pointer
|
183
|
+
|
184
|
+
# This struct is used when defining Python methods.
|
185
|
+
self.const_set :PyMethodDef, Class.new(FFI::Struct)
|
186
|
+
self::PyMethodDef.layout :ml_name, :pointer,
|
187
|
+
:ml_meth, :PyCFunction,
|
188
|
+
:ml_flags, :int,
|
189
|
+
:ml_doc, :pointer
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|
194
|
+
private :infect!
|
195
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rubypython/python'
|
2
|
+
require 'rubypython/macros'
|
3
|
+
|
4
|
+
# Raised when an error occurs in the \Python interpreter.
|
5
|
+
class RubyPython::PythonError < RuntimeError
|
6
|
+
# The \Python traceback object associated with this error. This will be
|
7
|
+
# a RubyPython::RubyPyProxy object.
|
8
|
+
attr_reader :traceback
|
9
|
+
|
10
|
+
# Creates the PythonError.
|
11
|
+
# [typeName] The class name of the \Python error.
|
12
|
+
# [msg] The message attached to the \Python error.
|
13
|
+
# [traceback] The traceback, if any, associated with the \Python error.
|
14
|
+
def initialize(typeName, msg, traceback = nil)
|
15
|
+
@type = typeName
|
16
|
+
@traceback = traceback
|
17
|
+
super([typeName, msg].join(': '))
|
18
|
+
end
|
19
|
+
|
20
|
+
# This method should be called when an error has occurred in the \Python
|
21
|
+
# interpreter. This acts as factory function for PythonError objects. The
|
22
|
+
# function fetches calls +#fetch+ to get the error information from the
|
23
|
+
# \Python interpreter and uses this to build a PythonError object. It then
|
24
|
+
# calls +#clear to clear the error flag in the python interpreter. After
|
25
|
+
# the error flag has been cleared, the PythonError object is returned.
|
26
|
+
def self.handle_error
|
27
|
+
rbType, rbValue, rbTraceback = fetch()
|
28
|
+
|
29
|
+
if not rbValue.null?
|
30
|
+
msg = rbValue.getAttr("__str__").callObject RubyPython::PyObject.buildArgTuple
|
31
|
+
msg = msg.rubify
|
32
|
+
else
|
33
|
+
msg = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
if not rbTraceback.null?
|
37
|
+
traceback = RubyPython::RubyPyProxy.new rbTraceback
|
38
|
+
else
|
39
|
+
traceback = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Decrease the reference count. This will happen anyway when they go out
|
43
|
+
# of scope but might as well.
|
44
|
+
rbValue.xDecref
|
45
|
+
pyName = rbType.getAttr("__name__")
|
46
|
+
|
47
|
+
rbType.xDecref
|
48
|
+
rbName = pyName.rubify
|
49
|
+
pyName.xDecref
|
50
|
+
|
51
|
+
RubyPython::PythonError.clear
|
52
|
+
RubyPython::PythonError.new(rbName, msg, traceback)
|
53
|
+
end
|
54
|
+
|
55
|
+
# A wrapper to the \Python C API +PyErr_Fetch+ function. Returns an array
|
56
|
+
# with three PyObject instances, representing the Type, the Value, and the
|
57
|
+
# stack trace of the Python error.
|
58
|
+
def self.fetch
|
59
|
+
typePointer = FFI::MemoryPointer.new :pointer
|
60
|
+
valuePointer = FFI::MemoryPointer.new :pointer
|
61
|
+
tracebackPointer = FFI::MemoryPointer.new :pointer
|
62
|
+
|
63
|
+
RubyPython::Python.PyErr_Fetch typePointer, valuePointer, tracebackPointer
|
64
|
+
|
65
|
+
rbType = RubyPython::PyObject.new typePointer.read_pointer
|
66
|
+
rbValue = RubyPython::PyObject.new valuePointer.read_pointer
|
67
|
+
rbTraceback = RubyPython::PyObject.new tracebackPointer.read_pointer
|
68
|
+
[rbType, rbValue, rbTraceback]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Determines whether an error has occurred in the \Python interpreter.
|
72
|
+
def self.error?
|
73
|
+
!RubyPython::Python.PyErr_Occurred.null?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Resets the \Python interpreter error flag
|
77
|
+
def self.clear
|
78
|
+
RubyPython::Python.PyErr_Clear
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,336 @@
|
|
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
|
+
# In most cases, users will interact with RubyPyProxy objects that hold
|
9
|
+
# references to active objects in the \Python interpreter. RubyPyProxy
|
10
|
+
# delegates method calls to \Python objects, wrapping and returning the
|
11
|
+
# results as RubyPyProxy objects.
|
12
|
+
#
|
13
|
+
# The allocation, deallocation, and reference counting on RubyPyProxy
|
14
|
+
# objects is automatic: RubyPython takes care of it all. When the object
|
15
|
+
# is garbage collected, the instance will automatically decrement its
|
16
|
+
# object reference count.
|
17
|
+
#
|
18
|
+
# [NOTE:] All RubyPyProxy objects become invalid when the \Python
|
19
|
+
# interpreter is halted.
|
20
|
+
#
|
21
|
+
# == Calling Methods With Blocks
|
22
|
+
# Any method which is forwarded to a \Python object may be called with a
|
23
|
+
# block. The result of the method will passed as the argument to that
|
24
|
+
# block.
|
25
|
+
#
|
26
|
+
# RubyPython.run do
|
27
|
+
# sys = RubyPython.import 'sys'
|
28
|
+
# sys.version { |v| v.rubify.split(' ') }
|
29
|
+
# end
|
30
|
+
# # => [ "2.6.1", … ]
|
31
|
+
#
|
32
|
+
# == Passing Procs and Methods to \Python Methods
|
33
|
+
# RubyPython supports passing Proc and Method objects to \Python methods.
|
34
|
+
# The Proc or Method object must be passed explicitly. As seen above,
|
35
|
+
# supplying a block to a method will result in the return value of the
|
36
|
+
# method call being passed to the block.
|
37
|
+
#
|
38
|
+
# When a Proc or Method is supplied as a callback, then arguments that it
|
39
|
+
# will be called with will be wrapped \Python objects. It will therefore
|
40
|
+
# typically be necessary to write a wrapper around any Ruby callback that
|
41
|
+
# requires native Ruby objects.
|
42
|
+
#
|
43
|
+
# # Python Code: sample.py
|
44
|
+
# def apply_callback(callback, argument):
|
45
|
+
# return callback(argument)
|
46
|
+
#
|
47
|
+
# # IRB Session
|
48
|
+
# >> RubyPython.start
|
49
|
+
# => true
|
50
|
+
# >> sys = RubyPython.import 'sys'
|
51
|
+
# => <module 'sys' (built-in)>
|
52
|
+
# >> sys.path.append('.')
|
53
|
+
# => None
|
54
|
+
# >> sample = RubyPython.import 'sample'
|
55
|
+
# => <module 'sample' from './sample.pyc'>
|
56
|
+
# >> callback = Proc.new { |arg| arg * 2 }
|
57
|
+
# => # <Proc:0x000001018df490@(irb):5>
|
58
|
+
# >> sample.apply_callback(callback, 21).rubify
|
59
|
+
# => 42
|
60
|
+
# >> RubyPython.stop
|
61
|
+
# => true
|
62
|
+
class RubyPyProxy < BlankObject
|
63
|
+
include Operators
|
64
|
+
|
65
|
+
attr_reader :pObject
|
66
|
+
|
67
|
+
# Creates a \Python proxy for the provided Ruby object.
|
68
|
+
#
|
69
|
+
# Only the following Ruby types can be represented in \Python:
|
70
|
+
# * String
|
71
|
+
# * Array
|
72
|
+
# * Hash
|
73
|
+
# * Fixnum
|
74
|
+
# * Bignum
|
75
|
+
# * Float
|
76
|
+
# * Symbol (as a String)
|
77
|
+
# * Proc
|
78
|
+
# * Method
|
79
|
+
# * +true+ (as True)
|
80
|
+
# * +false+ (as False)
|
81
|
+
# * +nil+ (as None)
|
82
|
+
def initialize(pObject)
|
83
|
+
if pObject.kind_of? PyObject
|
84
|
+
@pObject = pObject
|
85
|
+
else
|
86
|
+
@pObject = PyObject.new pObject
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Handles the job of wrapping up anything returned by a RubyPyProxy
|
91
|
+
# instance. The behavior differs depending on the value of
|
92
|
+
# +RubyPython.legacy_mode+. If legacy mode is inactive, every returned
|
93
|
+
# object is wrapped by an instance of +RubyPyProxy+. If legacy mode is
|
94
|
+
# active, RubyPython first attempts to convert the returned object to a
|
95
|
+
# native Ruby type, and then only wraps the object if this fails.
|
96
|
+
def _wrap(pyobject)
|
97
|
+
if pyobject.class?
|
98
|
+
RubyPyClass.new(pyobject)
|
99
|
+
elsif RubyPython.__send__ :legacy_mode?
|
100
|
+
pyobject.rubify
|
101
|
+
else
|
102
|
+
RubyPyProxy.new(pyobject)
|
103
|
+
end
|
104
|
+
rescue Conversion::UnsupportedConversion => exc
|
105
|
+
RubyPyProxy.new pyobject
|
106
|
+
end
|
107
|
+
private :_wrap
|
108
|
+
|
109
|
+
reveal(:respond_to?)
|
110
|
+
|
111
|
+
# The standard Ruby +#respond_to?+ method has been renamed to allow
|
112
|
+
# RubyPython to query if the proxied \Python object supports the method
|
113
|
+
# desired. Setter methods (e.g., +foo=+) are always supported.
|
114
|
+
alias :is_real_method? :respond_to?
|
115
|
+
|
116
|
+
# RubyPython checks the attribute dictionary of the wrapped object to
|
117
|
+
# check whether it will respond to a method call. This should not return
|
118
|
+
# false positives but it may return false negatives. The built-in Ruby
|
119
|
+
# respond_to? method has been aliased to is_real_method?.
|
120
|
+
def respond_to?(mname)
|
121
|
+
return true if is_real_method?(mname)
|
122
|
+
mname = mname.to_s
|
123
|
+
return true if mname =~ /=$/
|
124
|
+
@pObject.hasAttr(mname)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Delegates method calls to proxied \Python objects.
|
128
|
+
#
|
129
|
+
# == Delegation Rules
|
130
|
+
# 1. If the method ends with a question-mark (e.g., +nil?+), it can only
|
131
|
+
# be a Ruby method on RubyPyProxy. Attempt to reveal it (RubyPyProxy
|
132
|
+
# is a BlankObject) and call it.
|
133
|
+
# 2. If the method ends with equals signs (e.g., +value=+) it's a setter
|
134
|
+
# and we can always set an attribute on a \Python object.
|
135
|
+
# 3. If the method ends with an exclamation point (e.g., +foo!+) we are
|
136
|
+
# attempting to call a method with keyword arguments.
|
137
|
+
# 4. The Python method or value will be called, if it's callable.
|
138
|
+
# 5. RubyPython will wrap the return value in a RubyPyProxy object
|
139
|
+
# (unless legacy_mode has been turned on).
|
140
|
+
# 6. If a block has been provided, the wrapped return value will be
|
141
|
+
# passed into the block.
|
142
|
+
def method_missing(name, *args, &block)
|
143
|
+
name = name.to_s
|
144
|
+
|
145
|
+
if name =~ /\?$/
|
146
|
+
begin
|
147
|
+
RubyPyProxy.reveal(name.to_sym)
|
148
|
+
return self.__send__(name.to_sym, *args, &block)
|
149
|
+
rescue RuntimeError => exc
|
150
|
+
raise NoMethodError.new(name) if exc.message =~ /Don't know how to reveal/
|
151
|
+
raise
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
kwargs = false
|
156
|
+
|
157
|
+
if name =~ /=$/
|
158
|
+
return @pObject.setAttr(name.chomp('='),
|
159
|
+
PyObject.convert(*args).first)
|
160
|
+
elsif name =~ /!$/
|
161
|
+
kwargs = true
|
162
|
+
name.chomp! "!"
|
163
|
+
end
|
164
|
+
|
165
|
+
raise NoMethodError.new(name) if !@pObject.hasAttr(name)
|
166
|
+
|
167
|
+
pFunc = @pObject.getAttr(name)
|
168
|
+
|
169
|
+
if pFunc.callable?
|
170
|
+
if args.empty? and pFunc.class?
|
171
|
+
pReturn = pFunc
|
172
|
+
else
|
173
|
+
if kwargs and args.last.is_a?(Hash)
|
174
|
+
pKeywords = PyObject.convert(args.pop).first
|
175
|
+
end
|
176
|
+
|
177
|
+
orig_args = args
|
178
|
+
args = PyObject.convert(*args)
|
179
|
+
pTuple = PyObject.buildArgTuple(*args)
|
180
|
+
pReturn = if pKeywords
|
181
|
+
pFunc.callObjectKeywords(pTuple, pKeywords)
|
182
|
+
else
|
183
|
+
pFunc.callObject(pTuple)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Clean up unused Python vars instead of waiting on Ruby's GC to
|
187
|
+
# do it.
|
188
|
+
pFunc.xDecref
|
189
|
+
pTuple.xDecref
|
190
|
+
pKeywords.xDecref if pKeywords
|
191
|
+
orig_args.each_with_index do |arg, i|
|
192
|
+
# Only decref objects that were created in PyObject.convert.
|
193
|
+
if !arg.kind_of?(RubyPython::PyObject) and !arg.kind_of?(RubyPython::RubyPyProxy)
|
194
|
+
args[i].xDecref
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
raise PythonError.handle_error if PythonError.error?
|
199
|
+
end
|
200
|
+
else
|
201
|
+
pReturn = pFunc
|
202
|
+
end
|
203
|
+
|
204
|
+
result = _wrap(pReturn)
|
205
|
+
|
206
|
+
if block
|
207
|
+
block.call(result)
|
208
|
+
else
|
209
|
+
result
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# RubyPython will attempt to translate the wrapped object into a native
|
214
|
+
# Ruby object. This will only succeed for simple built-in type.
|
215
|
+
def rubify
|
216
|
+
@pObject.rubify
|
217
|
+
end
|
218
|
+
|
219
|
+
# Returns the String representation of the wrapped object via a call to
|
220
|
+
# the object's <tt>__repr__</tt> method, or the +repr+ method in PyMain.
|
221
|
+
def inspect
|
222
|
+
self.__repr__.rubify
|
223
|
+
rescue PythonError, NoMethodError
|
224
|
+
RubyPython::PyMain.repr(self).rubify
|
225
|
+
end
|
226
|
+
|
227
|
+
# Returns the string representation of the wrapped object via a call to
|
228
|
+
# the object's <tt>__str__</tt> method or the +str+ method in PyMain.
|
229
|
+
def to_s
|
230
|
+
self.__str__.rubify
|
231
|
+
rescue PythonError, NoMethodError
|
232
|
+
RubyPython::PyMain.str(self).rubify
|
233
|
+
end
|
234
|
+
|
235
|
+
# Converts the wrapped \Python object to a Ruby Array. Note that this
|
236
|
+
# only converts one level, so a nested array will remain a proxy object.
|
237
|
+
# Only wrapped objects which have an <tt>__iter__</tt> method may be
|
238
|
+
# converted using +to_a+.
|
239
|
+
#
|
240
|
+
# Note that for \Python Dict objects, this method returns what you would
|
241
|
+
# get in \Python, not in Ruby: +a_dict.to_a+ returns an array of the
|
242
|
+
# dictionary's keys.
|
243
|
+
#
|
244
|
+
# === List #to_a Returns an Array
|
245
|
+
# >> RubyPython.start
|
246
|
+
# => true
|
247
|
+
# >> list = RubyPython::RubyPyProxy.new([1, 'a', 2, 'b'])
|
248
|
+
# => [1, 'a', 2, 'b']
|
249
|
+
# >> list.kind_of? RubyPython::RubyPyProxy
|
250
|
+
# => true
|
251
|
+
# >> list.to_a
|
252
|
+
# => [1, 'a', 2, 'b']
|
253
|
+
# >> RubyPython.stop
|
254
|
+
# => true
|
255
|
+
#
|
256
|
+
# === Dict #to_a Returns An Array of Keys
|
257
|
+
# >> RubyPython.start
|
258
|
+
# => true
|
259
|
+
# >> dict = RubyPython::RubyPyProxy.new({1 => '2', :three => [4,5]})
|
260
|
+
# => {1: '2', 'three': [4, 5]}
|
261
|
+
# >> dict.kind_of? RubyPython::RubyPyProxy
|
262
|
+
# => true
|
263
|
+
# >> dict.to_a
|
264
|
+
# => [1, 'three']
|
265
|
+
# >> RubyPython.stop
|
266
|
+
# => true
|
267
|
+
#
|
268
|
+
# === Non-Array Values Do Not Convert
|
269
|
+
# >> RubyPython.start
|
270
|
+
# => true
|
271
|
+
# >> item = RubyPython::RubyPyProxy.new(42)
|
272
|
+
# => 42
|
273
|
+
# >> item.to_a
|
274
|
+
# NoMethodError: __iter__
|
275
|
+
def to_a
|
276
|
+
iter = self.__iter__
|
277
|
+
ary = []
|
278
|
+
loop do
|
279
|
+
ary << iter.next()
|
280
|
+
end
|
281
|
+
rescue PythonError => exc
|
282
|
+
raise if exc.message !~ /StopIteration/
|
283
|
+
ary
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns the methods on the \Python object by calling the +dir+
|
287
|
+
# built-in.
|
288
|
+
def methods
|
289
|
+
pObject.dir.map { |x| x.to_sym }
|
290
|
+
end
|
291
|
+
|
292
|
+
# Creates a PyEnumerable for this object. The object must have the
|
293
|
+
# <tt>__iter__</tt> method.
|
294
|
+
def to_enum
|
295
|
+
PyEnumerable.new(@pObject)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# A class to wrap \Python modules. It behaves exactly the same as
|
300
|
+
# RubyPyProxy. It is just here for Bookkeeping and aesthetics.
|
301
|
+
class RubyPyModule < RubyPyProxy; end
|
302
|
+
|
303
|
+
# A class to wrap \Python classes.
|
304
|
+
class RubyPyClass < RubyPyProxy
|
305
|
+
# Create an instance of the wrapped class. This is a workaround for the
|
306
|
+
# fact that \Python classes are meant to be callable.
|
307
|
+
def new(*args)
|
308
|
+
args = PyObject.convert(*args)
|
309
|
+
pTuple = PyObject.buildArgTuple(*args)
|
310
|
+
pReturn = @pObject.callObject(pTuple)
|
311
|
+
raise PythonError.handle_error if PythonError.error?
|
312
|
+
RubyPyInstance.new pReturn
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# An object representing an instance of a \Python class. It behaves
|
317
|
+
# exactly the same as RubyPyProxy. It is just here for Bookkeeping and
|
318
|
+
# aesthetics.
|
319
|
+
class RubyPyInstance < RubyPyProxy; end
|
320
|
+
|
321
|
+
# An object representing a Python enumerable object.
|
322
|
+
class PyEnumerable < RubyPyProxy
|
323
|
+
include Enumerable
|
324
|
+
|
325
|
+
def each
|
326
|
+
iter = self.__iter__
|
327
|
+
loop do
|
328
|
+
begin
|
329
|
+
yield iter.next
|
330
|
+
rescue RubyPython::PythonError => exc
|
331
|
+
return if exc.message =~ /StopIteration/
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|