pycall 0.1.0.alpha.20170711 → 1.0.0

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.
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
@@ -1,13 +0,0 @@
1
- require 'pycall'
2
-
3
- module PyCall
4
- @exceptions = {
5
- Exception => LibPython.PyExc_RuntimeError,
6
- TypeError => LibPython.PyExc_TypeError,
7
- }.freeze
8
-
9
- def self.raise_python_exception(exception)
10
- pyexc = @exceptions[exception.class] || @exceptions[Exception]
11
- LibPython.PyErr_SetString(pyexc, "Ruby exception: #{exception.class}: #{exception.message}")
12
- end
13
- end
@@ -1,58 +0,0 @@
1
- module PyCall
2
- class PyObject
3
- include PyObjectWrapper
4
-
5
- def self.null
6
- new(LibPython::PyObjectStruct.new(FFI::Pointer::NULL))
7
- end
8
- end
9
-
10
- def self.getattr(pyobj, name, default=nil)
11
- name = check_attr_name(name)
12
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
13
- value = LibPython.PyObject_GetAttrString(pyobj, name)
14
- if value.null?
15
- return default if default
16
- raise PyError.fetch
17
- end
18
- value.to_ruby
19
- end
20
-
21
- def self.setattr(pyobj, name, value)
22
- name = check_attr_name(name)
23
- value = Conversions.from_ruby(value)
24
- value = value.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
25
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
26
- return self unless LibPython.PyObject_SetAttrString(pyobj, name, value) == -1
27
- raise PyError.fetch
28
- end
29
-
30
- def self.hasattr?(pyobj, name)
31
- name = check_attr_name(name)
32
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
33
- 1 == LibPython.PyObject_HasAttrString(pyobj, name)
34
- end
35
-
36
- def self.check_attr_name(name)
37
- return name.to_str if name.respond_to? :to_str
38
- return name.to_s if name.kind_of? Symbol
39
- raise TypeError, "attribute name must be a String or a Symbol: #{name.inspect}"
40
- end
41
- private_class_method :check_attr_name
42
-
43
- def self.getitem(pyobj, key)
44
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
45
- pykey = Conversions.from_ruby(key)
46
- value = LibPython.PyObject_GetItem(pyobj, pykey)
47
- return value.to_ruby unless value.null?
48
- raise PyError.fetch
49
- end
50
-
51
- def self.setitem(pyobj, key, value)
52
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
53
- pykey = Conversions.from_ruby(key)
54
- value = Conversions.from_ruby(value)
55
- return self unless LibPython.PyObject_SetItem(pyobj, pykey, value) == -1
56
- raise PyError.fetch
57
- end
58
- end
@@ -1,137 +0,0 @@
1
- require 'pycall'
2
-
3
- module PyCall
4
- class RubyWrapStruct < LibPython::PyObjectStruct
5
- layout ob_refcnt: :ssize_t,
6
- ob_type: LibPython::PyTypeObjectStruct.by_ref,
7
- rb_object_id: :ssize_t
8
- end
9
-
10
- # This will be called from __initialize_pycall__ defined in pycall/init.rb
11
- private_class_method def self.__initialize_ruby_wrapper__()
12
- @ruby_wrapper_members = FFI::MemoryPointer.new(LibPython::PyMemberDef.size, 2)
13
- LibPython::PyMemberDef.new(@ruby_wrapper_members).tap do |member|
14
- member.name = 'rb_object_id'
15
- member[:type] = LibPython::T_PYSSIZET
16
- member[:offset] = LibPython::PyObjectStruct.size
17
- member[:flags] = RubyWrapStruct.offset_of(:rb_object_id)
18
- member.doc = "Ruby object ID"
19
- end
20
- @ruby_wrapper_dealloc = FFI::Function.new(:void, [LibPython::PyObjectStruct.ptr]) do |pyptr|
21
- GCGuard.unregister(pyptr)
22
- nil
23
- end
24
- @ruby_wrapper_repr = FFI::Function.new(LibPython::PyObjectStruct.ptr, [LibPython::PyObjectStruct.ptr]) do |pyptr|
25
- str = if pyptr.null?
26
- '<PyCall.ruby_wrapper NULL>'
27
- else
28
- obj = ObjectSpace._id2ref(RubyWrapStruct.new(pyptr.pointer)[:rb_object_id])
29
- "<PyCall.ruby_wrapper #{obj.inspect}>"
30
- end
31
- Conversions.from_ruby(str)
32
- end
33
- @ruby_wrapper_hash = FFI::Function.new(:uint64, [LibPython::PyObjectStruct.ptr]) do |pyptr|
34
- h = ObjectSpace._id2ref(RubyWrapStruct.new(pyptr.pointer)[:rb_object_id]).hash
35
- h == -1 ? PyCall::HASH_SALT : h
36
- end
37
- pysalt32 = 0xb592cd9b # This value comes from PyCall.jl
38
- @ruby_wrapper_hash32 = FFI::Function.new(:uint32, [LibPython::PyObjectStruct.ptr]) do |pyptr|
39
- # int64to32hash from src/support/hashing.c in julia
40
- key = ObjectSpace._id2ref(RubyWrapStruct.new(pyptr.pointer)[:rb_object_id]).hash
41
- key = (~key) + (key << 18)
42
- key = key ^ (key >> 31)
43
- key = key * 21
44
- key = key ^ (key >> 11)
45
- key = key + (key << 6)
46
- key = key ^ (key >> 22)
47
- h = 0xFFFFFFFF & key
48
- h == -1 ? pysalt32 : h
49
- end
50
- @ruby_callable_call = FFI::Function.new(
51
- LibPython::PyObjectStruct.ptr,
52
- [LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr]
53
- ) do |self_, args_, kwargs_|
54
- obj = ObjectSpace._id2ref(RubyWrapStruct.new(self_.pointer)[:rb_object_id])
55
- begin
56
- args = Conversions.to_ruby(args_).to_ary
57
- if kwargs_.null?
58
- ret = obj.(*args)
59
- else
60
- kwargs = PyCall::Dict.new(kwargs_).to_hash
61
- ret = obj.(*args, **kwargs)
62
- end
63
- Conversions.from_ruby(ret)
64
- rescue Exception => err
65
- PyCall.raise_python_exception(err)
66
- LibPython::PyObjectStruct.null
67
- end
68
- end
69
- @ruby_callable_getattr = FFI::Function.new(
70
- LibPython::PyObjectStruct.ptr,
71
- [LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr]
72
- ) do |self_, attr_|
73
- obj = ObjectSpace._id2ref(RubyWrapStruct.new(self_.pointer)[:rb_object_id])
74
- attr = Conversions.to_ruby(attr_)
75
- begin
76
- case attr
77
- when '__name__', 'func_name'
78
- if obj.respond_to? :name
79
- Conversions.from_ruby(obj.name)
80
- else
81
- Conversions.from_ruby(obj.to_s)
82
- end
83
- when '__doc__', 'func_doc'
84
- # TODO: support docstring
85
- PyCall.none
86
- when '__module__', '__defaults__', 'func_defaults', '__closure__', 'func_closure'
87
- PyCall.none
88
- else
89
- # TODO: handle __code__ and func_code
90
- LibPython.PyObject_GenericGetAttr(self_, attr_)
91
- end
92
- rescue Exception => err
93
- PyCall.raise_python_exception(err)
94
- LibPython::PyObjectStruct.null
95
- end
96
- end
97
- @ruby_wrapper = LibPython::PyTypeObjectStruct.new("PyCall.ruby_wrapper", RubyWrapStruct.size) do |t|
98
- t[:tp_flags] |= LibPython::Py_TPFLAGS_BASETYPE
99
- t[:tp_members] = LibPython::PyMemberDef.new(@ruby_wrapper_members)
100
- t[:tp_dealloc] = @ruby_wrapper_dealloc
101
- t[:tp_repr] = @ruby_wrapper_repr
102
- if FFI.type_size(LibPython.find_type(:Py_hash_t)) < FFI.type_size(:uint64)
103
- t[:tp_hash] = @ruby_wrapper_hash32
104
- else
105
- t[:tp_hash] = @ruby_wrapper_hash
106
- end
107
- end
108
- @ruby_callable = PyCall.ruby_wrapper_subclass_new("PyCall.ruby_callable") do |t|
109
- t[:tp_call] = @ruby_callable_call
110
- t[:tp_getattro] = @ruby_callable_getattr
111
- end
112
- end
113
-
114
- def self.ruby_wrapper_subclass_new(name)
115
- LibPython::PyTypeObjectStruct.new(name, RubyWrapStruct.size + FFI.type_size(:pointer)) do |t|
116
- t[:tp_base] = @ruby_wrapper.pointer
117
- LibPython.Py_IncRef(LibPython::PyObjectStruct.new(@ruby_wrapper.pointer))
118
- yield(t)
119
- end
120
- end
121
-
122
- def self.ruby_wrapper_new(type, obj)
123
- pyobj = LibPython._PyObject_New(type)
124
- RubyWrapStruct.new(pyobj.pointer).tap do |rw|
125
- rw[:rb_object_id] = obj.object_id
126
- GCGuard.register(rw, obj)
127
- end
128
- end
129
-
130
- def self.wrap_ruby_object(obj)
131
- ruby_wrapper_new(@ruby_wrapper, obj)
132
- end
133
-
134
- def self.wrap_ruby_callable(obj)
135
- ruby_wrapper_new(@ruby_callable, obj)
136
- end
137
- end
@@ -1,11 +0,0 @@
1
- module PyCall
2
- class TypeObject
3
- include PyObjectWrapper
4
-
5
- def to_s
6
- return "pytype(#{self.__name__})"
7
- end
8
-
9
- alias inspect to_s
10
- end
11
- end
data/lib/pycall/types.rb DELETED
@@ -1,19 +0,0 @@
1
- module PyCall
2
- module Types
3
- def self.pyisinstance(pyobj, pytype)
4
- pyobj = LibPython::PyObjectStruct.new(pyobj) if pyobj.kind_of? FFI::Pointer
5
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
6
-
7
- pytype = LibPython::PyObjectStruct.new(pytype) if pytype.kind_of? FFI::Pointer
8
- pytype = ptype.__pyobj__ unless pytype.kind_of? LibPython::PyObjectStruct
9
-
10
- LibPython.PyObject_IsInstance(pyobj, pytype) == 1
11
- end
12
-
13
- class << self
14
- private def check_pyobject(pyobj)
15
- # TODO: Check whether pyobj is PyObject
16
- end
17
- end
18
- end
19
- end
data/lib/pycall/utils.rb DELETED
@@ -1,106 +0,0 @@
1
- module PyCall
2
- module Utils
3
- def append_sys_path(path_str)
4
- pyobj = LibPython.PyUnicode_DecodeUTF8(path_str, path_str.bytesize, nil)
5
- sys.path << pyobj
6
- end
7
-
8
- def callable?(pyobj)
9
- unless pyobj.kind_of? LibPython::PyObjectStruct
10
- raise TypeError, "the argument must be a Python object" unless pyobj.respond_to? :__pyobj__
11
- pyobj = pyobj.__pyobj__
12
- end
13
- 1 == LibPython.PyCallable_Check(pyobj)
14
- end
15
-
16
- def dir(pyobj)
17
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
18
- value = LibPython.PyObject_Dir(pyobj)
19
- return value.to_ruby unless value.null?
20
- raise PyError.fetch
21
- end
22
-
23
- def incref(pyobj)
24
- LibPython.Py_IncRef(pyobj)
25
- pyobj
26
- end
27
-
28
- def decref(pyobj)
29
- LibPython.Py_DecRef(pyobj)
30
- pyobj.send :pointer=, FFI::Pointer::NULL
31
- pyobj
32
- end
33
-
34
- def int(pyobj)
35
- @int ||= PyCall.eval('int')
36
- @int.(pyobj)
37
- end
38
-
39
- def len(pyobj)
40
- @len ||= PyCall.eval('len')
41
- @len.(pyobj)
42
- end
43
-
44
- def None
45
- LibPython.Py_None
46
- end
47
-
48
- def none?(pyobj)
49
- case pyobj
50
- when FFI::Pointer
51
- ptr = pyobj
52
- when LibPython::PyObjectStruct
53
- ptr = pyobj.to_ptr
54
- else
55
- pyobj = pyobj.__pyobj__.to_ptr
56
- end
57
- ptr == self.None.to_ptr
58
- end
59
-
60
- def slice(*args)
61
- Slice.new(*args)
62
- end
63
-
64
- def str(pyobj)
65
- @str ||= PyCall.eval('str')
66
- @str.(pyobj)
67
- end
68
-
69
- def sys
70
- @sys ||= PyCall.import_module('sys')
71
- end
72
-
73
- def tuple(*args)
74
- PyCall::Tuple[*args]
75
- end
76
-
77
- def type(pyobj)
78
- @type ||= PyCall.eval('type')
79
- @type.(pyobj)
80
- end
81
-
82
- def with(ctx)
83
- __exit__ = PyCall.getattr(ctx, :__exit__)
84
- begin
85
- yield PyCall.getattr(ctx,:__enter__).()
86
- rescue Exception => err
87
- if err.kind_of? PyError
88
- exit_value = __exit__.(err.type, err.value, err.traceback)
89
- else
90
- # TODO: support telling what exception has been catched
91
- exit_value = __exit__.(PyCall.None, PyCall.None, PyCall.None)
92
- end
93
- raise err unless exit_value.equal? true
94
- else
95
- __exit__.(PyCall.None, PyCall.None, PyCall.None)
96
- end
97
- end
98
-
99
- def format_traceback(pyobj)
100
- @format_tb ||= import_module('traceback').format_tb
101
- @format_tb.(pyobj)
102
- end
103
- end
104
-
105
- extend Utils
106
- end