pycall 1.0.1-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +41 -0
  5. data/CHANGES.md +39 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +91 -0
  9. data/Rakefile +29 -0
  10. data/appveyor.yml +138 -0
  11. data/bin/console +10 -0
  12. data/bin/guard +17 -0
  13. data/bin/rspec +17 -0
  14. data/bin/runner +6 -0
  15. data/bin/setup +8 -0
  16. data/config/Guardfile +30 -0
  17. data/docker/Dockerfile +191 -0
  18. data/docker/Gemfile +12 -0
  19. data/docker/README.md +22 -0
  20. data/examples/classifier_comparison.rb +135 -0
  21. data/examples/datascience_rb_20170519.ipynb +4836 -0
  22. data/examples/hist.rb +32 -0
  23. data/examples/notebooks/classifier_comparison.ipynb +226 -0
  24. data/examples/notebooks/forest_importances.ipynb +238 -0
  25. data/examples/notebooks/iruby_integration.ipynb +183 -0
  26. data/examples/notebooks/lorenz_attractor.ipynb +214 -0
  27. data/examples/notebooks/polar_axes.ipynb +209 -0
  28. data/examples/notebooks/sum_benchmarking.ipynb +374 -0
  29. data/examples/notebooks/xkcd_style.ipynb +149 -0
  30. data/examples/plot_forest_importances_faces.rb +46 -0
  31. data/examples/sum_benchmarking.rb +49 -0
  32. data/ext/pycall/extconf.rb +3 -0
  33. data/ext/pycall/gc.c +74 -0
  34. data/ext/pycall/libpython.c +217 -0
  35. data/ext/pycall/pycall.c +2184 -0
  36. data/ext/pycall/pycall_internal.h +700 -0
  37. data/ext/pycall/range.c +69 -0
  38. data/ext/pycall/ruby_wrapper.c +432 -0
  39. data/lib/pycall.rb +91 -0
  40. data/lib/pycall/conversion.rb +173 -0
  41. data/lib/pycall/dict.rb +48 -0
  42. data/lib/pycall/error.rb +10 -0
  43. data/lib/pycall/gc_guard.rb +84 -0
  44. data/lib/pycall/import.rb +120 -0
  45. data/lib/pycall/init.rb +55 -0
  46. data/lib/pycall/iruby_helper.rb +40 -0
  47. data/lib/pycall/libpython.rb +12 -0
  48. data/lib/pycall/libpython/finder.rb +170 -0
  49. data/lib/pycall/libpython/pyobject_struct.rb +30 -0
  50. data/lib/pycall/libpython/pytypeobject_struct.rb +273 -0
  51. data/lib/pycall/list.rb +45 -0
  52. data/lib/pycall/pretty_print.rb +9 -0
  53. data/lib/pycall/pyerror.rb +30 -0
  54. data/lib/pycall/pyobject_wrapper.rb +212 -0
  55. data/lib/pycall/python/PyCall/__init__.py +1 -0
  56. data/lib/pycall/python/PyCall/six.py +23 -0
  57. data/lib/pycall/python/investigator.py +7 -0
  58. data/lib/pycall/pytypeobject_wrapper.rb +90 -0
  59. data/lib/pycall/set.rb +19 -0
  60. data/lib/pycall/slice.rb +8 -0
  61. data/lib/pycall/tuple.rb +46 -0
  62. data/lib/pycall/version.rb +3 -0
  63. data/lib/pycall/wrapper_object_cache.rb +61 -0
  64. data/pycall.gemspec +40 -0
  65. data/tasks/docker.rake +21 -0
  66. data/tasks/pycall.rake +7 -0
  67. metadata +228 -0
data/lib/pycall.rb ADDED
@@ -0,0 +1,91 @@
1
+ module PyCall
2
+ require 'pycall/version'
3
+ require 'pycall/libpython'
4
+ require 'pycall/pyerror'
5
+ require 'pycall/pyobject_wrapper'
6
+ require 'pycall/pytypeobject_wrapper'
7
+ require 'pycall/init'
8
+
9
+ module_function
10
+
11
+ def builtins
12
+ @builtins ||= wrap_module(LibPython::API.builtins_module_ptr)
13
+ end
14
+
15
+ def callable?(obj)
16
+ case obj
17
+ when PyObjectWrapper
18
+ builtins.callable(obj.__pyptr__)
19
+ when PyPtr
20
+ builtins.callable(obj)
21
+ else
22
+ raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
23
+ end
24
+ end
25
+
26
+ def dir(obj)
27
+ case obj
28
+ when PyObjectWrapper
29
+ builtins.dir(obj.__pyptr__)
30
+ when PyPtr
31
+ builtins.dir(obj)
32
+ else
33
+ raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
34
+ end
35
+ end
36
+
37
+ def eval(expr, globals: nil, locals: nil)
38
+ globals ||= import_module(:__main__).__dict__
39
+ builtins.eval(expr, globals, locals)
40
+ end
41
+
42
+ def exec(code, globals: nil, locals: nil)
43
+ globals ||= import_module(:__main__).__dict__
44
+ if PYTHON_VERSION >= '3'
45
+ builtins.exec(code, globals, locals)
46
+ else
47
+ import_module('PyCall.six').exec_(code, globals, locals)
48
+ end
49
+ end
50
+
51
+ def import_module(name)
52
+ LibPython::Helpers.import_module(name)
53
+ end
54
+
55
+ def len(obj)
56
+ case obj
57
+ when PyObjectWrapper
58
+ builtins.len(obj.__pyptr__)
59
+ when PyPtr
60
+ builtins.len(obj)
61
+ else
62
+ raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
63
+ end
64
+ end
65
+
66
+ def sys
67
+ @sys ||= import_module('sys')
68
+ end
69
+
70
+ def tuple(iterable=nil)
71
+ pyptr = if iterable
72
+ builtins.tuple.(iterable)
73
+ else
74
+ builtins.tuple.()
75
+ end
76
+ Tuple.wrap_pyptr(pyptr)
77
+ end
78
+
79
+ def with(ctx)
80
+ begin
81
+ yield ctx.__enter__
82
+ rescue PyError => err
83
+ raise err unless ctx.__exit__(err.type, err.value, err.traceback)
84
+ rescue Exception => err
85
+ # TODO: support telling what exception has been catched
86
+ raise err unless ctx.__exit__(err.class, err, err.backtrace_locations)
87
+ else
88
+ ctx.__exit__(nil, nil, nil)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,173 @@
1
+ module PyCall
2
+ module Conversions
3
+ @python_type_map = []
4
+
5
+ class TypePair < Struct.new(:pytype, :rbtype)
6
+ def to_a
7
+ [pytype, rbtype]
8
+ end
9
+ end
10
+
11
+ def self.each_type_pair
12
+ i, n = 1, @python_type_map.length
13
+ while i <= n
14
+ yield @python_type_map[n - i]
15
+ i += 1
16
+ end
17
+ self
18
+ end
19
+
20
+ def self.python_type_mapping(pytype, rbtype)
21
+ each_type_pair do |type_pair|
22
+ next unless pytype == type_pair.pytype
23
+ type_pair.rbtype = rbtype
24
+ return
25
+ end
26
+ @python_type_map << TypePair.new(pytype, rbtype)
27
+ end
28
+
29
+ # Convert a PyCall::PyObjectStruct object to a Ruby object
30
+ #
31
+ # @param [PyCall::PyObjectStruct] pyptr a PyObjectStruct object.
32
+ #
33
+ # @return a Ruby object converted from `pyptr`.
34
+ def self.to_ruby(pyptr)
35
+ return nil if pyptr.null? || PyCall.none?(pyptr)
36
+
37
+ case
38
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyType_Type)
39
+ return TypeObject.new(pyptr)
40
+
41
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyBool_Type)
42
+ return Conversions.convert_to_boolean(pyptr)
43
+
44
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyInt_Type)
45
+ return Conversions.convert_to_integer(pyptr)
46
+
47
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyLong_Type)
48
+ # TODO: should make Bignum
49
+
50
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyFloat_Type)
51
+ return Conversions.convert_to_float(pyptr)
52
+
53
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyComplex_Type)
54
+ return Conversions.convert_to_complex(pyptr)
55
+
56
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyString_Type)
57
+ return Conversions.convert_to_string(pyptr)
58
+
59
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyUnicode_Type)
60
+ py_str_ptr = LibPython.PyUnicode_AsUTF8String(pyptr)
61
+ return Conversions.convert_to_string(py_str_ptr).force_encoding(Encoding::UTF_8)
62
+
63
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyList_Type)
64
+ return PyCall::List.new(pyptr)
65
+
66
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyTuple_Type)
67
+ return Conversions.convert_to_tuple(pyptr)
68
+
69
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PyDict_Type)
70
+ return PyCall::Dict.new(pyptr)
71
+
72
+ when PyCall::Types.pyisinstance(pyptr, LibPython.PySet_Type)
73
+ return PyCall::Set.new(pyptr)
74
+ end
75
+
76
+ pyobj = PyObject.new(pyptr)
77
+ each_type_pair do |tp|
78
+ pytype, rbtype = tp.to_a
79
+ next unless pyobj.kind_of?(pytype)
80
+ case
81
+ when rbtype.kind_of?(Proc)
82
+ return rbtype.(pyobj)
83
+ when rbtype.respond_to?(:from_python)
84
+ return rbtype.from_python(pyobj)
85
+ else
86
+ return rbtype.new(pyobj)
87
+ end
88
+ end
89
+ pyobj
90
+ end
91
+
92
+ def self.from_ruby(obj)
93
+ case obj
94
+ when LibPython::PyObjectStruct
95
+ obj
96
+ when PyObject, PyObjectWrapper
97
+ obj.__pyobj__
98
+ when TrueClass, FalseClass
99
+ LibPython.PyBool_FromLong(obj ? 1 : 0)
100
+ when Integer
101
+ LibPython.PyInt_FromSsize_t(obj)
102
+ when Float
103
+ LibPython.PyFloat_FromDouble(obj)
104
+ when String
105
+ if obj.encoding != Encoding::BINARY && (PyCall.unicode_literals? || !obj.ascii_only?)
106
+ obj = obj.encode(Encoding::UTF_8) if obj.encoding != Encoding::UTF_8
107
+ return LibPython.PyUnicode_DecodeUTF8(obj, obj.bytesize, nil)
108
+ end
109
+ LibPython.PyString_FromStringAndSize(obj, obj.bytesize)
110
+ when Symbol
111
+ from_ruby(obj.to_s)
112
+ when Array
113
+ PyCall::List.new(obj).__pyobj__
114
+ when Hash
115
+ PyCall::Dict.new(obj).__pyobj__
116
+ when Proc
117
+ PyCall.wrap_ruby_callable(obj)
118
+ else
119
+ PyCall.None
120
+ end
121
+ end
122
+
123
+ def self.convert_to_boolean(py_obj)
124
+ 0 != LibPython.PyInt_AsSsize_t(py_obj)
125
+ end
126
+
127
+ def self.convert_to_integer(py_obj)
128
+ LibPython.PyInt_AsSsize_t(py_obj)
129
+ end
130
+
131
+ def self.convert_to_float(py_obj)
132
+ LibPython.PyFloat_AsDouble(py_obj)
133
+ end
134
+
135
+ def self.convert_to_complex(py_obj)
136
+ real = LibPython.PyComplex_RealAsDouble(py_obj)
137
+ imag = LibPython.PyComplex_ImagAsDouble(py_obj)
138
+ Complex(real, imag)
139
+ end
140
+
141
+ def self.convert_to_string(py_obj)
142
+ FFI::MemoryPointer.new(:string) do |str_ptr|
143
+ FFI::MemoryPointer.new(:int) do |len_ptr|
144
+ res = LibPython.PyString_AsStringAndSize(py_obj, str_ptr, len_ptr)
145
+ return nil if res == -1 # FIXME: error
146
+
147
+ len = len_ptr.get(:int, 0)
148
+ return str_ptr.get_pointer(0).read_string(len)
149
+ end
150
+ end
151
+ end
152
+
153
+ def self.convert_to_array(py_obj, force_list: true, array_class: Array)
154
+ case
155
+ when force_list || py_obj.kind_of?(LibPython.PyList_Type)
156
+ len = LibPython.PySequence_Size(py_obj)
157
+ array_class.new(len) do |i|
158
+ LibPython.PySequence_GetItem(py_obj, i).to_ruby
159
+ end
160
+ end
161
+ end
162
+
163
+ def self.convert_to_tuple(py_obj)
164
+ PyCall::Tuple.new(py_obj)
165
+ end
166
+ end
167
+
168
+ class LibPython::PyObjectStruct
169
+ def to_ruby
170
+ Conversions.to_ruby(self)
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,48 @@
1
+ module PyCall
2
+ Dict = builtins.dict
3
+ class Dict
4
+ register_python_type_mapping
5
+
6
+ include Enumerable
7
+
8
+ def self.new(h)
9
+ super(h, {})
10
+ end
11
+
12
+ def length
13
+ PyCall.len(self)
14
+ end
15
+
16
+ def has_key?(key)
17
+ LibPython::Helpers.dict_contains(__pyptr__, key)
18
+ end
19
+
20
+ alias include? has_key?
21
+ alias key? has_key?
22
+ alias member? has_key?
23
+
24
+ def [](key)
25
+ super
26
+ rescue PyError
27
+ nil
28
+ end
29
+
30
+ def delete(key)
31
+ v = self[key]
32
+ LibPython::Helpers.delitem(__pyptr__, key)
33
+ v
34
+ end
35
+
36
+ def each
37
+ return enum_for unless block_given?
38
+ LibPython::Helpers.dict_each(__pyptr__, &proc)
39
+ self
40
+ end
41
+
42
+ def to_h
43
+ inject({}) do |h, (k, v)|
44
+ h.update(k => v)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,10 @@
1
+ module PyCall
2
+ class Error < StandardError
3
+ end
4
+
5
+ class PythonNotFound < Error
6
+ end
7
+
8
+ class LibPythonFunctionNotFound < Error
9
+ end
10
+ end
@@ -0,0 +1,84 @@
1
+ require 'pycall'
2
+
3
+ module PyCall
4
+ module GCGuard
5
+ @gc_guard = {}
6
+
7
+ class Key < Struct.new(:pyptr)
8
+ def initialize(pyptr)
9
+ self.pyptr = check_pyptr(pyptr)
10
+ # LibPython.Py_IncRef(pyptr)
11
+ end
12
+
13
+ def release
14
+ # LibPython.Py_DecRef(pyptr)
15
+ self.pyptr = nil
16
+ end
17
+
18
+ def ==(other)
19
+ case other
20
+ when Key
21
+ pyptr.pointer == other.pyptr.pointer
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ alias :eql? :==
28
+
29
+ def hash
30
+ pyptr.pointer.address
31
+ end
32
+
33
+ private
34
+
35
+ def check_pyptr(pyptr)
36
+ pyptr = pyptr.__pyobj__ if pyptr.respond_to? :__pyobj__
37
+ return pyptr if pyptr.kind_of? LibPython::PyObjectStruct
38
+ raise TypeError, "The argument must be a Python object"
39
+ end
40
+ end
41
+
42
+ def self.register(pyobj, obj)
43
+ key = Key.new(pyobj)
44
+ @gc_guard[key] ||= []
45
+ @gc_guard[key] << obj
46
+ end
47
+
48
+ def self.unregister(pyobj)
49
+ key = Key.new(pyobj)
50
+ @gc_guard.delete(key).tap { key.release }
51
+ end
52
+
53
+ def self.guarded_object_count
54
+ @gc_guard.length
55
+ end
56
+
57
+ def self.embed(pyobj, obj)
58
+ pyptr = pyobj.respond_to?(:__pyobj__) ? pyobj.__pyobj__ : pyobj
59
+ raise TypeError, "The argument must be a Python object" unless pyptr.kind_of? LibPython::PyObjectStruct
60
+ wo = LibPython.PyWeakref_NewRef(pyptr, weakref_callback)
61
+ register(wo, obj)
62
+ pyobj
63
+ end
64
+
65
+ private_class_method def self.weakref_callback
66
+ unless @weakref_callback
67
+ @weakref_callback_func = FFI::Function.new(
68
+ LibPython::PyObjectStruct.ptr,
69
+ [LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr]
70
+ ) do |callback, pyptr|
71
+ GCGuard.unregister(pyptr)
72
+ # LibPython.Py_DecRef(pyptr)
73
+ # LibPython.Py_IncRef(PyCall.None)
74
+ next PyCall.None
75
+ end
76
+ method_def = LibPython::PyMethodDef.new("weakref_callback", @weakref_callback_func, LibPython::METH_O, nil)
77
+ @weakref_callback = LibPython.PyCFunction_NewEx(method_def, nil, nil).tap {|po| LibPython.Py_IncRef(po) }
78
+ end
79
+ @weakref_callback
80
+ end
81
+ end
82
+
83
+ private_constant :GCGuard
84
+ end
@@ -0,0 +1,120 @@
1
+ require 'pycall'
2
+
3
+ module PyCall
4
+ module Import
5
+ class << self
6
+ attr_reader :main_object
7
+ end
8
+ end
9
+ end
10
+
11
+ PyCall::Import.instance_variable_set(:@main_object, self)
12
+
13
+ module PyCall
14
+ module Import
15
+ def pyimport(mod_name, as: nil)
16
+ as = mod_name unless as
17
+ check_valid_module_variable_name(mod_name, as)
18
+ mod = PyCall.import_module(mod_name)
19
+ define_singleton_method(as) { mod }
20
+ end
21
+
22
+ # This function is implemented as a mimic of `import_from` function defined in `Python/ceval.c`.
23
+ def pyfrom(mod_name, import: nil)
24
+ raise ArgumentError, "missing identifier(s) to be imported" unless import
25
+
26
+ mod_name = mod_name.to_str if mod_name.respond_to? :to_str
27
+ mod_name = mod_name.to_s if mod_name.is_a? Symbol
28
+
29
+ import = Array(import)
30
+ fromlist = import.map.with_index do |import_name, i|
31
+ case import_name
32
+ when assoc_array_matcher
33
+ import_name[0]
34
+ when Symbol, String
35
+ import_name
36
+ else
37
+ raise ArgumentError, "wrong type of import name #{import_name.class} (expected String or Symbol)"
38
+ end
39
+ end
40
+ from_list = PyCall.tuple(from_list)
41
+
42
+ main_dict_ptr = PyCall.import_module('__main__').__dict__.__pyptr__
43
+ globals = main_dict_ptr # FIXME: this should mimic to `import_name` function defined in `Python/ceval.c`.
44
+ locals = main_dict_ptr # FIXME: this should mimic to `import_name` function defined in `Python/ceval.c`.
45
+ level = 0 # TODO: support prefixed dots (#25)
46
+ mod = LibPython::Helpers.import_module(mod_name, globals, locals, fromlist, level)
47
+
48
+ import.each do |import_name|
49
+ case import_name
50
+ when assoc_array_matcher
51
+ name, asname = *import_name
52
+ when Symbol, String
53
+ name, asname = import_name, import_name
54
+ end
55
+
56
+ if PyCall::LibPython::Helpers.hasattr?(mod.__pyptr__, name)
57
+ pyobj = PyCall::LibPython::Helpers.getattr(mod.__pyptr__, name)
58
+ define_name(asname, pyobj)
59
+ next
60
+ end
61
+
62
+ if mod.respond_to? :__name__
63
+ pkgname = mod.__name__
64
+ fullname = "#{pkgname}.#{name}"
65
+ sys_modules = PyCall.import_module('sys').modules
66
+ if sys_modules.has_key?(fullname)
67
+ pyobj = module_dict[fullname]
68
+ define_name(asname, pyobj)
69
+ next
70
+ end
71
+ end
72
+
73
+ raise ArgumentError, "cannot import name #{fullname}" unless pyobj
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def define_name(name, pyobj)
80
+ if callable?(pyobj) && !type_object?(pyobj)
81
+ define_singleton_method(name) do |*args|
82
+ LibPython::Helpers.call_object(pyobj.__pyptr__, *args)
83
+ end
84
+ else
85
+ if constant_name?(name)
86
+ context = self
87
+ context = (self == PyCall::Import.main_object) ? Object : self
88
+ context.module_eval { const_set(name, pyobj) }
89
+ else
90
+ define_singleton_method(name) { pyobj }
91
+ end
92
+ end
93
+ end
94
+
95
+ def constant_name?(name)
96
+ name =~ /\A[A-Z]/
97
+ end
98
+
99
+ def check_valid_module_variable_name(mod_name, var_name)
100
+ var_name = var_name.to_s if var_name.kind_of? Symbol
101
+ if var_name.include?('.')
102
+ raise ArgumentError, "#{var_name} is not a valid module variable name, use pyimport #{mod_name}, as: <name>"
103
+ end
104
+ end
105
+
106
+ def assoc_array_matcher
107
+ @assoc_array_matcher ||= ->(ary) do
108
+ ary.is_a?(Array) && ary.length == 2
109
+ end
110
+ end
111
+
112
+ def callable?(pyobj)
113
+ LibPython::Helpers.callable?(pyobj.__pyptr__)
114
+ end
115
+
116
+ def type_object?(pyobj)
117
+ pyobj.__pyptr__.kind_of? PyTypePtr
118
+ end
119
+ end
120
+ end