pycall 0.1.0.alpha → 0.1.0.alpha.20170224

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.
@@ -0,0 +1,90 @@
1
+ module PyCall
2
+ class Dict
3
+ include PyObjectWrapper
4
+
5
+ def self.new(init=nil)
6
+ case init
7
+ when PyObject
8
+ super
9
+ when nil
10
+ new(LibPython.PyDict_New())
11
+ when Hash
12
+ new.tap do |dict|
13
+ init.each do |key, value|
14
+ dict[key] = value
15
+ end
16
+ end
17
+ else
18
+ raise TypeError, "the argument must be a PyObject or a Hash"
19
+ end
20
+ end
21
+
22
+ def initialize(pyobj)
23
+ super(pyobj, LibPython.PyDict_Type)
24
+ end
25
+
26
+ def [](key)
27
+ key = key.to_s if key.is_a? Symbol
28
+ if key.is_a? String
29
+ LibPython.PyDict_GetItemString(__pyobj__, key).to_ruby
30
+ else
31
+ LibPython.PyDict_GetItem(__pyobj__, key).to_ruby
32
+ end
33
+ end
34
+
35
+ def []=(key, value)
36
+ key = key.to_s if key.is_a? Symbol
37
+ value = Conversions.from_ruby(value)
38
+ if key.is_a? String
39
+ LibPython.PyDict_SetItemString(__pyobj__, key, value)
40
+ else
41
+ LibPython.PyDict_SetItem(__pyobj__, key, value)
42
+ end
43
+ value
44
+ end
45
+
46
+ def delete(key)
47
+ key = key.to_s if key.is_a? Symbol
48
+ if key.is_a? String
49
+ value = LibPython.PyDict_GetItemString(__pyobj__, key).to_ruby
50
+ LibPython.PyDict_DelItemString(__pyobj__, key)
51
+ else
52
+ value = LibPython.PyDict_GetItem(__pyobj__, key).to_ruby
53
+ LibPython.PyDict_DelItem(__pyobj__, key)
54
+ end
55
+ value
56
+ end
57
+
58
+ def size
59
+ LibPython.PyDict_Size(__pyobj__)
60
+ end
61
+
62
+ def keys
63
+ LibPython.PyDict_Keys(__pyobj__).to_ruby
64
+ end
65
+
66
+ def values
67
+ LibPython.PyDict_Values(__pyobj__).to_ruby
68
+ end
69
+
70
+ def has_key?(key)
71
+ 1 == LibPython.PyDict_Contains(__pyobj__, key).to_ruby
72
+ end
73
+
74
+ def default=(val)
75
+ # TODO: PYDict_SetDefault
76
+ end
77
+
78
+ def dup
79
+ # TODO: PyDict_Copy
80
+ end
81
+
82
+ def to_a
83
+ LibPython.PyDict_Items(__pyobj__).to_ruby
84
+ end
85
+
86
+ def to_hash
87
+ # TODO: PyDict_Next
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,39 @@
1
+ module PyCall
2
+ module Eval
3
+ Py_eval_input = 258
4
+
5
+ def self.eval(str, filename: "pycall")
6
+ globals_ptr = maindict_ptr
7
+ locals_ptr = maindict_ptr
8
+ defer_sigint do
9
+ py_code_ptr = LibPython.Py_CompileString(str, filename, Py_eval_input)
10
+ LibPython.PyEval_EvalCode(py_code_ptr, globals_ptr, locals_ptr)
11
+ end
12
+ end
13
+
14
+ class << self
15
+ private
16
+
17
+ def maindict_ptr
18
+ LibPython.PyModule_GetDict(PyCall.import_module("__main__"))
19
+ end
20
+
21
+ def defer_sigint
22
+ # TODO: should be implemented
23
+ yield
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.import_module(name)
29
+ name = name.to_s if name.kind_of? Symbol
30
+ raise TypeError, "name must be a String" unless name.kind_of? String
31
+ value = LibPython.PyImport_ImportModule(name)
32
+ return value.to_ruby unless value.null?
33
+ raise PyError.fetch
34
+ end
35
+
36
+ def self.eval(str)
37
+ Eval.eval(str).to_ruby
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ require 'pycall'
2
+
3
+ module PyCall
4
+ module Import
5
+ def self.main_object
6
+ @main_object
7
+ end
8
+ end
9
+ end
10
+
11
+ main_object = self
12
+ PyCall::Import.class_eval { @main_object = main_object }
13
+
14
+ module PyCall
15
+ module Import
16
+ def pyimport(mod_name, as: nil)
17
+ case as
18
+ when nil
19
+ as = mod_name
20
+ end
21
+
22
+ check_valid_module_variable_name(mod_name, as)
23
+
24
+ mod = PyCall.import_module(mod_name)
25
+ define_singleton_method(as) { mod }
26
+ end
27
+
28
+ def pyfrom(mod_name, import: nil)
29
+ raise ArgumentError, "missing identifiers to be imported" unless import
30
+
31
+ mod = PyCall.import_module(mod_name)
32
+ raise "Unable to import module #{mod_name}" unless mod # TODO: PyError
33
+
34
+ case import
35
+ when Hash
36
+ import.each do |attr, as|
37
+ val = PyCall.getattr(mod, attr)
38
+ define_name(as, val)
39
+ end
40
+ when Array
41
+ import.each do |attr|
42
+ val = PyCall.getattr(mod, attr)
43
+ define_name(attr, val)
44
+ end
45
+ when Symbol, String
46
+ val = PyCall.getattr(mod, import)
47
+ define_name(import, val)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def define_name(name, pyobj)
54
+ if constant_name?(name)
55
+ context = self
56
+ context = (self == PyCall::Import.main_object) ? Object : self
57
+ context.module_eval { const_set(name, pyobj) }
58
+ else
59
+ define_singleton_method(name) { pyobj }
60
+ end
61
+ end
62
+
63
+ def constant_name?(name)
64
+ name =~ /\A[A-Z]/
65
+ end
66
+
67
+ def check_valid_module_variable_name(mod_name, var_name)
68
+ var_name = var_name.to_s if var_name.kind_of? Symbol
69
+ if var_name.include?('.')
70
+ raise ArgumentError, "#{var_name} is not a valid module variable name, use pyimport #{mod_name}, as: <name>"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,16 @@
1
+ module PyCall
2
+ private_class_method def self.__initialize_pycall__
3
+ initialized = (0 != PyCall::LibPython.Py_IsInitialized())
4
+ return if initialized
5
+
6
+ PyCall::LibPython.Py_InitializeEx(0)
7
+
8
+ @builtin = LibPython.PyImport_ImportModule(PYTHON_VERSION < '3.0.0' ? '__builtin__' : 'builtins')
9
+ end
10
+
11
+ class << self
12
+ attr_reader :builtin
13
+ end
14
+
15
+ __initialize_pycall__
16
+ end
@@ -0,0 +1,331 @@
1
+ require 'ffi'
2
+
3
+ module PyCall
4
+ class PyObject < FFI::Struct
5
+ layout ob_refcnt: :ssize_t,
6
+ ob_type: :pointer
7
+ end
8
+
9
+ class PyTypeObject < FFI::Struct
10
+ layout ob_base: PyObject,
11
+ ob_size: :ssize_t,
12
+ tp_name: :string
13
+ end
14
+
15
+ module LibPython
16
+ extend FFI::Library
17
+
18
+ private_class_method
19
+
20
+ def self.find_libpython(python = nil)
21
+ python ||= 'python'
22
+ python_config = investigate_python_config(python)
23
+
24
+ v = python_config[:VERSION]
25
+ libprefix = FFI::Platform::LIBPREFIX
26
+ libs = [ "#{libprefix}python#{v}", "#{libprefix}python" ]
27
+ lib = python_config[:LIBRARY]
28
+ libs.unshift(File.basename(lib, File.extname(lib))) if lib
29
+ lib = python_config[:LDLIBRARY]
30
+ libs.unshift(lib, File.basename(lib)) if lib
31
+ libs.uniq!
32
+
33
+ executable = python_config[:executable]
34
+ libpaths = [ python_config[:LIBDIR] ]
35
+ if FFI::Platform.windows?
36
+ libpaths << dirname(executable)
37
+ else
38
+ libpaths << File.expand_path('../../lib', executable)
39
+ end
40
+ libpaths << python_config[:PYTHONFRAMEWORKPREFIX] if FFI::Platform.mac?
41
+
42
+ exec_prefix = python_config[:exec_prefix]
43
+ libpaths << exec_prefix << File.join(exec_prefix, 'lib')
44
+
45
+ unless ENV['PYTHONHOME']
46
+ # PYTHONHOME tells python where to look for both pure python and binary modules.
47
+ # When it is set, it replaces both `prefix` and `exec_prefix`
48
+ # and we thus need to set it to both in case they differ.
49
+ # This is also what the documentation recommends.
50
+ # However, they are documented to always be the same on Windows,
51
+ # where it causes problems if we try to include both.
52
+ if FFI::Platform.windows?
53
+ ENV['PYTHONHOME'] = exec_prefix
54
+ else
55
+ ENV['PYTHONHOME'] = [python_config[:prefix], exec_prefix].join(':')
56
+ end
57
+
58
+ # Unfortunately, setting PYTHONHOME screws up Canopy's Python distribution?
59
+ unless system(python, '-c', 'import site', out: File::NULL, err: File::NULL)
60
+ ENV['PYTHONHOME'] = nil
61
+ end
62
+ end
63
+
64
+ # Find libpython (we hope):
65
+ libsuffix = FFI::Platform::LIBSUFFIX
66
+ libs.each do |lib|
67
+ libpaths.each do |libpath|
68
+ libpath_lib = File.join(libpath, lib)
69
+ if File.file?("#{libpath_lib}.#{libsuffix}")
70
+ libs = ffi_lib("#{libpath_lib}.#{libsuffix}")
71
+ return libs.first
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def self.investigate_python_config(python)
78
+ python_env = { 'PYTHONIOENCODING' => 'UTF-8' }
79
+ IO.popen(python_env, [python, python_investigator_py], 'r') do |io|
80
+ {}.tap do |config|
81
+ io.each_line do |line|
82
+ key, value = line.chomp.split(': ', 2)
83
+ config[key.to_sym] = value
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def self.python_investigator_py
90
+ File.expand_path('../python/investigator.py', __FILE__)
91
+ end
92
+
93
+ ffi_lib_flags :lazy, :global
94
+ libpython = find_libpython ENV['PYTHON']
95
+
96
+ # --- global variables ---
97
+
98
+ attach_variable :_Py_NoneStruct, PyObject
99
+
100
+ def self.Py_None
101
+ _Py_NoneStruct
102
+ end
103
+
104
+ if libpython.find_variable('PyInt_Type')
105
+ has_PyInt_Type = true
106
+ attach_variable :PyInt_Type, PyTypeObject
107
+ else
108
+ has_PyInt_Type = false
109
+ attach_variable :PyInt_Type, :PyLong_Type, PyTypeObject
110
+ end
111
+
112
+ attach_variable :PyLong_Type, PyTypeObject
113
+ attach_variable :PyBool_Type, PyTypeObject
114
+ attach_variable :PyFloat_Type, PyTypeObject
115
+ attach_variable :PyComplex_Type, PyTypeObject
116
+ attach_variable :PyUnicode_Type, PyTypeObject
117
+
118
+ if libpython.find_symbol('PyString_FromStringAndSize')
119
+ string_as_bytes = false
120
+ attach_variable :PyString_Type, PyTypeObject
121
+ else
122
+ string_as_bytes = true
123
+ attach_variable :PyString_Type, :PyBytes_Type, PyTypeObject
124
+ end
125
+
126
+ attach_variable :PyList_Type, PyTypeObject
127
+ attach_variable :PyTuple_Type, PyTypeObject
128
+ attach_variable :PyDict_Type, PyTypeObject
129
+ attach_variable :PySet_Type, PyTypeObject
130
+
131
+ # --- functions ---
132
+
133
+ attach_function :Py_GetVersion, [], :string
134
+
135
+ # Py_InitializeEx :: (int) -> void
136
+ attach_function :Py_InitializeEx, [:int], :void
137
+
138
+ # Py_IsInitialized :: () -> int
139
+ attach_function :Py_IsInitialized, [], :int
140
+
141
+ # Comparing two objects
142
+ attach_function :PyObject_RichCompare, [PyObject.by_ref, PyObject.by_ref, :int], PyObject.by_ref
143
+
144
+ # Accessing Object's attributes
145
+ attach_function :PyObject_GetAttrString, [PyObject.by_ref, :string], PyObject.by_ref
146
+ attach_function :PyObject_SetAttrString, [PyObject.by_ref, :string, PyObject.by_ref], :int
147
+ attach_function :PyObject_HasAttrString, [PyObject.by_ref, :string], :int
148
+
149
+ # Accessing Object's items
150
+ attach_function :PyObject_GetItem, [PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
151
+ attach_function :PyObject_SetItem, [PyObject.by_ref, PyObject.by_ref, PyObject.by_ref], :int
152
+ attach_function :PyObject_DelItem, [PyObject.by_ref, PyObject.by_ref], :int
153
+
154
+ # Calling a object as a function
155
+ attach_function :PyObject_Call, [PyObject.by_ref, PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
156
+
157
+ # PyObject_IsInstane :: (PyPtr, PyPtr) -> int
158
+ attach_function :PyObject_IsInstance, [PyObject.by_ref, PyTypeObject.by_ref], :int
159
+
160
+ # PyBool_FromLong :: (long) -> PyPtr
161
+ attach_function :PyBool_FromLong, [:long], PyObject.by_ref
162
+
163
+ # PyInt_AsSsize_t :: (PyPtr) -> ssize_t
164
+ if has_PyInt_Type
165
+ attach_function :PyInt_AsSsize_t, [PyObject.by_ref], :ssize_t
166
+ else
167
+ attach_function :PyInt_AsSsize_t, :PyLong_AsSsize_t, [PyObject.by_ref], :ssize_t
168
+ end
169
+
170
+ # PyInt_FromSsize_t :: (ssize_t) -> PyPtr
171
+ if has_PyInt_Type
172
+ attach_function :PyInt_FromSsize_t, [:ssize_t], PyObject.by_ref
173
+ else
174
+ attach_function :PyInt_FromSsize_t, :PyLong_FromSsize_t, [:ssize_t], PyObject.by_ref
175
+ end
176
+
177
+ # PyFloat_FromDouble :: (double) -> PyPtr
178
+ attach_function :PyFloat_FromDouble, [:double], PyObject.by_ref
179
+
180
+ # PyFloat_AsDouble :: (PyPtr) -> double
181
+ attach_function :PyFloat_AsDouble, [PyObject.by_ref], :double
182
+
183
+ # PyComplex_RealAsDouble :: (PyPtr) -> double
184
+ attach_function :PyComplex_RealAsDouble, [PyObject.by_ref], :double
185
+
186
+ # PyComplex_ImagAsDouble :: (PyPtr) -> double
187
+ attach_function :PyComplex_ImagAsDouble, [PyObject.by_ref], :double
188
+
189
+ # String
190
+
191
+ if string_as_bytes
192
+ attach_function :PyString_FromStringAndSize, :PyBytes_FromStringAndSize, [:string, :ssize_t], PyObject.by_ref
193
+ else
194
+ attach_function :PyString_FromStringAndSize, [:string, :ssize_t], PyObject.by_ref
195
+ end
196
+
197
+ # PyString_AsStringAndSize :: (PyPtr, char**, int*) -> int
198
+ if string_as_bytes
199
+ attach_function :PyString_AsStringAndSize, :PyBytes_AsStringAndSize, [PyObject.by_ref, :pointer, :pointer], :int
200
+ else
201
+ attach_function :PyString_AsStringAndSize, [PyObject.by_ref, :pointer, :pointer], :int
202
+ end
203
+
204
+ # Unicode
205
+
206
+ # PyUnicode_DecodeUTF8
207
+ case
208
+ when libpython.find_symbol('PyUnicode_DecodeUTF8')
209
+ attach_function :PyUnicode_DecodeUTF8, [:string, :ssize_t, :string], PyObject.by_ref
210
+ when libpython.find_symbol('PyUnicodeUCS4_DecodeUTF8')
211
+ attach_function :PyUnicodeUCS4_DecodeUTF8, [:string, :ssize_t, :string], PyObject.by_ref
212
+ when libpython.find_symbol('PyUnicodeUCS2_DecodeUTF8')
213
+ attach_function :PyUnicodeUCS2_DecodeUTF8, [:string, :ssize_t, :string], PyObject.by_ref
214
+ end
215
+
216
+ # PyUnicode_AsUTF8String :: (PyPtr) -> PyPtr
217
+ case
218
+ when libpython.find_symbol('PyUnicode_AsUTF8String')
219
+ attach_function :PyUnicode_AsUTF8String, [PyObject.by_ref], PyObject.by_ref
220
+ when libpython.find_symbol('PyUnicodeUCS4_AsUTF8String')
221
+ attach_function :PyUnicode_AsUTF8String, :PyUnicodeUCS4_AsUTF8String, [PyObject.by_ref], PyObject.by_ref
222
+ when libpython.find_symbol('PyUnicodeUCS2_AsUTF8String')
223
+ attach_function :PyUnicode_AsUTF8String, :PyUnicodeUCS2_AsUTF8String, [PyObject.by_ref], PyObject.by_ref
224
+ end
225
+
226
+ # Tuple
227
+
228
+ attach_function :PyTuple_New, [:ssize_t], PyObject.by_ref
229
+ attach_function :PyTuple_GetItem, [PyObject.by_ref, :ssize_t], PyObject.by_ref
230
+ attach_function :PyTuple_SetItem, [PyObject.by_ref, :ssize_t, PyObject.by_ref], :int
231
+ attach_function :PyTuple_Size, [PyObject.by_ref], :ssize_t
232
+
233
+ # Slice
234
+
235
+ attach_function :PySlice_New, [PyObject.by_ref, PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
236
+
237
+ # List
238
+
239
+ attach_function :PyList_New, [:ssize_t], PyObject.by_ref
240
+ attach_function :PyList_Size, [PyObject.by_ref], :ssize_t
241
+ attach_function :PyList_GetItem, [PyObject.by_ref, :ssize_t], PyObject.by_ref
242
+ attach_function :PyList_SetItem, [PyObject.by_ref, :ssize_t, PyObject.by_ref], :int
243
+ attach_function :PyList_Append, [PyObject.by_ref, PyObject.by_ref], :int
244
+
245
+ # PySequence_Size :: (PyPtr) -> ssize_t
246
+ attach_function :PySequence_Size, [PyObject.by_ref], :ssize_t
247
+
248
+ # PySequence_GetItem :: (PyPtr, ssize_t) -> PyPtr
249
+ attach_function :PySequence_GetItem, [PyObject.by_ref, :ssize_t], PyObject.by_ref
250
+
251
+ # Dict
252
+
253
+ attach_function :PyDict_New, [], PyObject.by_ref
254
+
255
+ # PyDict_GetItem :: (PyPtr, PyPtr) -> PyPtr
256
+ attach_function :PyDict_GetItem, [PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
257
+
258
+ # PyDict_GetItemString :: (PyPtr, char const*) -> PyPtr
259
+ attach_function :PyDict_GetItemString, [PyObject.by_ref, :string], PyObject.by_ref
260
+
261
+ # PyDict_SetItem :: (PyPtr, PyPtr, PyPtr) -> int
262
+ attach_function :PyDict_SetItem, [PyObject.by_ref, PyObject.by_ref, PyObject.by_ref], :int
263
+
264
+ # PyDict_SetItemString :: (PyPtr, char const*, PyPtr) -> int
265
+ attach_function :PyDict_SetItemString, [PyObject.by_ref, :string, PyObject.by_ref], :int
266
+
267
+ # PyDict_DelItem :: (PyPtr, PyPtr) -> int
268
+ attach_function :PyDict_DelItem, [PyObject.by_ref, PyObject.by_ref], :int
269
+
270
+ # PyDict_DelItemString :: (PyPtr, char const*) -> int
271
+ attach_function :PyDict_DelItem, [PyObject.by_ref, :string], :int
272
+
273
+ # PyDict_Size :: (PyPtr) -> ssize_t
274
+ attach_function :PyDict_Size, [PyObject.by_ref], :ssize_t
275
+
276
+ # PyDict_Keys :: (PyPtr) -> PyPtr
277
+ attach_function :PyDict_Keys, [PyObject.by_ref], PyObject.by_ref
278
+
279
+ # PyDict_Values :: (PyPtr) -> PyPtr
280
+ attach_function :PyDict_Values, [PyObject.by_ref], PyObject.by_ref
281
+
282
+ # PyDict_Items :: (PyPtr) -> PyPtr
283
+ attach_function :PyDict_Items, [PyObject.by_ref], PyObject.by_ref
284
+
285
+ # PyDict_Contains :: (PyPtr, PyPtr) -> int
286
+ attach_function :PyDict_Contains, [PyObject.by_ref, PyObject.by_ref], :int
287
+
288
+ # PySet_Size :: (PyPtr) -> ssize_t
289
+ attach_function :PySet_Size, [PyObject.by_ref], :ssize_t
290
+
291
+ # PySet_Contains :: (PyPtr, PyPtr) -> int
292
+ attach_function :PySet_Contains, [PyObject.by_ref, PyObject.by_ref], :int
293
+
294
+ # PySet_Add :: (PyPtr, PyPtr) -> int
295
+ attach_function :PySet_Add, [PyObject.by_ref, PyObject.by_ref], :int
296
+
297
+ # PySet_Discard :: (PyPtr, PyPtr) -> int
298
+ attach_function :PySet_Discard, [PyObject.by_ref, PyObject.by_ref], :int
299
+
300
+ # PyModule_GetDict :: (PyPtr) -> PyPtr
301
+ attach_function :PyModule_GetDict, [PyObject.by_ref], PyObject.by_ref
302
+
303
+ # PyImport_ImportModule :: (char const*) -> PyPtr
304
+ attach_function :PyImport_ImportModule, [:string], PyObject.by_ref
305
+
306
+ # Operators
307
+
308
+ attach_function :PyNumber_Add, [PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
309
+ attach_function :PyNumber_Subtract, [PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
310
+ attach_function :PyNumber_Multiply, [PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
311
+ attach_function :PyNumber_TrueDivide, [PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
312
+
313
+ # Py_CompileString :: (char const*, char const*, int) -> PyPtr
314
+ attach_function :Py_CompileString, [:string, :string, :int], PyObject.by_ref
315
+
316
+ # PyEval_EvalCode :: (PyPtr, PyPtr, PyPtr) -> PyPtr
317
+ attach_function :PyEval_EvalCode, [PyObject.by_ref, PyObject.by_ref, PyObject.by_ref], PyObject.by_ref
318
+
319
+ # Error
320
+
321
+ attach_function :PyErr_Print, [], :void
322
+ attach_function :PyErr_Occurred, [], PyObject.by_ref
323
+ attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
324
+ attach_function :PyErr_NormalizeException, [:pointer, :pointer, :pointer], :void
325
+
326
+ public_class_method
327
+ end
328
+
329
+ PYTHON_DESCRIPTION = LibPython.Py_GetVersion().freeze
330
+ PYTHON_VERSION = PYTHON_DESCRIPTION.split(' ', 2)[0].freeze
331
+ end