pycall 0.1.0.alpha.20170502 → 0.1.0.alpha.20170711
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/bin/console +2 -6
- data/bin/runner +6 -0
- data/examples/datascience_rb_20170519.ipynb +4836 -0
- data/examples/notebooks/classifier_comparison.ipynb +241 -0
- data/examples/notebooks/forest_importances.ipynb +261 -0
- data/examples/notebooks/iruby_integration.ipynb +204 -0
- data/examples/notebooks/lorenz_attractor.ipynb +245 -0
- data/examples/notebooks/polar_axes.ipynb +244 -0
- data/examples/notebooks/sum_benchmarking.ipynb +368 -0
- data/examples/notebooks/xkcd_style.ipynb +149 -0
- data/lib/pycall/conversion.rb +2 -0
- data/lib/pycall/dict.rb +2 -0
- data/lib/pycall/exception.rb +13 -0
- data/lib/pycall/gc_guard.rb +84 -0
- data/lib/pycall/import.rb +53 -14
- data/lib/pycall/init.rb +10 -0
- data/lib/pycall/libpython/pyobject_struct.rb +30 -0
- data/lib/pycall/libpython/pytypeobject_struct.rb +273 -0
- data/lib/pycall/libpython.rb +41 -35
- data/lib/pycall/list.rb +2 -0
- data/lib/pycall/pyobject_wrapper.rb +2 -0
- data/lib/pycall/ruby_wrapper.rb +137 -0
- data/lib/pycall/set.rb +3 -1
- data/lib/pycall/tuple.rb +3 -1
- data/lib/pycall/version.rb +1 -1
- data/lib/pycall.rb +3 -0
- data/pycall.gemspec +1 -0
- data/tasks/docker.rake +1 -0
- metadata +30 -2
data/lib/pycall/init.rb
CHANGED
@@ -11,10 +11,20 @@ module PyCall
|
|
11
11
|
end
|
12
12
|
|
13
13
|
@builtin = LibPython.PyImport_ImportModule(PYTHON_VERSION < '3.0.0' ? '__builtin__' : 'builtins').to_ruby
|
14
|
+
|
15
|
+
begin
|
16
|
+
import_module('stackless')
|
17
|
+
@has_stackless_extension = true
|
18
|
+
rescue PyError
|
19
|
+
@has_stackless_extension = false
|
20
|
+
end
|
21
|
+
|
22
|
+
__initialize_ruby_wrapper__
|
14
23
|
end
|
15
24
|
|
16
25
|
class << self
|
17
26
|
attr_reader :builtin
|
27
|
+
attr_reader :has_stackless_extension
|
18
28
|
end
|
19
29
|
|
20
30
|
__initialize_pycall__
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module PyCall
|
4
|
+
module LibPython
|
5
|
+
class PyObjectStruct < FFI::Struct
|
6
|
+
end
|
7
|
+
|
8
|
+
class PyTypeObjectStruct < PyObjectStruct
|
9
|
+
end
|
10
|
+
|
11
|
+
class PyObjectStruct < FFI::Struct
|
12
|
+
layout ob_refcnt: :ssize_t,
|
13
|
+
ob_type: PyTypeObjectStruct.by_ref
|
14
|
+
|
15
|
+
def self.null
|
16
|
+
new(FFI::Pointer::NULL)
|
17
|
+
end
|
18
|
+
|
19
|
+
def py_none?
|
20
|
+
PyCall.none?(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def kind_of?(klass)
|
24
|
+
klass = klass.__pyobj__ if klass.kind_of? PyObjectWrapper
|
25
|
+
return super unless klass.kind_of? PyObjectStruct
|
26
|
+
PyCall::Types.pyisinstance(self, klass)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'pycall/libpython/pyobject_struct'
|
2
|
+
|
3
|
+
module PyCall
|
4
|
+
module LibPython
|
5
|
+
# types:
|
6
|
+
T_SHORT = 0
|
7
|
+
T_INT = 1
|
8
|
+
T_LONG = 2
|
9
|
+
T_FLOAT = 3
|
10
|
+
T_DOUBLE = 4
|
11
|
+
T_STRING = 5
|
12
|
+
T_OBJECT = 6
|
13
|
+
T_CHAR = 7
|
14
|
+
T_BYTE = 8
|
15
|
+
T_UBYTE = 9
|
16
|
+
T_USHORT = 10
|
17
|
+
T_UINT = 11
|
18
|
+
T_ULONG = 12
|
19
|
+
T_STRING_INPLACE = 13
|
20
|
+
T_BOOL = 14
|
21
|
+
T_OBJECT_EX = 16
|
22
|
+
T_LONGLONG = 17 # added in Python 2.5
|
23
|
+
T_ULONGLONG = 18 # added in Python 2.5
|
24
|
+
T_PYSSIZET = 19 # added in Python 2.6
|
25
|
+
T_NONE = 20 # added in Python 3.0
|
26
|
+
|
27
|
+
# flags:
|
28
|
+
READONLY = 1
|
29
|
+
READ_RESTRICTED = 2
|
30
|
+
PY_WRITE_RESTRICTED = 4
|
31
|
+
RESTRICTED = (READ_RESTRICTED | PY_WRITE_RESTRICTED)
|
32
|
+
|
33
|
+
# Python 2.7
|
34
|
+
Py_TPFLAGS_HAVE_GETCHARBUFFER = 0x00000001<<0
|
35
|
+
Py_TPFLAGS_HAVE_SEQUENCE_IN = 0x00000001<<1
|
36
|
+
Py_TPFLAGS_GC = 0 # was sometimes (0x00000001<<2) in Python <= 2.1
|
37
|
+
Py_TPFLAGS_HAVE_INPLACEOPS = 0x00000001<<3
|
38
|
+
Py_TPFLAGS_CHECKTYPES = 0x00000001<<4
|
39
|
+
Py_TPFLAGS_HAVE_RICHCOMPARE = 0x00000001<<5
|
40
|
+
Py_TPFLAGS_HAVE_WEAKREFS = 0x00000001<<6
|
41
|
+
Py_TPFLAGS_HAVE_ITER = 0x00000001<<7
|
42
|
+
Py_TPFLAGS_HAVE_CLASS = 0x00000001<<8
|
43
|
+
Py_TPFLAGS_HAVE_INDEX = 0x00000001<<17
|
44
|
+
Py_TPFLAGS_HAVE_NEWBUFFER = 0x00000001<<21
|
45
|
+
Py_TPFLAGS_STRING_SUBCLASS = 0x00000001<<27
|
46
|
+
|
47
|
+
# Python 3.0+ has only these:
|
48
|
+
Py_TPFLAGS_HEAPTYPE = 0x00000001<<9
|
49
|
+
Py_TPFLAGS_BASETYPE = 0x00000001<<10
|
50
|
+
Py_TPFLAGS_READY = 0x00000001<<12
|
51
|
+
Py_TPFLAGS_READYING = 0x00000001<<13
|
52
|
+
Py_TPFLAGS_HAVE_GC = 0x00000001<<14
|
53
|
+
Py_TPFLAGS_HAVE_VERSION_TAG = 0x00000001<<18
|
54
|
+
Py_TPFLAGS_VALID_VERSION_TAG = 0x00000001<<19
|
55
|
+
Py_TPFLAGS_IS_ABSTRACT = 0x00000001<<20
|
56
|
+
Py_TPFLAGS_INT_SUBCLASS = 0x00000001<<23
|
57
|
+
Py_TPFLAGS_LONG_SUBCLASS = 0x00000001<<24
|
58
|
+
Py_TPFLAGS_LIST_SUBCLASS = 0x00000001<<25
|
59
|
+
Py_TPFLAGS_TUPLE_SUBCLASS = 0x00000001<<26
|
60
|
+
Py_TPFLAGS_BYTES_SUBCLASS = 0x00000001<<27
|
61
|
+
Py_TPFLAGS_UNICODE_SUBCLASS = 0x00000001<<28
|
62
|
+
Py_TPFLAGS_DICT_SUBCLASS = 0x00000001<<29
|
63
|
+
Py_TPFLAGS_BASE_EXC_SUBCLASS = 0x00000001<<30
|
64
|
+
Py_TPFLAGS_TYPE_SUBCLASS = 0x00000001<<31
|
65
|
+
|
66
|
+
# only use this if we have the stackless extension
|
67
|
+
Py_TPFLAGS_HAVE_STACKLESS_EXTENSION_ = 0x00000003<<15
|
68
|
+
|
69
|
+
class PyMethodDef < FFI::Struct
|
70
|
+
layout ml_name: :string,
|
71
|
+
ml_meth: :pointer,
|
72
|
+
ml_flags: :int,
|
73
|
+
ml_doc: :string # may be NULL
|
74
|
+
|
75
|
+
def initialize(*args)
|
76
|
+
case args.length
|
77
|
+
when 3, 4
|
78
|
+
name, meth, flags, doc = *args
|
79
|
+
super()
|
80
|
+
self.ml_name = name
|
81
|
+
self[:ml_meth] = meth
|
82
|
+
self[:ml_flags] = flags
|
83
|
+
self.ml_doc = doc
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def ml_name=(str)
|
90
|
+
@saved_name = FFI::MemoryPointer.from_string(str || '')
|
91
|
+
self.pointer.put_pointer(offset_of(:ml_name), @saved_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ml_doc=(str)
|
95
|
+
@saved_doc = FFI::MemoryPointer.from_string(str || '')
|
96
|
+
self.pointer.put_pointer(offset_of(:ml_name), @saved_doc)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# ml_flags should be one of:
|
101
|
+
METH_VARARGS = 0x0001 # args are a tuple of arguments
|
102
|
+
METH_KEYWORDS = 0x0002 # two arguments: the varargs and the kwargs
|
103
|
+
METH_NOARGS = 0x0004 # no arguments (NULL argument pointer)
|
104
|
+
METH_O = 0x0008 # single argument (not wrapped in tuple)
|
105
|
+
|
106
|
+
# not sure when these are needed:
|
107
|
+
METH_CLASS = 0x0010 # for class methods
|
108
|
+
METH_STATIC = 0x0020 # for static methods
|
109
|
+
|
110
|
+
class PyGetSetDef < FFI::Struct
|
111
|
+
layout name: :string,
|
112
|
+
get: :pointer,
|
113
|
+
set: :pointer, # may be NULL for read-only members
|
114
|
+
doc: :string,
|
115
|
+
closure: :pointer
|
116
|
+
end
|
117
|
+
|
118
|
+
class PyMemberDef < FFI::Struct
|
119
|
+
layout name: :string,
|
120
|
+
type: :int,
|
121
|
+
offset: :ssize_t,
|
122
|
+
flags: :int,
|
123
|
+
doc: :string
|
124
|
+
|
125
|
+
[:name, :doc].each do |field|
|
126
|
+
define_method(:"#{field}=") do |str|
|
127
|
+
saved_str = FFI::MemoryPointer.from_string(str)
|
128
|
+
instance_variable_set(:"@saved_#{field}", saved_str)
|
129
|
+
self.pointer.put_pointer(offset_of(field), saved_str)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class PyTypeObjectStruct < PyObjectStruct
|
135
|
+
layout ob_refcnt: :ssize_t,
|
136
|
+
ob_type: PyTypeObjectStruct.by_ref,
|
137
|
+
ob_size: :ssize_t,
|
138
|
+
|
139
|
+
tp_name: :string, # For printing, in format "<module>.<name>"
|
140
|
+
|
141
|
+
# For allocation
|
142
|
+
tp_basicsize: :ssize_t,
|
143
|
+
tp_itemsize: :ssize_t,
|
144
|
+
|
145
|
+
# Methods to implement standard operations
|
146
|
+
|
147
|
+
tp_dealloc: :pointer,
|
148
|
+
tp_print: :pointer,
|
149
|
+
tp_getattr: :pointer,
|
150
|
+
tp_setattr: :pointer,
|
151
|
+
tp_as_async: :pointer, # formerly known as tp_compare (Python 2) or tp_reserved (Python 3)
|
152
|
+
tp_repr: :pointer,
|
153
|
+
|
154
|
+
# Method suites for standard classes
|
155
|
+
|
156
|
+
tp_as_number: :pointer,
|
157
|
+
tp_as_sequence: :pointer,
|
158
|
+
tp_as_mapping: :pointer,
|
159
|
+
|
160
|
+
# More standard operations (here for binary compatibility)
|
161
|
+
|
162
|
+
tp_hash: :pointer,
|
163
|
+
tp_call: :pointer,
|
164
|
+
tp_str: :pointer,
|
165
|
+
tp_getattro: :pointer,
|
166
|
+
tp_setattro: :pointer,
|
167
|
+
|
168
|
+
# Functions to access object as input/output buffer
|
169
|
+
tp_as_buffer: :pointer,
|
170
|
+
|
171
|
+
# Flags to define presence of optional/expanded features
|
172
|
+
tp_flags: :ulong,
|
173
|
+
|
174
|
+
tp_doc: :string, # Documentation string
|
175
|
+
|
176
|
+
# Assigned meaning in release 2.0
|
177
|
+
# call function for all accessible objects
|
178
|
+
tp_traverse: :pointer,
|
179
|
+
|
180
|
+
# delete references to contained objects
|
181
|
+
tp_clear: :pointer,
|
182
|
+
|
183
|
+
# Assigned meaning in release 2.1
|
184
|
+
# rich comparisons
|
185
|
+
tp_richcompare: :pointer,
|
186
|
+
|
187
|
+
# weak reference enabler
|
188
|
+
tp_weaklistoffset: :ssize_t,
|
189
|
+
|
190
|
+
# Iterators
|
191
|
+
tp_iter: :pointer,
|
192
|
+
tp_iternext: :pointer,
|
193
|
+
|
194
|
+
# Attribute descriptor and subclassing stuff
|
195
|
+
tp_methods: PyMethodDef.by_ref,
|
196
|
+
tp_members: PyMemberDef.by_ref,
|
197
|
+
tp_getset: PyGetSetDef.by_ref,
|
198
|
+
tp_base: :pointer,
|
199
|
+
tp_dict: PyObjectStruct.by_ref,
|
200
|
+
tp_descr_get: :pointer,
|
201
|
+
tp_descr_set: :pointer,
|
202
|
+
tp_dictoffset: :ssize_t,
|
203
|
+
tp_init: :pointer,
|
204
|
+
tp_alloc: :pointer,
|
205
|
+
tp_new: :pointer,
|
206
|
+
tp_free: :pointer, # Low-level free-memory routine
|
207
|
+
tp_is_gc: :pointer, # For PyObject_IS_GC
|
208
|
+
tp_bases: PyObjectStruct.by_ref,
|
209
|
+
tp_mro: PyObjectStruct.by_ref, # method resolution order
|
210
|
+
tp_cache: PyObjectStruct.by_ref,
|
211
|
+
tp_subclasses: PyObjectStruct.by_ref,
|
212
|
+
tp_weaklist: PyObjectStruct.by_ref,
|
213
|
+
tp_del: :pointer,
|
214
|
+
|
215
|
+
# Type attribute cache version tag. Added in version 2.6
|
216
|
+
tp_version_tag: :uint,
|
217
|
+
|
218
|
+
tp_finalize: :pointer,
|
219
|
+
|
220
|
+
# The following members are only used for COUNT_ALLOCS builds of Python
|
221
|
+
tp_allocs: :ssize_t,
|
222
|
+
tp_frees: :ssize_t,
|
223
|
+
tp_maxalloc: :ssize_t,
|
224
|
+
tp_prev: :pointer,
|
225
|
+
tp_next: :pointer
|
226
|
+
|
227
|
+
def self.new(*args)
|
228
|
+
case args.length
|
229
|
+
when 0, 1
|
230
|
+
super
|
231
|
+
else
|
232
|
+
name, basic_size = *args
|
233
|
+
new.tap do |t|
|
234
|
+
# NOTE: Disable autorelease for avoiding SEGV occurrance in Python's GC collect function
|
235
|
+
# at which the __new__ method object of this type object is freed.
|
236
|
+
t.pointer.autorelease = false
|
237
|
+
|
238
|
+
# PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
239
|
+
t[:ob_refcnt] = 1
|
240
|
+
t[:ob_type] = LibPython.PyType_Type
|
241
|
+
t[:ob_size] = 0
|
242
|
+
|
243
|
+
t[:tp_basicsize] = basic_size
|
244
|
+
stackless_extension_flag = PyCall.has_stackless_extension ? Py_TPFLAGS_HAVE_STACKLESS_EXTENSION_ : 0
|
245
|
+
t[:tp_flags] = if PYTHON_VERSION >= '3'
|
246
|
+
stackless_extension_flag | Py_TPFLAGS_HAVE_VERSION_TAG
|
247
|
+
else
|
248
|
+
Py_TPFLAGS_HAVE_GETCHARBUFFER |
|
249
|
+
Py_TPFLAGS_HAVE_SEQUENCE_IN |
|
250
|
+
Py_TPFLAGS_HAVE_INPLACEOPS |
|
251
|
+
Py_TPFLAGS_HAVE_RICHCOMPARE |
|
252
|
+
Py_TPFLAGS_HAVE_WEAKREFS |
|
253
|
+
Py_TPFLAGS_HAVE_ITER |
|
254
|
+
Py_TPFLAGS_HAVE_CLASS |
|
255
|
+
stackless_extension_flag |
|
256
|
+
Py_TPFLAGS_HAVE_INDEX
|
257
|
+
end
|
258
|
+
t.tp_name = name
|
259
|
+
yield t if block_given?
|
260
|
+
t[:tp_new] = LibPython.find_symbol(:PyType_GenericNew) if t[:tp_new] == FFI::Pointer::NULL
|
261
|
+
raise PyError.fetch if LibPython.PyType_Ready(t) < 0
|
262
|
+
LibPython.Py_IncRef(t)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def tp_name=(str)
|
268
|
+
@saved_name = FFI::MemoryPointer.from_string(str)
|
269
|
+
self.pointer.put_pointer(offset_of(:tp_name), @saved_name)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
data/lib/pycall/libpython.rb
CHANGED
@@ -1,28 +1,11 @@
|
|
1
1
|
require 'ffi'
|
2
|
+
require 'pycall/libpython/pyobject_struct'
|
3
|
+
require 'pycall/libpython/pytypeobject_struct'
|
2
4
|
|
3
5
|
module PyCall
|
4
6
|
module LibPython
|
5
7
|
extend FFI::Library
|
6
8
|
|
7
|
-
class PyObjectStruct < FFI::Struct
|
8
|
-
layout ob_refcnt: :ssize_t,
|
9
|
-
ob_type: PyObjectStruct.by_ref
|
10
|
-
|
11
|
-
def self.null
|
12
|
-
new(FFI::Pointer::NULL)
|
13
|
-
end
|
14
|
-
|
15
|
-
def py_none?
|
16
|
-
PyCall.none?(self)
|
17
|
-
end
|
18
|
-
|
19
|
-
def kind_of?(klass)
|
20
|
-
klass = klass.__pyobj__ if klass.kind_of? PyObjectWrapper
|
21
|
-
return super unless klass.kind_of? PyObjectStruct
|
22
|
-
PyCall::Types.pyisinstance(self, klass)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
9
|
private_class_method
|
27
10
|
|
28
11
|
def self.find_libpython(python = nil)
|
@@ -151,6 +134,8 @@ module PyCall
|
|
151
134
|
ffi_lib_flags :lazy, :global
|
152
135
|
libpython = find_libpython ENV['PYTHON']
|
153
136
|
|
137
|
+
define_singleton_method(:find_symbol) {|name| libpython.find_symbol(name.to_s) }
|
138
|
+
|
154
139
|
attach_function :Py_GetVersion, [], :string
|
155
140
|
PYTHON_DESCRIPTION = LibPython.Py_GetVersion().freeze
|
156
141
|
PYTHON_VERSION = PYTHON_DESCRIPTION.split(' ', 2)[0].freeze
|
@@ -171,37 +156,42 @@ module PyCall
|
|
171
156
|
_Py_NoneStruct
|
172
157
|
end
|
173
158
|
|
174
|
-
attach_variable :PyType_Type,
|
159
|
+
attach_variable :PyType_Type, PyTypeObjectStruct
|
175
160
|
|
176
161
|
if libpython.find_variable('PyInt_Type')
|
177
162
|
has_PyInt_Type = true
|
178
|
-
attach_variable :PyInt_Type,
|
163
|
+
attach_variable :PyInt_Type, PyTypeObjectStruct
|
179
164
|
else
|
180
165
|
has_PyInt_Type = false
|
181
|
-
attach_variable :PyInt_Type, :PyLong_Type,
|
166
|
+
attach_variable :PyInt_Type, :PyLong_Type, PyTypeObjectStruct
|
182
167
|
end
|
183
168
|
|
184
|
-
attach_variable :PyLong_Type,
|
185
|
-
attach_variable :PyBool_Type,
|
186
|
-
attach_variable :PyFloat_Type,
|
187
|
-
attach_variable :PyComplex_Type,
|
188
|
-
attach_variable :PyUnicode_Type,
|
169
|
+
attach_variable :PyLong_Type, PyTypeObjectStruct
|
170
|
+
attach_variable :PyBool_Type, PyTypeObjectStruct
|
171
|
+
attach_variable :PyFloat_Type, PyTypeObjectStruct
|
172
|
+
attach_variable :PyComplex_Type, PyTypeObjectStruct
|
173
|
+
attach_variable :PyUnicode_Type, PyTypeObjectStruct
|
189
174
|
|
190
175
|
if libpython.find_symbol('PyString_FromStringAndSize')
|
191
176
|
string_as_bytes = false
|
192
|
-
attach_variable :PyString_Type,
|
177
|
+
attach_variable :PyString_Type, PyTypeObjectStruct
|
193
178
|
else
|
194
179
|
string_as_bytes = true
|
195
|
-
attach_variable :PyString_Type, :PyBytes_Type,
|
180
|
+
attach_variable :PyString_Type, :PyBytes_Type, PyTypeObjectStruct
|
196
181
|
end
|
197
182
|
|
198
|
-
attach_variable :PyList_Type,
|
199
|
-
attach_variable :PyTuple_Type,
|
200
|
-
attach_variable :PyDict_Type,
|
201
|
-
attach_variable :PySet_Type,
|
183
|
+
attach_variable :PyList_Type, PyTypeObjectStruct
|
184
|
+
attach_variable :PyTuple_Type, PyTypeObjectStruct
|
185
|
+
attach_variable :PyDict_Type, PyTypeObjectStruct
|
186
|
+
attach_variable :PySet_Type, PyTypeObjectStruct
|
202
187
|
|
203
|
-
attach_variable :PyFunction_Type,
|
204
|
-
attach_variable :PyMethod_Type,
|
188
|
+
attach_variable :PyFunction_Type, PyTypeObjectStruct
|
189
|
+
attach_variable :PyMethod_Type, PyTypeObjectStruct
|
190
|
+
|
191
|
+
# --- exceptions ---
|
192
|
+
|
193
|
+
attach_variable :PyExc_RuntimeError, PyObjectStruct.ptr
|
194
|
+
attach_variable :PyExc_TypeError, PyObjectStruct.ptr
|
205
195
|
|
206
196
|
# --- functions ---
|
207
197
|
|
@@ -216,6 +206,7 @@ module PyCall
|
|
216
206
|
|
217
207
|
# Object
|
218
208
|
|
209
|
+
attach_function :_PyObject_New, [PyTypeObjectStruct.ptr], PyObjectStruct.ptr
|
219
210
|
attach_function :PyObject_RichCompare, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, :int], PyObjectStruct.by_ref
|
220
211
|
attach_function :PyObject_GetAttrString, [PyObjectStruct.by_ref, :string], PyObjectStruct.by_ref
|
221
212
|
attach_function :PyObject_SetAttrString, [PyObjectStruct.by_ref, :string, PyObjectStruct.by_ref], :int
|
@@ -231,6 +222,10 @@ module PyCall
|
|
231
222
|
attach_function :PyObject_Type, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
|
232
223
|
attach_function :PyCallable_Check, [PyObjectStruct.by_ref], :int
|
233
224
|
|
225
|
+
# Type
|
226
|
+
|
227
|
+
attach_function :PyType_Ready, [PyTypeObjectStruct.ptr], :int
|
228
|
+
|
234
229
|
# Bool
|
235
230
|
|
236
231
|
attach_function :PyBool_FromLong, [:long], PyObjectStruct.by_ref
|
@@ -339,13 +334,23 @@ module PyCall
|
|
339
334
|
attach_function :PySet_Size, [PyObjectStruct.by_ref], :ssize_t
|
340
335
|
attach_function :PySet_Contains, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
|
341
336
|
|
337
|
+
# Method
|
338
|
+
|
339
|
+
attach_function :PyCFunction_NewEx, [PyMethodDef.ptr, :pointer, :pointer], PyObjectStruct.ptr
|
340
|
+
|
341
|
+
# Weakref
|
342
|
+
|
343
|
+
attach_function :PyWeakref_NewRef, [PyObjectStruct.ptr, PyObjectStruct.ptr], PyObjectStruct.ptr
|
344
|
+
|
342
345
|
# Module
|
343
346
|
|
344
347
|
attach_function :PyModule_GetDict, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
|
345
348
|
|
346
349
|
# Import
|
347
350
|
|
351
|
+
attach_function :PyImport_GetModuleDict, [], PyObjectStruct.by_ref
|
348
352
|
attach_function :PyImport_ImportModule, [:string], PyObjectStruct.by_ref
|
353
|
+
attach_function :PyImport_ImportModuleLevel, [:string, PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref, :int], PyObjectStruct.by_ref
|
349
354
|
|
350
355
|
# Operators
|
351
356
|
|
@@ -367,6 +372,7 @@ module PyCall
|
|
367
372
|
attach_function :PyErr_Occurred, [], PyObjectStruct.by_ref
|
368
373
|
attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
|
369
374
|
attach_function :PyErr_NormalizeException, [:pointer, :pointer, :pointer], :void
|
375
|
+
attach_function :PyErr_SetString, [PyObjectStruct.ptr, :string], :void
|
370
376
|
|
371
377
|
public_class_method
|
372
378
|
end
|
data/lib/pycall/list.rb
CHANGED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'pycall'
|
2
|
+
|
3
|
+
module PyCall
|
4
|
+
class RubyWrapStruct < LibPython::PyObjectStruct
|
5
|
+
layout ob_refcnt: :ssize_t,
|
6
|
+
ob_type: LibPython::PyTypeObjectStruct.by_ref,
|
7
|
+
rb_object_id: :ssize_t
|
8
|
+
end
|
9
|
+
|
10
|
+
# This will be called from __initialize_pycall__ defined in pycall/init.rb
|
11
|
+
private_class_method def self.__initialize_ruby_wrapper__()
|
12
|
+
@ruby_wrapper_members = FFI::MemoryPointer.new(LibPython::PyMemberDef.size, 2)
|
13
|
+
LibPython::PyMemberDef.new(@ruby_wrapper_members).tap do |member|
|
14
|
+
member.name = 'rb_object_id'
|
15
|
+
member[:type] = LibPython::T_PYSSIZET
|
16
|
+
member[:offset] = LibPython::PyObjectStruct.size
|
17
|
+
member[:flags] = RubyWrapStruct.offset_of(:rb_object_id)
|
18
|
+
member.doc = "Ruby object ID"
|
19
|
+
end
|
20
|
+
@ruby_wrapper_dealloc = FFI::Function.new(:void, [LibPython::PyObjectStruct.ptr]) do |pyptr|
|
21
|
+
GCGuard.unregister(pyptr)
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
@ruby_wrapper_repr = FFI::Function.new(LibPython::PyObjectStruct.ptr, [LibPython::PyObjectStruct.ptr]) do |pyptr|
|
25
|
+
str = if pyptr.null?
|
26
|
+
'<PyCall.ruby_wrapper NULL>'
|
27
|
+
else
|
28
|
+
obj = ObjectSpace._id2ref(RubyWrapStruct.new(pyptr.pointer)[:rb_object_id])
|
29
|
+
"<PyCall.ruby_wrapper #{obj.inspect}>"
|
30
|
+
end
|
31
|
+
Conversions.from_ruby(str)
|
32
|
+
end
|
33
|
+
@ruby_wrapper_hash = FFI::Function.new(:uint64, [LibPython::PyObjectStruct.ptr]) do |pyptr|
|
34
|
+
h = ObjectSpace._id2ref(RubyWrapStruct.new(pyptr.pointer)[:rb_object_id]).hash
|
35
|
+
h == -1 ? PyCall::HASH_SALT : h
|
36
|
+
end
|
37
|
+
pysalt32 = 0xb592cd9b # This value comes from PyCall.jl
|
38
|
+
@ruby_wrapper_hash32 = FFI::Function.new(:uint32, [LibPython::PyObjectStruct.ptr]) do |pyptr|
|
39
|
+
# int64to32hash from src/support/hashing.c in julia
|
40
|
+
key = ObjectSpace._id2ref(RubyWrapStruct.new(pyptr.pointer)[:rb_object_id]).hash
|
41
|
+
key = (~key) + (key << 18)
|
42
|
+
key = key ^ (key >> 31)
|
43
|
+
key = key * 21
|
44
|
+
key = key ^ (key >> 11)
|
45
|
+
key = key + (key << 6)
|
46
|
+
key = key ^ (key >> 22)
|
47
|
+
h = 0xFFFFFFFF & key
|
48
|
+
h == -1 ? pysalt32 : h
|
49
|
+
end
|
50
|
+
@ruby_callable_call = FFI::Function.new(
|
51
|
+
LibPython::PyObjectStruct.ptr,
|
52
|
+
[LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr]
|
53
|
+
) do |self_, args_, kwargs_|
|
54
|
+
obj = ObjectSpace._id2ref(RubyWrapStruct.new(self_.pointer)[:rb_object_id])
|
55
|
+
begin
|
56
|
+
args = Conversions.to_ruby(args_).to_ary
|
57
|
+
if kwargs_.null?
|
58
|
+
ret = obj.(*args)
|
59
|
+
else
|
60
|
+
kwargs = PyCall::Dict.new(kwargs_).to_hash
|
61
|
+
ret = obj.(*args, **kwargs)
|
62
|
+
end
|
63
|
+
Conversions.from_ruby(ret)
|
64
|
+
rescue Exception => err
|
65
|
+
PyCall.raise_python_exception(err)
|
66
|
+
LibPython::PyObjectStruct.null
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@ruby_callable_getattr = FFI::Function.new(
|
70
|
+
LibPython::PyObjectStruct.ptr,
|
71
|
+
[LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr]
|
72
|
+
) do |self_, attr_|
|
73
|
+
obj = ObjectSpace._id2ref(RubyWrapStruct.new(self_.pointer)[:rb_object_id])
|
74
|
+
attr = Conversions.to_ruby(attr_)
|
75
|
+
begin
|
76
|
+
case attr
|
77
|
+
when '__name__', 'func_name'
|
78
|
+
if obj.respond_to? :name
|
79
|
+
Conversions.from_ruby(obj.name)
|
80
|
+
else
|
81
|
+
Conversions.from_ruby(obj.to_s)
|
82
|
+
end
|
83
|
+
when '__doc__', 'func_doc'
|
84
|
+
# TODO: support docstring
|
85
|
+
PyCall.none
|
86
|
+
when '__module__', '__defaults__', 'func_defaults', '__closure__', 'func_closure'
|
87
|
+
PyCall.none
|
88
|
+
else
|
89
|
+
# TODO: handle __code__ and func_code
|
90
|
+
LibPython.PyObject_GenericGetAttr(self_, attr_)
|
91
|
+
end
|
92
|
+
rescue Exception => err
|
93
|
+
PyCall.raise_python_exception(err)
|
94
|
+
LibPython::PyObjectStruct.null
|
95
|
+
end
|
96
|
+
end
|
97
|
+
@ruby_wrapper = LibPython::PyTypeObjectStruct.new("PyCall.ruby_wrapper", RubyWrapStruct.size) do |t|
|
98
|
+
t[:tp_flags] |= LibPython::Py_TPFLAGS_BASETYPE
|
99
|
+
t[:tp_members] = LibPython::PyMemberDef.new(@ruby_wrapper_members)
|
100
|
+
t[:tp_dealloc] = @ruby_wrapper_dealloc
|
101
|
+
t[:tp_repr] = @ruby_wrapper_repr
|
102
|
+
if FFI.type_size(LibPython.find_type(:Py_hash_t)) < FFI.type_size(:uint64)
|
103
|
+
t[:tp_hash] = @ruby_wrapper_hash32
|
104
|
+
else
|
105
|
+
t[:tp_hash] = @ruby_wrapper_hash
|
106
|
+
end
|
107
|
+
end
|
108
|
+
@ruby_callable = PyCall.ruby_wrapper_subclass_new("PyCall.ruby_callable") do |t|
|
109
|
+
t[:tp_call] = @ruby_callable_call
|
110
|
+
t[:tp_getattro] = @ruby_callable_getattr
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.ruby_wrapper_subclass_new(name)
|
115
|
+
LibPython::PyTypeObjectStruct.new(name, RubyWrapStruct.size + FFI.type_size(:pointer)) do |t|
|
116
|
+
t[:tp_base] = @ruby_wrapper.pointer
|
117
|
+
LibPython.Py_IncRef(LibPython::PyObjectStruct.new(@ruby_wrapper.pointer))
|
118
|
+
yield(t)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.ruby_wrapper_new(type, obj)
|
123
|
+
pyobj = LibPython._PyObject_New(type)
|
124
|
+
RubyWrapStruct.new(pyobj.pointer).tap do |rw|
|
125
|
+
rw[:rb_object_id] = obj.object_id
|
126
|
+
GCGuard.register(rw, obj)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.wrap_ruby_object(obj)
|
131
|
+
ruby_wrapper_new(@ruby_wrapper, obj)
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.wrap_ruby_callable(obj)
|
135
|
+
ruby_wrapper_new(@ruby_callable, obj)
|
136
|
+
end
|
137
|
+
end
|
data/lib/pycall/set.rb
CHANGED
data/lib/pycall/tuple.rb
CHANGED
data/lib/pycall/version.rb
CHANGED
data/lib/pycall.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require "pycall/version"
|
2
2
|
require "pycall/libpython"
|
3
|
+
require "pycall/gc_guard"
|
4
|
+
require "pycall/exception"
|
3
5
|
require "pycall/pyobject_wrapper"
|
4
6
|
require "pycall/pyobject"
|
5
7
|
require "pycall/pyerror"
|
@@ -13,4 +15,5 @@ require "pycall/dict"
|
|
13
15
|
require "pycall/set"
|
14
16
|
require "pycall/slice"
|
15
17
|
require "pycall/utils"
|
18
|
+
require "pycall/ruby_wrapper"
|
16
19
|
require "pycall/init"
|
data/pycall.gemspec
CHANGED