pycall 0.1.0.alpha.20170711 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +13 -1
  4. data/CHANGES.md +35 -0
  5. data/Gemfile +0 -5
  6. data/README.md +41 -49
  7. data/Rakefile +22 -1
  8. data/appveyor.yml +9 -26
  9. data/examples/classifier_comparison.rb +52 -52
  10. data/examples/hist.rb +11 -11
  11. data/examples/notebooks/classifier_comparison.ipynb +51 -66
  12. data/examples/notebooks/forest_importances.ipynb +26 -49
  13. data/examples/notebooks/iruby_integration.ipynb +15 -36
  14. data/examples/notebooks/lorenz_attractor.ipynb +16 -47
  15. data/examples/notebooks/polar_axes.ipynb +29 -64
  16. data/examples/notebooks/sum_benchmarking.ipynb +109 -103
  17. data/examples/notebooks/xkcd_style.ipynb +12 -12
  18. data/examples/plot_forest_importances_faces.rb +8 -8
  19. data/examples/sum_benchmarking.rb +15 -19
  20. data/ext/pycall/extconf.rb +3 -0
  21. data/ext/pycall/gc.c +74 -0
  22. data/ext/pycall/libpython.c +217 -0
  23. data/ext/pycall/pycall.c +2184 -0
  24. data/ext/pycall/pycall_internal.h +700 -0
  25. data/ext/pycall/range.c +69 -0
  26. data/ext/pycall/ruby_wrapper.c +432 -0
  27. data/lib/pycall.rb +91 -19
  28. data/lib/pycall/dict.rb +28 -82
  29. data/lib/pycall/error.rb +10 -0
  30. data/lib/pycall/import.rb +45 -40
  31. data/lib/pycall/init.rb +44 -20
  32. data/lib/pycall/libpython.rb +6 -380
  33. data/lib/pycall/libpython/finder.rb +170 -0
  34. data/lib/pycall/list.rb +21 -51
  35. data/lib/pycall/pretty_print.rb +9 -0
  36. data/lib/pycall/pyerror.rb +14 -20
  37. data/lib/pycall/pyobject_wrapper.rb +157 -158
  38. data/lib/pycall/python/PyCall/__init__.py +1 -0
  39. data/lib/pycall/python/PyCall/six.py +23 -0
  40. data/lib/pycall/pytypeobject_wrapper.rb +79 -0
  41. data/lib/pycall/slice.rb +3 -22
  42. data/lib/pycall/tuple.rb +1 -7
  43. data/lib/pycall/version.rb +1 -1
  44. data/lib/pycall/wrapper_object_cache.rb +61 -0
  45. data/pycall.gemspec +4 -2
  46. data/tasks/pycall.rake +7 -0
  47. metadata +65 -27
  48. data/lib/pycall/eval.rb +0 -57
  49. data/lib/pycall/exception.rb +0 -13
  50. data/lib/pycall/pyobject.rb +0 -58
  51. data/lib/pycall/ruby_wrapper.rb +0 -137
  52. data/lib/pycall/type_object.rb +0 -11
  53. data/lib/pycall/types.rb +0 -19
  54. data/lib/pycall/utils.rb +0 -106
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,23 @@
1
+ import sys
2
+
3
+ PY3 = sys.version_info[0] == 3
4
+
5
+ if PY3:
6
+ import builtins
7
+ else:
8
+ import __builtin__ as builtins
9
+
10
+ if PY3:
11
+ exec_ = getattr(builtins, 'exec')
12
+ else:
13
+ def exec_(_code_, _globals_=None, _locals_=None):
14
+ """Execute code in a namespace."""
15
+ if _globals_ is None:
16
+ frame = sys._getframe(1)
17
+ _globals_ = frame.f_globals
18
+ if _locals_ is None:
19
+ _locals_ = frame.f_locals
20
+ del frame
21
+ elif _locals_ is None:
22
+ _locals_ = _globals_
23
+ exec("""exec _code_ in _globals_, _locals_""")
@@ -0,0 +1,79 @@
1
+ require 'pycall/pyobject_wrapper'
2
+
3
+ module PyCall
4
+ module PyTypeObjectWrapper
5
+ include PyObjectWrapper
6
+
7
+ def self.extend_object(cls)
8
+ unless cls.kind_of? Class
9
+ raise TypeError, "PyTypeObjectWrapper cannot extend non-class objects"
10
+ end
11
+ pyptr = cls.instance_variable_get(:@__pyptr__)
12
+ unless pyptr.kind_of? PyTypePtr
13
+ raise TypeError, "@__pyptr__ should have PyCall::PyTypePtr object"
14
+ end
15
+ super
16
+ cls.include PyObjectWrapper
17
+ end
18
+
19
+ def inherited(subclass)
20
+ subclass.instance_variable_set(:@__pyptr__, __pyptr__)
21
+ end
22
+
23
+ def new(*args)
24
+ wrap_pyptr(LibPython::Helpers.call_object(__pyptr__, *args))
25
+ end
26
+
27
+ def wrap_pyptr(pyptr)
28
+ return pyptr if pyptr.kind_of? self
29
+ pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper
30
+ unless pyptr.kind_of? PyPtr
31
+ raise TypeError, "unexpected argument type #{pyptr.class} (expected PyCall::PyPtr)"
32
+ end
33
+ unless pyptr.kind_of? __pyptr__
34
+ raise TypeError, "unexpected argument Python type #{pyptr.__ob_type__.__tp_name__} (expected #{__pyptr__.__tp_name__})"
35
+ end
36
+ allocate.tap do |obj|
37
+ obj.instance_variable_set(:@__pyptr__, pyptr)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def register_python_type_mapping
44
+ PyCall::Conversion.register_python_type_mapping(__pyptr__, self)
45
+ end
46
+ end
47
+
48
+ module_function
49
+
50
+ class WrapperClassCache < WrapperObjectCache
51
+ def initialize
52
+ types = [LibPython::API::PyType_Type]
53
+ types << LibPython::API::PyClass_Type if defined? LibPython::API::PyClass_Type
54
+ super(*types)
55
+ end
56
+
57
+ def check_wrapper_object(wrapper_object)
58
+ unless wrapper_object.kind_of?(Class) && wrapper_object.kind_of?(PyTypeObjectWrapper)
59
+ raise TypeError, "unexpected type #{wrapper_object.class} (expected Class extended by PyTypeObjectWrapper)"
60
+ end
61
+ end
62
+
63
+ def self.instance
64
+ @instance ||= self.new
65
+ end
66
+ end
67
+
68
+ private_constant :WrapperClassCache
69
+
70
+ def wrap_class(pytypeptr)
71
+ check_isclass(pytypeptr)
72
+ WrapperClassCache.instance.lookup(pytypeptr) do
73
+ Class.new do |cls|
74
+ cls.instance_variable_set(:@__pyptr__, pytypeptr)
75
+ cls.extend PyTypeObjectWrapper
76
+ end
77
+ end
78
+ end
79
+ end
data/lib/pycall/slice.rb CHANGED
@@ -1,27 +1,8 @@
1
1
  module PyCall
2
+ Slice = builtins.slice
2
3
  class Slice
3
- include PyObjectWrapper
4
-
5
- def self.new(*args)
6
- start, stop, step = nil
7
- case args.length
8
- when 1
9
- stop = args[0]
10
- return super(stop) if stop.kind_of?(LibPython::PyObjectStruct)
11
- when 2
12
- start, stop = args
13
- when 3
14
- start, stop, step = args
15
- else
16
- much_or_few = args.length > 3 ? 'much' : 'few'
17
- raise ArgumentError, "too #{much_or_few} arguments (#{args.length} for 1..3)"
18
- end
19
- start = start ? Conversions.from_ruby(start) : LibPython::PyObjectStruct.null
20
- stop = stop ? Conversions.from_ruby(stop) : LibPython::PyObjectStruct.null
21
- step = step ? Conversions.from_ruby(step) : LibPython::PyObjectStruct.null
22
- pyobj = LibPython.PySlice_New(start, stop, step)
23
- return pyobj.to_ruby unless pyobj.null?
24
- raise PyError.fetch
4
+ def self.all
5
+ new(nil)
25
6
  end
26
7
  end
27
8
  end
data/lib/pycall/tuple.rb CHANGED
@@ -38,13 +38,7 @@ module PyCall
38
38
  end
39
39
 
40
40
  def to_a
41
- [].tap do |ary|
42
- i, n = 0, length
43
- while i < n
44
- ary << self[i]
45
- i += 1
46
- end
47
- end
41
+ Array.new(length) {|i| self[i] }
48
42
  end
49
43
 
50
44
  alias to_ary to_a
@@ -1,3 +1,3 @@
1
1
  module PyCall
2
- VERSION = "0.1.0.alpha.20170711"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,61 @@
1
+ module PyCall
2
+ class WrapperObjectCache
3
+ def initialize(*restricted_pytypes)
4
+ unless restricted_pytypes.empty?
5
+ restricted_pytypes.each do |pytype|
6
+ next if pytype.kind_of? PyTypePtr
7
+ raise TypeError, "unexpected type of object in the arguments (#{pytype.class} for PyCall::PyTypePtr)"
8
+ end
9
+ end
10
+ @restricted_pytypes = restricted_pytypes
11
+ @wrapper_object_table = {}
12
+ @wrapped_pyptr_table = {}
13
+ @weakref_table = {}
14
+ end
15
+
16
+ def lookup(pyptr)
17
+ # TODO: check pytypeptr type
18
+ unless pyptr.kind_of? PyPtr
19
+ raise TypeError, "unexpected argument type #{pyptr.class} (expected PyCall::PyPtr)"
20
+ end
21
+
22
+ unless @restricted_pytypes.empty?
23
+ unless @restricted_pytypes.any? {|pytype| pyptr.kind_of? pytype }
24
+ raise TypeError, "unexpected argument Python type #{pyptr.__ob_type__.__name__} (expected either of them in [#{@restricted_pytypes.map(&:__tp_name__).join(', ')}])"
25
+ end
26
+ end
27
+
28
+ wrapper_object_id = @wrapper_object_table[pyptr.__address__]
29
+ if wrapper_object_id
30
+ wrapper_object = ObjectSpace._id2ref(wrapper_object_id) rescue nil
31
+ return wrapper_object if wrapper_object
32
+ end
33
+
34
+ wrapper_object = yield(pyptr)
35
+ check_wrapper_object(wrapper_object)
36
+ register_wrapper_object(pyptr, wrapper_object)
37
+
38
+ wrapper_object
39
+ end
40
+
41
+ def check_wrapper_object(wrapper_object)
42
+ unless wrapper_object.kind_of?(PyObjectWrapper)
43
+ raise TypeError, "unexpected wrapper object (expected an object extended by PyObjectWrapper)"
44
+ end
45
+ end
46
+
47
+ def register_wrapper_object(pyptr, wrapper_object)
48
+ @wrapper_object_table[pyptr.__address__] = wrapper_object.__id__
49
+ @wrapped_pyptr_table[wrapper_object.__id__] = pyptr.__address__
50
+ ObjectSpace.define_finalizer(wrapper_object, &method(:unregister_wrapper_object))
51
+ # TODO: weakref
52
+ self
53
+ end
54
+
55
+ def unregister_wrapper_object(wrapper_object_id)
56
+ pyptr_addr = @wrapped_pyptr_table.delete(wrapper_object_id)
57
+ @wrapper_object_table.delete(pyptr_addr) if pyptr_addr
58
+ self
59
+ end
60
+ end
61
+ end
data/pycall.gemspec CHANGED
@@ -27,12 +27,14 @@ Gem::Specification.new do |spec|
27
27
  spec.bindir = "exe"
28
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
-
31
- spec.add_dependency "ffi"
30
+ spec.extensions = ["ext/pycall/extconf.rb"]
32
31
 
33
32
  spec.add_development_dependency "bundler", "~> 1.13"
34
33
  spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rake-compiler"
35
+ spec.add_development_dependency "rake-compiler-dock"
35
36
  spec.add_development_dependency "rspec", "~> 3.0"
36
37
  spec.add_development_dependency "launchy"
37
38
  spec.add_development_dependency "pry"
39
+ spec.add_development_dependency "pry-byebug"
38
40
  end
data/tasks/pycall.rake ADDED
@@ -0,0 +1,7 @@
1
+ namespace :pycall do
2
+ desc 'Show PYTHON_DESCRIPTION'
3
+ task :PYTHON_DESCRIPTION do
4
+ require 'pycall'
5
+ puts PyCall::PYTHON_DESCRIPTION
6
+ end
7
+ end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pycall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha.20170711
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenta Murata
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-11 00:00:00.000000000 Z
11
+ date: 2017-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: ffi
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +38,34 @@ dependencies:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler-dock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -94,16 +108,32 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  description: pycall
98
126
  email:
99
127
  - mrkn@mrkn.jp
100
128
  executables: []
101
- extensions: []
129
+ extensions:
130
+ - ext/pycall/extconf.rb
102
131
  extra_rdoc_files: []
103
132
  files:
104
133
  - ".gitignore"
105
134
  - ".rspec"
106
135
  - ".travis.yml"
136
+ - CHANGES.md
107
137
  - Gemfile
108
138
  - LICENSE.txt
109
139
  - README.md
@@ -130,33 +160,41 @@ files:
130
160
  - examples/notebooks/xkcd_style.ipynb
131
161
  - examples/plot_forest_importances_faces.rb
132
162
  - examples/sum_benchmarking.rb
163
+ - ext/pycall/extconf.rb
164
+ - ext/pycall/gc.c
165
+ - ext/pycall/libpython.c
166
+ - ext/pycall/pycall.c
167
+ - ext/pycall/pycall_internal.h
168
+ - ext/pycall/range.c
169
+ - ext/pycall/ruby_wrapper.c
133
170
  - lib/pycall.rb
134
171
  - lib/pycall/conversion.rb
135
172
  - lib/pycall/dict.rb
136
- - lib/pycall/eval.rb
137
- - lib/pycall/exception.rb
173
+ - lib/pycall/error.rb
138
174
  - lib/pycall/gc_guard.rb
139
175
  - lib/pycall/import.rb
140
176
  - lib/pycall/init.rb
141
177
  - lib/pycall/iruby_helper.rb
142
178
  - lib/pycall/libpython.rb
179
+ - lib/pycall/libpython/finder.rb
143
180
  - lib/pycall/libpython/pyobject_struct.rb
144
181
  - lib/pycall/libpython/pytypeobject_struct.rb
145
182
  - lib/pycall/list.rb
183
+ - lib/pycall/pretty_print.rb
146
184
  - lib/pycall/pyerror.rb
147
- - lib/pycall/pyobject.rb
148
185
  - lib/pycall/pyobject_wrapper.rb
186
+ - lib/pycall/python/PyCall/__init__.py
187
+ - lib/pycall/python/PyCall/six.py
149
188
  - lib/pycall/python/investigator.py
150
- - lib/pycall/ruby_wrapper.rb
189
+ - lib/pycall/pytypeobject_wrapper.rb
151
190
  - lib/pycall/set.rb
152
191
  - lib/pycall/slice.rb
153
192
  - lib/pycall/tuple.rb
154
- - lib/pycall/type_object.rb
155
- - lib/pycall/types.rb
156
- - lib/pycall/utils.rb
157
193
  - lib/pycall/version.rb
194
+ - lib/pycall/wrapper_object_cache.rb
158
195
  - pycall.gemspec
159
196
  - tasks/docker.rake
197
+ - tasks/pycall.rake
160
198
  homepage: https://github.com/mrkn/pycall
161
199
  licenses:
162
200
  - MIT
@@ -172,12 +210,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
172
210
  version: '0'
173
211
  required_rubygems_version: !ruby/object:Gem::Requirement
174
212
  requirements:
175
- - - ">"
213
+ - - ">="
176
214
  - !ruby/object:Gem::Version
177
- version: 1.3.1
215
+ version: '0'
178
216
  requirements: []
179
217
  rubyforge_project:
180
- rubygems_version: 2.6.11
218
+ rubygems_version: 2.6.13
181
219
  signing_key:
182
220
  specification_version: 4
183
221
  summary: pycall
data/lib/pycall/eval.rb DELETED
@@ -1,57 +0,0 @@
1
- module PyCall
2
- module Eval
3
- Py_file_input = 257
4
- Py_eval_input = 258
5
-
6
- def self.input_type(sym)
7
- return Py_file_input if sym == :file
8
- return Py_eval_input if sym == :eval
9
- raise ArgumentError, "Unknown input_type for compile Python code"
10
- end
11
-
12
- def self.eval(str, filename: "pycall", input_type: :eval)
13
- input_type = self.input_type(input_type)
14
- globals_ptr = main_dict.__pyobj__
15
- locals_ptr = main_dict.__pyobj__
16
- defer_sigint do
17
- py_code_ptr = LibPython.Py_CompileString(str, filename, input_type)
18
- raise PyError.fetch if py_code_ptr.null?
19
- LibPython.PyEval_EvalCode(py_code_ptr, globals_ptr, locals_ptr)
20
- end
21
- end
22
-
23
- class << self
24
- private
25
-
26
- def main_dict
27
- @main_dict ||= PyCall.import_module("__main__") do |main_module|
28
- PyCall.incref(LibPython.PyModule_GetDict(main_module.__pyobj__)).to_ruby
29
- end
30
- end
31
-
32
- def defer_sigint
33
- # TODO: should be implemented
34
- yield
35
- end
36
- end
37
- end
38
-
39
- def self.import_module(name)
40
- name = name.to_s if name.kind_of? Symbol
41
- raise TypeError, "name must be a String" unless name.kind_of? String
42
- value = LibPython.PyImport_ImportModule(name)
43
- raise PyError.fetch if value.null?
44
- value = value.to_ruby
45
- return value unless block_given?
46
- begin
47
- yield value
48
- ensure
49
- PyCall.decref(value.__pyobj__)
50
- end
51
- end
52
-
53
- def self.eval(str, conversion: true, filename: "pycall", input_type: :eval)
54
- result = Eval.eval(str, filename: filename, input_type: input_type)
55
- conversion ? result.to_ruby : result
56
- end
57
- end