pycall 0.1.0.alpha.20170419b → 0.1.0.alpha.20170426

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36ce6abd1f064086a1f22ae9d67294e6b4687387
4
- data.tar.gz: 474754869f8f19454eb462523afb95d678f2a8f4
3
+ metadata.gz: 3bcfecf2fed43a5e8e5c52f5d96cfc899f180a6e
4
+ data.tar.gz: 66b6648e36b7eef5e8961413144a837e85bb47cf
5
5
  SHA512:
6
- metadata.gz: ac241e957192e609d29df35bf7655c3430a07b7ae75d4eacec7317157d1435f655c3cdee7c4224cf0de7a55e51b029d5db5e04c099586ecdede39a926e07f189
7
- data.tar.gz: a5fcf65ce708790d6f82c6d757c3b8c5313e4e62310064a32917e9c9c80e0761cd11c4188b3618e91d016efdf09e691dd73ba582020951e7952116ffe0edcfca
6
+ metadata.gz: '0117417889164ebeb9756fd40e7393db68a1c83204f0fdfd57c9be0cabf44cde6d9b474eba22aa08aa69c97a995c78d665a94388f87b32f78761cf4b3ee1e3f1'
7
+ data.tar.gz: 60d13590b7a44567a8ce47d20e137ac90518a6279777e1398c7ccf4bc6741e63d7f26b3b01672aff82b3eba8299f516017325a55825c0d1672d120b1f1873047
@@ -23,3 +23,7 @@ addons:
23
23
  before_install:
24
24
  - gem update --system
25
25
  - gem update bundler
26
+
27
+ before_script:
28
+ - echo === python investigator.py ===
29
+ - python lib/pycall/python/investigator.py
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
- # PyCall
1
+ # PyCall: Calling Python functions from the Ruby language
2
2
 
3
3
  [![Build Status](https://travis-ci.org/mrkn/pycall.svg?branch=master)](https://travis-ci.org/mrkn/pycall)
4
+ [![Build status](https://ci.appveyor.com/api/projects/status/071is0f4iu0vy8lp/branch/master?svg=true)](https://ci.appveyor.com/project/mrkn/pycall/branch/master)
4
5
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/pycall`. To experiment with that code, run `bin/console` for an interactive prompt.
6
-
7
- TODO: Delete this and the text above, and describe your gem
6
+ This library provides the features to directly call and partially interoperate with Python from the Ruby language. You can import arbitrary Python modules into Ruby modules, call Python functions with automatic type conversion from Ruby to Python.
8
7
 
9
8
  ## Installation
10
9
 
@@ -20,11 +19,64 @@ And then execute:
20
19
 
21
20
  Or install it yourself as:
22
21
 
23
- $ gem install pycall
22
+ $ gem install --pre pycall
24
23
 
25
24
  ## Usage
26
25
 
27
- TODO: Write usage instructions here
26
+ Here is a simple example to call Python's `math.sin` function and compare it to the `Math.sin` in Ruby:
27
+
28
+ require 'pycall/import'
29
+ include PyCall::Import
30
+ pyimport :math
31
+ math.sin.(math.pi / 4) - Math.sin(Math::PI / 4) # => 0.0
32
+ # ^ This period is necessary
33
+
34
+ Type conversions from Ruby to Python are automatically performed for numeric, boolean, string, arrays, and hashes.
35
+
36
+ ### Python function call
37
+
38
+ In this version of pycall, the all of functions and methods in Python is wrapped as callable objects in Ruby. It means we need to put a priod between the name of function and `(` like `math.sin.(...)` in the above example.
39
+
40
+ This unnatural notation is a temporary specification, so we should be able to write `math.sin(...)` in the future.
41
+
42
+ ## Wrapping Python classes
43
+
44
+ **NOTE: Currently I'm trying to rewrite class wrapping system, so the content of this section will be changed.**
45
+
46
+ Using `PyCall::PyObjectWrapper` module, we can create incarnation classes for Python classes in Ruby language. For example, the following script defines a incarnation class for `numpy.ndarray` class.
47
+
48
+ ```ruby
49
+ require 'pycall'
50
+
51
+ class Ndarray
52
+ import PyCall::PyObjectWrapper
53
+ wrap_class PyCall.import_module('numpy').ndarray
54
+ end
55
+ ```
56
+
57
+ Defineing incarnation classes using `wrap_class` registeres automatic type conversion, so it changes the class of wrapper object. For example:
58
+
59
+ require 'pycall/import'
60
+ include PyCall::Import
61
+ pyimport :numpy, as: :np
62
+ x1 = np.array(PyCall.tuple(10))
63
+ x1.class # => PyCall::PyObject
64
+
65
+ class Ndarray
66
+ import PyCall::PyObjectWrapper
67
+ wrap_class PyCall.import_module('numpy').ndarray
68
+ # NOTE: From here, numpy.ndarray objects are converted to Ndarray objects
69
+ end
70
+
71
+ x2 = np.array(PyCall.tuple(10))
72
+ x2.class # => Ndarray
73
+
74
+
75
+ **NOTE: I will write an efficient wrapper for numpy by RubyKaigi 2017.**
76
+
77
+ ### Specifying the Python version
78
+
79
+ If you want to use a specific version of Python instead of the default, you can change the Python version by setting the `PYTHON` environment variable to the path of the `python` executable.
28
80
 
29
81
  ## Development
30
82
 
@@ -37,6 +89,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
37
89
  Bug reports and pull requests are welcome on GitHub at https://github.com/mrkn/pycall.
38
90
 
39
91
 
92
+ ## Acknowledgement
93
+
94
+ [PyCall.jl](https://github.com/JuliaPy/PyCall.jl) is referred too many times to implement this library.
95
+
40
96
  ## License
41
97
 
42
98
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,155 @@
1
+ ---
2
+ environment:
3
+ matrix:
4
+ # Ruby 2.1 (32bit)
5
+ - ruby_version: "21"
6
+ PYTHONDIR: "C:\\Python27"
7
+ PYTHON: "C:\\Python27\\python.exe"
8
+
9
+ - ruby_version: "21"
10
+ PYTHONDIR: "C:\\Python33"
11
+ PYTHON: "C:\\Python33\\python.exe"
12
+
13
+ - ruby_version: "21"
14
+ PYTHONDIR: "C:\\Python34"
15
+ PYTHON: "C:\\Python34\\python.exe"
16
+
17
+ - ruby_version: "21"
18
+ PYTHONDIR: "C:\\Python35"
19
+ PYTHON: "C:\\Python35\\python.exe"
20
+
21
+ - ruby_version: "21"
22
+ PYTHONDIR: "C:\\Python36"
23
+ PYTHON: "C:\\Python36\\python.exe"
24
+
25
+ # Ruby 2.1 (64bit)
26
+ - ruby_version: "21-x64"
27
+ PYTHONDIR: "C:\\Python27-x64"
28
+ PYTHON: "C:\\Python27-x64\\python.exe"
29
+
30
+ - ruby_version: "21-x64"
31
+ PYTHONDIR: "C:\\Python33-x64"
32
+ PYTHON: "C:\\Python33-x64\\python.exe"
33
+
34
+ - ruby_version: "21-x64"
35
+ PYTHONDIR: "C:\\Python34-x64"
36
+ PYTHON: "C:\\Python34-x64\\python.exe"
37
+
38
+ - ruby_version: "21-x64"
39
+ PYTHONDIR: "C:\\Python35-x64"
40
+ PYTHON: "C:\\Python35-x64\\python.exe"
41
+
42
+ - ruby_version: "21-x64"
43
+ PYTHONDIR: "C:\\Python36-x64"
44
+ PYTHON: "C:\\Python36-x64\\python.exe"
45
+
46
+ # Ruby 2.2 (32bit)
47
+ - ruby_version: "22"
48
+ PYTHONDIR: "C:\\Python27"
49
+ PYTHON: "C:\\Python27\\python.exe"
50
+
51
+ - ruby_version: "22"
52
+ PYTHONDIR: "C:\\Python33"
53
+ PYTHON: "C:\\Python33\\python.exe"
54
+
55
+ - ruby_version: "22"
56
+ PYTHONDIR: "C:\\Python34"
57
+ PYTHON: "C:\\Python34\\python.exe"
58
+
59
+ - ruby_version: "22"
60
+ PYTHONDIR: "C:\\Python35"
61
+ PYTHON: "C:\\Python35\\python.exe"
62
+
63
+ - ruby_version: "22"
64
+ PYTHONDIR: "C:\\Python36"
65
+ PYTHON: "C:\\Python36\\python.exe"
66
+
67
+ # Ruby 2.2 (64bit)
68
+ - ruby_version: "22-x64"
69
+ PYTHONDIR: "C:\\Python27-x64"
70
+ PYTHON: "C:\\Python27-x64\\python.exe"
71
+
72
+ - ruby_version: "22-x64"
73
+ PYTHONDIR: "C:\\Python33-x64"
74
+ PYTHON: "C:\\Python33-x64\\python.exe"
75
+
76
+ - ruby_version: "22-x64"
77
+ PYTHONDIR: "C:\\Python34-x64"
78
+ PYTHON: "C:\\Python34-x64\\python.exe"
79
+
80
+ - ruby_version: "22-x64"
81
+ PYTHONDIR: "C:\\Python35-x64"
82
+ PYTHON: "C:\\Python35-x64\\python.exe"
83
+
84
+ - ruby_version: "22-x64"
85
+ PYTHONDIR: "C:\\Python36-x64"
86
+ PYTHON: "C:\\Python36-x64\\python.exe"
87
+
88
+ # Ruby 2.3 (32bit)
89
+ - ruby_version: "23"
90
+ PYTHONDIR: "C:\\Python27"
91
+ PYTHON: "C:\\Python27\\python.exe"
92
+
93
+ - ruby_version: "23"
94
+ PYTHONDIR: "C:\\Python33"
95
+ PYTHON: "C:\\Python33\\python.exe"
96
+
97
+ - ruby_version: "23"
98
+ PYTHONDIR: "C:\\Python34"
99
+ PYTHON: "C:\\Python34\\python.exe"
100
+
101
+ - ruby_version: "23"
102
+ PYTHONDIR: "C:\\Python35"
103
+ PYTHON: "C:\\Python35\\python.exe"
104
+
105
+ - ruby_version: "23"
106
+ PYTHONDIR: "C:\\Python36"
107
+ PYTHON: "C:\\Python36\\python.exe"
108
+
109
+ # Ruby 2.3 (64bit)
110
+ - ruby_version: "23-x64"
111
+ PYTHONDIR: "C:\\Python27-x64"
112
+ PYTHON: "C:\\Python27-x64\\python.exe"
113
+
114
+ - ruby_version: "23-x64"
115
+ PYTHONDIR: "C:\\Python33-x64"
116
+ PYTHON: "C:\\Python33-x64\\python.exe"
117
+
118
+ - ruby_version: "23-x64"
119
+ PYTHONDIR: "C:\\Python34-x64"
120
+ PYTHON: "C:\\Python34-x64\\python.exe"
121
+
122
+ - ruby_version: "23-x64"
123
+ PYTHONDIR: "C:\\Python35-x64"
124
+ PYTHON: "C:\\Python35-x64\\python.exe"
125
+
126
+ - ruby_version: "23-x64"
127
+ PYTHONDIR: "C:\\Python36-x64"
128
+ PYTHON: "C:\\Python36-x64\\python.exe"
129
+
130
+ branches:
131
+ only:
132
+ - master
133
+ - /release-.*/
134
+
135
+ notifications:
136
+ - provider: Email
137
+ on_build_success: false
138
+ on_build_failure: false
139
+ on_build_status_changed: false
140
+
141
+ deploy: off
142
+ build: off
143
+
144
+ install:
145
+ - "SET PATH=%PYTHONDIR%;%PYTHONDIR%\\Scripts;%PATH%"
146
+ - "SET PATH=C:\\Ruby%ruby_version%\\bin;%PATH%"
147
+ - bundle install
148
+
149
+ before_test:
150
+ - ECHO "=== python investigator.py ==="
151
+ - "python lib\\pycall\\python\\investigator.py"
152
+
153
+ test_script:
154
+ - "SET DEBUG_FIND_LIBPYTHON=1"
155
+ - rake
@@ -102,13 +102,11 @@ module PyCall
102
102
  when Float
103
103
  LibPython.PyFloat_FromDouble(obj)
104
104
  when String
105
- case obj.encoding
106
- when Encoding::US_ASCII, Encoding::BINARY
107
- LibPython.PyString_FromStringAndSize(obj, obj.bytesize)
108
- else
109
- obj = obj.encode(Encoding::UTF_8)
110
- LibPython.PyUnicode_DecodeUTF8(obj, obj.bytesize, nil)
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)
111
108
  end
109
+ LibPython.PyString_FromStringAndSize(obj, obj.bytesize)
112
110
  when Symbol
113
111
  from_ruby(obj.to_s)
114
112
  when Array
@@ -10,7 +10,7 @@ module PyCall
10
10
  PyCall::LibPython.PySys_SetArgvEx(0, argv, 0)
11
11
  end
12
12
 
13
- @builtin = LibPython.PyImport_ImportModule(PYTHON_VERSION < '3.0.0' ? '__builtin__' : 'builtins')
13
+ @builtin = LibPython.PyImport_ImportModule(PYTHON_VERSION < '3.0.0' ? '__builtin__' : 'builtins').to_ruby
14
14
  end
15
15
 
16
16
  class << self
@@ -26,6 +26,8 @@ module PyCall
26
26
  private_class_method
27
27
 
28
28
  def self.find_libpython(python = nil)
29
+ debug = (ENV['DEBUG_FIND_LIBPYTHON'] == '1')
30
+ dir_sep = File::ALT_SEPARATOR || File::SEPARATOR
29
31
  python ||= 'python'
30
32
  python_config = investigate_python_config(python)
31
33
 
@@ -42,6 +44,8 @@ module PyCall
42
44
  libs << "#{libprefix}python#{v}" << "#{libprefix}python"
43
45
  libs.uniq!
44
46
 
47
+ $stderr.puts "DEBUG(find_libpython) libs: #{libs.inspect}" if debug
48
+
45
49
  executable = python_config[:executable]
46
50
  libpaths = [ python_config[:LIBDIR] ]
47
51
  if FFI::Platform.windows?
@@ -51,9 +55,11 @@ module PyCall
51
55
  end
52
56
  libpaths << python_config[:PYTHONFRAMEWORKPREFIX] if FFI::Platform.mac?
53
57
  exec_prefix = python_config[:exec_prefix]
54
- libpaths << exec_prefix << File.join(exec_prefix, 'lib')
58
+ libpaths << exec_prefix << [exec_prefix, 'lib'].join(dir_sep)
55
59
  libpaths.compact!
56
60
 
61
+ $stderr.puts "DEBUG(find_libpython) libpaths: #{libpaths.inspect}" if debug
62
+
57
63
  unless ENV['PYTHONHOME']
58
64
  # PYTHONHOME tells python where to look for both pure python and binary modules.
59
65
  # When it is set, it replaces both `prefix` and `exec_prefix`
@@ -88,7 +94,6 @@ module PyCall
88
94
  # Find libpython (we hope):
89
95
  libsuffix = FFI::Platform::LIBSUFFIX
90
96
  multiarch = python_config[:MULTIARCH] || python_config[:multiarch]
91
- dir_sep = File::ALT_SEPARATOR || File::SEPARATOR
92
97
  libs.each do |lib|
93
98
  libpaths.each do |libpath|
94
99
  # NOTE: File.join doesn't use File::ALT_SEPARATOR
@@ -99,10 +104,14 @@ module PyCall
99
104
  libpath_lib,
100
105
  "#{libpath_lib}.#{libsuffix}"
101
106
  ].each do |fullname|
102
- next unless File.file?(fullname)
107
+ unless File.file?(fullname)
108
+ $stderr.puts "DEBUG(find_libpython) Unable to find #{fullname}" if debug
109
+ next
110
+ end
103
111
  begin
104
- libs = ffi_lib(fullname)
105
- return libs.first
112
+ dynlibs = ffi_lib(fullname)
113
+ $stderr.puts "DEBUG(find_libpython) ffi_lib(#{fullname.inspect}) = #{dynlibs.inspect}" if debug
114
+ return dynlibs.first
106
115
  rescue LoadError
107
116
  # skip load error
108
117
  end
@@ -110,6 +119,17 @@ module PyCall
110
119
  end
111
120
  end
112
121
  end
122
+
123
+ # Find libpython in the system path
124
+ libs.each do |lib|
125
+ begin
126
+ dynlibs = ffi_lib(lib)
127
+ $stderr.puts "DEBUG(find_libpython) ffi_lib(#{lib.inspect}) = #{dynlibs.inspect}" if debug
128
+ return dynlibs.first
129
+ rescue LoadError
130
+ # skip load error
131
+ end
132
+ end
113
133
  end
114
134
 
115
135
  def self.investigate_python_config(python)
@@ -131,6 +151,18 @@ module PyCall
131
151
  ffi_lib_flags :lazy, :global
132
152
  libpython = find_libpython ENV['PYTHON']
133
153
 
154
+ attach_function :Py_GetVersion, [], :string
155
+ PYTHON_DESCRIPTION = LibPython.Py_GetVersion().freeze
156
+ PYTHON_VERSION = PYTHON_DESCRIPTION.split(' ', 2)[0].freeze
157
+
158
+ # --- types ---
159
+
160
+ if PYTHON_VERSION < '3.2'
161
+ typedef :long, :Py_hash_t
162
+ else
163
+ typedef :ssize_t, :Py_hash_t
164
+ end
165
+
134
166
  # --- global variables ---
135
167
 
136
168
  attach_variable :_Py_NoneStruct, PyObjectStruct
@@ -173,7 +205,6 @@ module PyCall
173
205
 
174
206
  # --- functions ---
175
207
 
176
- attach_function :Py_GetVersion, [], :string
177
208
  attach_function :Py_InitializeEx, [:int], :void
178
209
  attach_function :Py_IsInitialized, [], :int
179
210
  attach_function :PySys_SetArgvEx, [:int, :pointer, :int], :void
@@ -194,6 +225,7 @@ module PyCall
194
225
  attach_function :PyObject_Call, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
195
226
  attach_function :PyObject_IsInstance, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
196
227
  attach_function :PyObject_Dir, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
228
+ attach_function :PyObject_Hash, [PyObjectStruct.by_ref], :Py_hash_t
197
229
  attach_function :PyObject_Repr, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
198
230
  attach_function :PyObject_Str, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
199
231
  attach_function :PyObject_Type, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
@@ -339,6 +371,10 @@ module PyCall
339
371
  public_class_method
340
372
  end
341
373
 
342
- PYTHON_DESCRIPTION = LibPython.Py_GetVersion().freeze
343
- PYTHON_VERSION = PYTHON_DESCRIPTION.split(' ', 2)[0].freeze
374
+ PYTHON_DESCRIPTION = LibPython::PYTHON_DESCRIPTION
375
+ PYTHON_VERSION = LibPython::PYTHON_VERSION
376
+
377
+ def self.unicode_literals?
378
+ @unicode_literals ||= (PYTHON_VERSION >= '3.0')
379
+ end
344
380
  end
@@ -56,6 +56,16 @@ module PyCall
56
56
 
57
57
  attr_reader :__pyobj__
58
58
 
59
+ def eql?(other)
60
+ rich_compare(other, :==)
61
+ end
62
+
63
+ def hash
64
+ hash_value = LibPython.PyObject_Hash(__pyobj__)
65
+ return super if hash_value == -1
66
+ hash_value
67
+ end
68
+
59
69
  def type
60
70
  LibPython.PyObject_Type(__pyobj__).to_ruby
61
71
  end
@@ -1,3 +1,3 @@
1
1
  module PyCall
2
- VERSION = "0.1.0.alpha.20170419b"
2
+ VERSION = "0.1.0.alpha.20170426"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pycall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha.20170419b
4
+ version: 0.1.0.alpha.20170426
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-04-19 00:00:00.000000000 Z
11
+ date: 2017-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -80,6 +80,7 @@ files:
80
80
  - LICENSE.txt
81
81
  - README.md
82
82
  - Rakefile
83
+ - appveyor.yml
83
84
  - bin/console
84
85
  - bin/guard
85
86
  - bin/rspec