pycall 1.0.1-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) 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/2.1/pycall.so +0 -0
  40. data/lib/2.2/pycall.so +0 -0
  41. data/lib/2.3/pycall.so +0 -0
  42. data/lib/2.4/pycall.so +0 -0
  43. data/lib/pycall/conversion.rb +173 -0
  44. data/lib/pycall/dict.rb +48 -0
  45. data/lib/pycall/error.rb +10 -0
  46. data/lib/pycall/gc_guard.rb +84 -0
  47. data/lib/pycall/import.rb +120 -0
  48. data/lib/pycall/init.rb +55 -0
  49. data/lib/pycall/iruby_helper.rb +40 -0
  50. data/lib/pycall/libpython/finder.rb +170 -0
  51. data/lib/pycall/libpython/pyobject_struct.rb +30 -0
  52. data/lib/pycall/libpython/pytypeobject_struct.rb +273 -0
  53. data/lib/pycall/libpython.rb +12 -0
  54. data/lib/pycall/list.rb +45 -0
  55. data/lib/pycall/pretty_print.rb +9 -0
  56. data/lib/pycall/pyerror.rb +30 -0
  57. data/lib/pycall/pyobject_wrapper.rb +212 -0
  58. data/lib/pycall/python/PyCall/__init__.py +1 -0
  59. data/lib/pycall/python/PyCall/six.py +23 -0
  60. data/lib/pycall/python/investigator.py +7 -0
  61. data/lib/pycall/pytypeobject_wrapper.rb +90 -0
  62. data/lib/pycall/set.rb +19 -0
  63. data/lib/pycall/slice.rb +8 -0
  64. data/lib/pycall/tuple.rb +46 -0
  65. data/lib/pycall/version.rb +3 -0
  66. data/lib/pycall/wrapper_object_cache.rb +61 -0
  67. data/lib/pycall.rb +91 -0
  68. data/pycall.gemspec +40 -0
  69. data/tasks/docker.rake +21 -0
  70. data/tasks/pycall.rake +7 -0
  71. metadata +228 -0
@@ -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
@@ -0,0 +1,55 @@
1
+ module PyCall
2
+ def self.const_missing(name)
3
+ case name
4
+ when :PyPtr, :PyTypePtr, :PyObjectWrapper, :PYTHON_DESCRIPTION, :PYTHON_VERSION
5
+ PyCall.init
6
+ const_get(name)
7
+ else
8
+ super
9
+ end
10
+ end
11
+
12
+ module LibPython
13
+ def self.const_missing(name)
14
+ case name
15
+ when :API, :Conversion, :Helpers, :PYTHON_DESCRIPTION, :PYTHON_VERSION
16
+ PyCall.init
17
+ const_get(name)
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.init(python = ENV['PYTHON'])
25
+ return false if LibPython.instance_variable_defined?(:@handle)
26
+ class << PyCall
27
+ remove_method :const_missing
28
+ end
29
+ class << PyCall::LibPython
30
+ remove_method :const_missing
31
+ end
32
+
33
+ ENV['PYTHONPATH'] = [ File.expand_path('../python', __FILE__), ENV['PYTHONPATH'] ].compact.join(File::PATH_SEPARATOR)
34
+
35
+ LibPython.instance_variable_set(:@handle, LibPython::Finder.find_libpython(python))
36
+ class << LibPython
37
+ undef_method :handle
38
+ attr_reader :handle
39
+ end
40
+
41
+ begin
42
+ major, minor, _ = RUBY_VERSION.split('.')
43
+ require "#{major}.#{minor}/pycall.so"
44
+ rescue LoadError
45
+ require 'pycall.so'
46
+ end
47
+
48
+ require 'pycall/dict'
49
+ require 'pycall/list'
50
+ require 'pycall/slice'
51
+ const_set(:PYTHON_VERSION, LibPython::PYTHON_VERSION)
52
+ const_set(:PYTHON_DESCRIPTION, LibPython::PYTHON_DESCRIPTION)
53
+ true
54
+ end
55
+ end
@@ -0,0 +1,40 @@
1
+ require 'pycall'
2
+ require 'iruby'
3
+
4
+ module PyCall
5
+ module IRubyHelper
6
+ private
7
+
8
+ def check_pyobject_respond_to_format_method(obj, method_name)
9
+ return false unless obj.kind_of? PyObject
10
+ return false unless PyCall.hasattr?(obj, method_name)
11
+ PyCall.getattr(obj, method_name).kind_of? PyCall::LibPython.PyMethod_Type
12
+ end
13
+
14
+ def register_pyobject_formatter(format_name, mime, priority_value=0)
15
+ method_name = :"_repr_#{format_name}_"
16
+ match do |obj|
17
+ check_pyobject_respond_to_format_method(obj, method_name)
18
+ end
19
+ priority priority_value
20
+ format mime do |obj|
21
+ PyCall.getattr(obj, method_name).()
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ ::IRuby::Display::Registry.module_eval do
28
+ extend PyCall::IRubyHelper
29
+
30
+ register_pyobject_formatter :html, 'text/html'
31
+ register_pyobject_formatter :markdown, 'text/markdown'
32
+ register_pyobject_formatter :svg, 'image/svg+xml'
33
+ register_pyobject_formatter :png, 'image/png'
34
+ register_pyobject_formatter :jpeg, 'image/jpeg'
35
+ register_pyobject_formatter :latex, 'text/latex'
36
+ register_pyobject_formatter :json, 'application/json'
37
+ register_pyobject_formatter :javascript, 'application/javascript'
38
+ register_pyobject_formatter :pdf, 'application/pdf'
39
+ register_pyobject_formatter :pretty, 'text/plain', -1000
40
+ end
@@ -0,0 +1,170 @@
1
+ require 'pycall/error'
2
+ require 'fiddle'
3
+
4
+ module PyCall
5
+ module LibPython
6
+ module Finder
7
+ case RUBY_PLATFORM
8
+ when /cygwin/
9
+ libprefix = 'cyg'
10
+ libsuffix = 'dll'
11
+ when /mingw/, /mswin/
12
+ libprefix = ''
13
+ libsuffix = 'dll'
14
+ when /darwin/
15
+ libsuffix = 'dylib'
16
+ end
17
+
18
+ LIBPREFIX = libprefix || 'lib'
19
+ LIBSUFFIX = libsuffix || 'so'
20
+
21
+ class << self
22
+ def find_libpython(python = nil)
23
+ debug_report("find_libpython(#{python.inspect})")
24
+ if python
25
+ begin
26
+ python_config = investigate_python_config(python)
27
+ rescue
28
+ raise ::PyCall::PythonNotFound
29
+ end
30
+ else
31
+ %w[python python3].each do |python_cmd|
32
+ begin
33
+ python_config = investigate_python_config(python_cmd)
34
+ python = python_cmd
35
+ break
36
+ rescue
37
+ raise ::PyCall::PythonNotFound
38
+ end
39
+ end
40
+ end
41
+
42
+ libs = make_libs(python_config)
43
+ libpaths = make_libpaths(python_config)
44
+
45
+ # Try LIBPYTHON environment variable first.
46
+ if (libpython = ENV['LIBPYTHON'])
47
+ if File.file?(libpython)
48
+ begin
49
+ return dlopen(libpython)
50
+ rescue Fiddle::DLError
51
+ debug_report "#{$!.class}: #{$!.message}"
52
+ else
53
+ debug_report "Success to dlopen #{libpython.inspect} from ENV['LIBPYTHON']"
54
+ end
55
+ end
56
+ warn "WARNING(#{self}.#{__method__}) Ignore the wrong libpython location specified in ENV['LIBPYTHON']."
57
+ end
58
+
59
+ # Find libpython (we hope):
60
+ multiarch = python_config[:MULTIARCH] || python_config[:multiarch]
61
+ libs.each do |lib|
62
+ libpaths.each do |libpath|
63
+ libpath_libs = [ File.join(libpath, lib) ]
64
+ libpath_libs << File.join(libpath, multiarch, lib) if multiarch
65
+ libpath_libs.each do |libpath_lib|
66
+ [ libpath_lib, "#{libpath_lib}.#{LIBSUFFIX}" ].each do |fullname|
67
+ unless File.file? fullname
68
+ debug_report "Unable to find #{fullname}"
69
+ next
70
+ end
71
+ begin
72
+ return dlopen(libpath_lib)
73
+ rescue Fiddle::DLError
74
+ debug_report "#{$!.class}: #{$!.message}"
75
+ else
76
+ debug_report "Success to dlopen #{libpaht_lib}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ # Find libpython in the system path
84
+ libs.each do |lib|
85
+ begin
86
+ return dlopen(lib)
87
+ rescue Fiddle::DLError
88
+ debug_report "#{$!.class}: #{$!.message}"
89
+ else
90
+ debug_report "Success to dlopen #{lib}"
91
+ end
92
+ end
93
+
94
+ raise ::PyCall::PythonNotFound
95
+ end
96
+
97
+ def investigate_python_config(python)
98
+ python_env = { 'PYTHONIOENCODING' => 'UTF-8' }
99
+ debug_report("investigate_python_config(#{python.inspect})")
100
+ IO.popen(python_env, [python, python_investigator_py], 'r') do |io|
101
+ {}.tap do |config|
102
+ io.each_line do |line|
103
+ key, value = line.chomp.split(': ', 2)
104
+ config[key.to_sym] = value if value != 'None'
105
+ end
106
+ end
107
+ end
108
+ rescue Errno::ENOENT
109
+ raise PyCall::PythonInvestigationFailed
110
+ end
111
+
112
+ def python_investigator_py
113
+ File.expand_path('../../python/investigator.py', __FILE__)
114
+ end
115
+
116
+ def make_libs(python_config)
117
+ libs = []
118
+ %i(INSTSONAME LDLIBRARY).each do |key|
119
+ lib = python_config[key]
120
+ libs << lib << File.basename(lib) if lib
121
+ end
122
+ if (lib = python_config[:LIBRARY])
123
+ libs << File.basename(lib, File.extname(lib))
124
+ end
125
+
126
+ v = python_config[:VERSION]
127
+ libs << "#{LIBPREFIX}python#{v}" << "#{LIBPREFIX}python"
128
+ libs.uniq!
129
+
130
+ debug_report "libs: #{libs.inspect}"
131
+ return libs
132
+ end
133
+
134
+ def make_libpaths(python_config)
135
+ executable = python_config[:executable]
136
+ libpaths = [ python_config[:LIBDIR] ]
137
+ if Fiddle::WINDOWS
138
+ libpaths << File.dirname(executable)
139
+ else
140
+ libpaths << File.expand_path('../../lib', executable)
141
+ end
142
+ libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
143
+ exec_prefix = python_config[:exec_prefix]
144
+ libpaths << exec_prefix << File.join(exec_prefix, 'lib')
145
+ libpaths.compact!
146
+
147
+ debug_report "libpaths: #{libpaths.inspect}"
148
+ return libpaths
149
+ end
150
+
151
+ private
152
+
153
+ def dlopen(libname)
154
+ Fiddle.dlopen(libname).tap do |handle|
155
+ debug_report("dlopen(#{libname.inspect}) = #{handle.inspect}") if handle
156
+ end
157
+ end
158
+
159
+ def debug_report(message)
160
+ return unless debug?
161
+ $stderr.puts "DEBUG(find_libpython) #{message}"
162
+ end
163
+
164
+ def debug?
165
+ @debug ||= (ENV['PYCALL_DEBUG_FIND_LIBPYTHON'] == '1')
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -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