pycall 0.1.0.alpha.20170502 → 0.1.0.alpha.20170711

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/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
@@ -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, PyObjectStruct
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, PyObjectStruct
163
+ attach_variable :PyInt_Type, PyTypeObjectStruct
179
164
  else
180
165
  has_PyInt_Type = false
181
- attach_variable :PyInt_Type, :PyLong_Type, PyObjectStruct
166
+ attach_variable :PyInt_Type, :PyLong_Type, PyTypeObjectStruct
182
167
  end
183
168
 
184
- attach_variable :PyLong_Type, PyObjectStruct
185
- attach_variable :PyBool_Type, PyObjectStruct
186
- attach_variable :PyFloat_Type, PyObjectStruct
187
- attach_variable :PyComplex_Type, PyObjectStruct
188
- attach_variable :PyUnicode_Type, PyObjectStruct
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, PyObjectStruct
177
+ attach_variable :PyString_Type, PyTypeObjectStruct
193
178
  else
194
179
  string_as_bytes = true
195
- attach_variable :PyString_Type, :PyBytes_Type, PyObjectStruct
180
+ attach_variable :PyString_Type, :PyBytes_Type, PyTypeObjectStruct
196
181
  end
197
182
 
198
- attach_variable :PyList_Type, PyObjectStruct
199
- attach_variable :PyTuple_Type, PyObjectStruct
200
- attach_variable :PyDict_Type, PyObjectStruct
201
- attach_variable :PySet_Type, PyObjectStruct
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, PyObjectStruct
204
- attach_variable :PyMethod_Type, PyObjectStruct
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
@@ -32,6 +32,8 @@ module PyCall
32
32
  LibPython.PyList_Size(__pyobj__)
33
33
  end
34
34
 
35
+ alias length size
36
+
35
37
  def include?(value)
36
38
  value = Conversions.from_ruby(value)
37
39
  value = LibPython.PySequence_Contains(__pyobj__, value)
@@ -1,4 +1,6 @@
1
1
  module PyCall
2
+ HASH_SALT = "PyCall::PyObject".hash
3
+
2
4
  Py_LT = 0
3
5
  Py_LE = 1
4
6
  Py_EQ = 2
@@ -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
@@ -6,10 +6,12 @@ module PyCall
6
6
  super(pyobj)
7
7
  end
8
8
 
9
- def length
9
+ def size
10
10
  LibPython.PySet_Size(__pyobj__)
11
11
  end
12
12
 
13
+ alias length size
14
+
13
15
  def include?(obj)
14
16
  1 == LibPython.PySet_Contains(__pyobj__, Conversions.from_ruby(obj))
15
17
  end
data/lib/pycall/tuple.rb CHANGED
@@ -22,10 +22,12 @@ module PyCall
22
22
  new(ary)
23
23
  end
24
24
 
25
- def length
25
+ def size
26
26
  LibPython.PyTuple_Size(__pyobj__)
27
27
  end
28
28
 
29
+ alias length size
30
+
29
31
  def [](index)
30
32
  LibPython.PyTuple_GetItem(__pyobj__, index).to_ruby
31
33
  end
@@ -1,3 +1,3 @@
1
1
  module PyCall
2
- VERSION = "0.1.0.alpha.20170502"
2
+ VERSION = "0.1.0.alpha.20170711"
3
3
  end
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
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency "rake", "~> 10.0"
35
35
  spec.add_development_dependency "rspec", "~> 3.0"
36
36
  spec.add_development_dependency "launchy"
37
+ spec.add_development_dependency "pry"
37
38
  end
data/tasks/docker.rake CHANGED
@@ -5,6 +5,7 @@ namespace :docker do
5
5
  end
6
6
  end
7
7
 
8
+ desc 'Run docker container [port=8888] [attach_local=$(pwd)]'
8
9
  task :run do
9
10
  require 'securerandom'
10
11
  require 'launchy'