pycall 0.1.0.alpha → 0.1.0.alpha.20170224

Sign up to get free protection for your applications and to get access to all the features.
@@ -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