pycall 1.2.0 → 1.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +150 -0
- data/.github/workflows/windows.yml +127 -0
- data/.gitignore +2 -0
- data/CHANGES.md +39 -1
- data/README.md +153 -7
- data/Rakefile +81 -19
- data/ci/travis_install.sh +30 -9
- data/examples/classifier_comparison.rb +1 -1
- data/examples/hist.rb +1 -1
- data/examples/notebooks/classifier_comparison.ipynb +1 -1
- data/examples/notebooks/leaflet.ipynb +77 -0
- data/ext/pycall/libpython.c +7 -1
- data/ext/pycall/pycall.c +60 -6
- data/ext/pycall/pycall_internal.h +27 -1
- data/ext/pycall/ruby_wrapper.c +11 -2
- data/ext/pycall/thread.c +36 -0
- data/lib/pycall.rb +11 -0
- data/lib/pycall/dict.rb +2 -2
- data/lib/pycall/init.rb +3 -8
- data/lib/pycall/iruby_helper.rb +1 -1
- data/lib/pycall/libpython/finder.rb +131 -84
- data/lib/pycall/list.rb +2 -2
- data/lib/pycall/pyobject_wrapper.rb +1 -1
- data/lib/pycall/python/investigator.py +82 -9
- data/lib/pycall/pytypeobject_wrapper.rb +10 -0
- data/lib/pycall/version.rb +7 -1
- data/lib/pycall/wrapper_object_cache.rb +51 -25
- data/pycall.gemspec +9 -2
- metadata +23 -7
- data/.travis.yml +0 -56
- data/appveyor.yml +0 -104
data/ext/pycall/ruby_wrapper.c
CHANGED
@@ -356,11 +356,16 @@ PyRuby_getattro_with_gvl(PyRubyObject *pyro, PyObject *pyobj_name)
|
|
356
356
|
|
357
357
|
VALUE cPyRubyPtr;
|
358
358
|
|
359
|
-
|
359
|
+
static rb_data_type_t pycall_pyrubyptr_data_type = {
|
360
360
|
"PyCall::PyRubyPtr",
|
361
361
|
{ 0, pycall_pyptr_free, pycall_pyptr_memsize, },
|
362
362
|
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
363
|
-
|
363
|
+
# if defined _WIN32 && !defined __CYGWIN__
|
364
|
+
0,
|
365
|
+
# else
|
366
|
+
&pycall_pyptr_data_type,
|
367
|
+
# endif
|
368
|
+
0, RUBY_TYPED_FREE_IMMEDIATELY
|
364
369
|
#endif
|
365
370
|
};
|
366
371
|
|
@@ -462,6 +467,10 @@ pycall_init_ruby_wrapper(void)
|
|
462
467
|
|
463
468
|
/* PyCall::PyRubyPtr */
|
464
469
|
|
470
|
+
#if defined _WIN32 && !defined __CYGWIN__
|
471
|
+
pycall_pyrubyptr_data_type.parent = &pycall_pyptr_data_type;
|
472
|
+
#endif
|
473
|
+
|
465
474
|
cPyRubyPtr = rb_define_class_under(mPyCall, "PyRubyPtr", cPyPtr);
|
466
475
|
rb_define_alloc_func(cPyRubyPtr, pycall_pyruby_allocate);
|
467
476
|
rb_define_method(cPyRubyPtr, "__ruby_object_id__", pycall_pyruby_get_ruby_object_id, 0);
|
data/ext/pycall/thread.c
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#include "pycall_internal.h"
|
2
|
+
|
3
|
+
#if defined(PYCALL_THREAD_WIN32)
|
4
|
+
int pycall_tls_create(pycall_tls_key *tls_key)
|
5
|
+
{
|
6
|
+
*tls_key = TlsAlloc();
|
7
|
+
return *tls_key == TLS_OUT_OF_INDEXES;
|
8
|
+
}
|
9
|
+
|
10
|
+
void *pycall_tls_get(pycall_tls_key tls_key)
|
11
|
+
{
|
12
|
+
return TlsGetValue(tls_key);
|
13
|
+
}
|
14
|
+
|
15
|
+
int pycall_tls_set(pycall_tls_key tls_key, void *ptr)
|
16
|
+
{
|
17
|
+
return 0 == TlsSetValue(tls_key, ptr);
|
18
|
+
}
|
19
|
+
#endif
|
20
|
+
|
21
|
+
#if defined(PYCALL_THREAD_PTHREAD)
|
22
|
+
int pycall_tls_create(pycall_tls_key *tls_key)
|
23
|
+
{
|
24
|
+
return pthread_key_create(tls_key, NULL);
|
25
|
+
}
|
26
|
+
|
27
|
+
void *pycall_tls_get(pycall_tls_key tls_key)
|
28
|
+
{
|
29
|
+
return pthread_getspecific(tls_key);
|
30
|
+
}
|
31
|
+
|
32
|
+
int pycall_tls_set(pycall_tls_key tls_key, void *ptr)
|
33
|
+
{
|
34
|
+
return pthread_setspecific(tls_key, ptr);
|
35
|
+
}
|
36
|
+
#endif
|
data/lib/pycall.rb
CHANGED
@@ -58,6 +58,17 @@ module PyCall
|
|
58
58
|
LibPython::Helpers.hasattr?(obj.__pyptr__, name)
|
59
59
|
end
|
60
60
|
|
61
|
+
def same?(left, right)
|
62
|
+
case left
|
63
|
+
when PyObjectWrapper
|
64
|
+
case right
|
65
|
+
when PyObjectWrapper
|
66
|
+
return left.__pyptr__ == right.__pyptr__
|
67
|
+
end
|
68
|
+
end
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
61
72
|
def import_module(name)
|
62
73
|
LibPython::Helpers.import_module(name)
|
63
74
|
end
|
data/lib/pycall/dict.rb
CHANGED
data/lib/pycall/init.rb
CHANGED
@@ -30,20 +30,15 @@ module PyCall
|
|
30
30
|
remove_method :const_missing
|
31
31
|
end
|
32
32
|
|
33
|
-
ENV['PYTHONPATH'] = [ File.expand_path('../python', __FILE__), ENV['PYTHONPATH'] ].compact.join(File::PATH_SEPARATOR)
|
34
|
-
|
35
33
|
LibPython.instance_variable_set(:@handle, LibPython::Finder.find_libpython(python))
|
36
34
|
class << LibPython
|
37
35
|
undef_method :handle
|
38
36
|
attr_reader :handle
|
39
37
|
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
rescue LoadError
|
45
|
-
require 'pycall.so'
|
46
|
-
end
|
39
|
+
require 'pycall.so'
|
40
|
+
|
41
|
+
PyCall.sys.path.append(File.expand_path('../python', __FILE__))
|
47
42
|
|
48
43
|
require 'pycall/dict'
|
49
44
|
require 'pycall/list'
|
data/lib/pycall/iruby_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'pycall/error'
|
2
2
|
require 'fiddle'
|
3
|
+
require 'pathname'
|
3
4
|
|
4
5
|
module PyCall
|
5
6
|
module LibPython
|
@@ -27,73 +28,114 @@ module PyCall
|
|
27
28
|
def find_python_config(python = nil)
|
28
29
|
python ||= DEFAULT_PYTHON
|
29
30
|
Array(python).each do |python_cmd|
|
30
|
-
|
31
|
-
|
31
|
+
begin
|
32
|
+
python_config = investigate_python_config(python_cmd)
|
33
|
+
return [python_cmd, python_config] unless python_config.empty?
|
34
|
+
rescue
|
35
|
+
end
|
32
36
|
end
|
33
|
-
rescue
|
34
|
-
raise ::PyCall::PythonNotFound
|
35
|
-
else
|
36
37
|
raise ::PyCall::PythonNotFound
|
37
38
|
end
|
38
39
|
|
39
40
|
def find_libpython(python = nil)
|
40
41
|
debug_report("find_libpython(#{python.inspect})")
|
41
42
|
python, python_config = find_python_config(python)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
43
|
+
suffix = python_config[:SHLIB_SUFFIX]
|
44
|
+
|
45
|
+
use_conda = (ENV.fetch("CONDA_PREFIX", nil) == File.dirname(python_config[:executable]))
|
46
|
+
python_home = if !ENV.key?("PYTHONHOME") || use_conda
|
47
|
+
python_config[:PYTHONHOME]
|
48
|
+
else
|
49
|
+
ENV["PYTHONHOME"]
|
50
|
+
end
|
51
|
+
ENV["PYTHONHOME"] = python_home
|
52
|
+
|
53
|
+
candidate_paths(python_config) do |path|
|
54
|
+
debug_report("Candidate: #{path}")
|
55
|
+
normalized = normalize_path(path, suffix)
|
56
|
+
if normalized
|
57
|
+
debug_report("Trying to dlopen: #{normalized}")
|
50
58
|
begin
|
51
|
-
return dlopen(
|
59
|
+
return dlopen(normalized)
|
52
60
|
rescue Fiddle::DLError
|
53
|
-
debug_report "#{$!.class}: #{$!.message}"
|
54
|
-
else
|
55
|
-
debug_report "Success to dlopen #{libpython.inspect} from ENV['LIBPYTHON']"
|
61
|
+
debug_report "dlopen(#{normalized.inspect}) => #{$!.class}: #{$!.message}"
|
56
62
|
end
|
63
|
+
else
|
64
|
+
debug_report("Not found.")
|
57
65
|
end
|
58
|
-
warn "WARNING(#{self}.#{__method__}) Ignore the wrong libpython location specified in ENV['LIBPYTHON']."
|
59
66
|
end
|
67
|
+
end
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
69
|
+
def candidate_names(python_config)
|
70
|
+
names = []
|
71
|
+
names << python_config[:LDLIBRARY] if python_config[:LDLIBRARY]
|
72
|
+
suffix = python_config[:SHLIB_SUFFIX]
|
73
|
+
if python_config[:LIBRARY]
|
74
|
+
ext = File.extname(python_config[:LIBRARY])
|
75
|
+
names << python_config[:LIBRARY].delete_suffix(ext) + suffix
|
76
|
+
end
|
77
|
+
dlprefix = if windows? then "" else "lib" end
|
78
|
+
sysdata = {
|
79
|
+
v_major: python_config[:version_major],
|
80
|
+
VERSION: python_config[:VERSION],
|
81
|
+
ABIFLAGS: python_config[:ABIFLAGS],
|
82
|
+
}
|
83
|
+
[
|
84
|
+
"python%{VERSION}%{ABIFLAGS}" % sysdata,
|
85
|
+
"python%{VERSION}" % sysdata,
|
86
|
+
"python%{v_major}" % sysdata,
|
87
|
+
"python"
|
88
|
+
].each do |stem|
|
89
|
+
names << "#{dlprefix}#{stem}#{suffix}"
|
83
90
|
end
|
84
91
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
92
|
+
names.compact!
|
93
|
+
names.uniq!
|
94
|
+
|
95
|
+
debug_report("candidate_names: #{names}")
|
96
|
+
return names
|
97
|
+
end
|
98
|
+
|
99
|
+
def candidate_paths(python_config)
|
100
|
+
# The candidate library that linked by executable
|
101
|
+
yield python_config[:linked_libpython]
|
102
|
+
|
103
|
+
lib_dirs = make_libpaths(python_config)
|
104
|
+
lib_basenames = candidate_names(python_config)
|
105
|
+
|
106
|
+
# candidates by absolute paths
|
107
|
+
lib_dirs.each do |dir|
|
108
|
+
lib_basenames.each do |name|
|
109
|
+
yield File.join(dir, name)
|
93
110
|
end
|
94
111
|
end
|
95
112
|
|
96
|
-
|
113
|
+
# library names for searching in system library paths
|
114
|
+
lib_basenames.each do |name|
|
115
|
+
yield name
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def normalize_path(path, suffix, apple_p=apple?)
|
120
|
+
return nil if path.nil?
|
121
|
+
case
|
122
|
+
when path.nil?,
|
123
|
+
Pathname.new(path).relative?
|
124
|
+
nil
|
125
|
+
when File.exist?(path)
|
126
|
+
File.realpath(path)
|
127
|
+
when File.exist?(path + suffix)
|
128
|
+
File.realpath(path + suffix)
|
129
|
+
when apple_p
|
130
|
+
normalize_path(remove_suffix_apple(path), ".so", false)
|
131
|
+
else
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Strip off .so or .dylib
|
137
|
+
def remove_suffix_apple(path)
|
138
|
+
path.sub(/\.(?:dylib|so)\z/, '')
|
97
139
|
end
|
98
140
|
|
99
141
|
def investigate_python_config(python)
|
@@ -120,47 +162,25 @@ module PyCall
|
|
120
162
|
File.expand_path('../../python/investigator.py', __FILE__)
|
121
163
|
end
|
122
164
|
|
123
|
-
def
|
124
|
-
|
125
|
-
case RUBY_PLATFORM
|
126
|
-
when /mingw32/, /cygwin/, /mswin/
|
127
|
-
ENV['PYTHONHOME'] = python_config[:exec_prefix]
|
128
|
-
else
|
129
|
-
ENV['PYTHONHOME'] = python_config.values_at(:prefix, :exec_prefix).join(':')
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
165
|
+
def make_libpaths(python_config)
|
166
|
+
libpaths = python_config.values_at(:LIBPL, :srcdir, :LIBDIR)
|
133
167
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
lib
|
138
|
-
libs << lib << File.basename(lib) if lib
|
139
|
-
end
|
140
|
-
if (lib = python_config[:LIBRARY])
|
141
|
-
libs << File.basename(lib, File.extname(lib))
|
168
|
+
if windows?
|
169
|
+
libpaths << File.dirname(python_config[:executable])
|
170
|
+
else
|
171
|
+
libpaths << File.expand_path('../../lib', python_config[:executable])
|
142
172
|
end
|
143
173
|
|
144
|
-
|
145
|
-
|
146
|
-
libs.uniq!
|
147
|
-
|
148
|
-
debug_report "libs: #{libs.inspect}"
|
149
|
-
return libs
|
150
|
-
end
|
151
|
-
|
152
|
-
def make_libpaths(python_config)
|
153
|
-
executable = python_config[:executable]
|
154
|
-
libpaths = [ python_config[:LIBDIR] ]
|
155
|
-
if Fiddle::WINDOWS
|
156
|
-
libpaths << File.dirname(executable)
|
157
|
-
else
|
158
|
-
libpaths << File.expand_path('../../lib', executable)
|
174
|
+
if apple?
|
175
|
+
libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
|
159
176
|
end
|
160
|
-
|
177
|
+
|
161
178
|
exec_prefix = python_config[:exec_prefix]
|
162
|
-
libpaths << exec_prefix
|
179
|
+
libpaths << exec_prefix
|
180
|
+
libpaths << File.join(exec_prefix, 'lib')
|
181
|
+
|
163
182
|
libpaths.compact!
|
183
|
+
libpaths.uniq!
|
164
184
|
|
165
185
|
debug_report "libpaths: #{libpaths.inspect}"
|
166
186
|
return libpaths
|
@@ -168,6 +188,14 @@ module PyCall
|
|
168
188
|
|
169
189
|
private
|
170
190
|
|
191
|
+
def windows?
|
192
|
+
Fiddle::WINDOWS
|
193
|
+
end
|
194
|
+
|
195
|
+
def apple?
|
196
|
+
RUBY_PLATFORM.include?("darwin")
|
197
|
+
end
|
198
|
+
|
171
199
|
def dlopen(libname)
|
172
200
|
Fiddle.dlopen(libname).tap do |handle|
|
173
201
|
debug_report("dlopen(#{libname.inspect}) = #{handle.inspect}") if handle
|
@@ -186,3 +214,22 @@ module PyCall
|
|
186
214
|
end
|
187
215
|
end
|
188
216
|
end
|
217
|
+
|
218
|
+
if __FILE__ == $0
|
219
|
+
require "pp"
|
220
|
+
python, python_config = PyCall::LibPython::Finder.find_python_config
|
221
|
+
|
222
|
+
puts "python_config:"
|
223
|
+
pp python_config
|
224
|
+
|
225
|
+
puts "\ncandidate_names:"
|
226
|
+
p PyCall::LibPython::Finder.candidate_names(python_config)
|
227
|
+
|
228
|
+
puts "\nlib_dirs:"
|
229
|
+
p PyCall::LibPython::Finder.make_libpaths(python_config)
|
230
|
+
|
231
|
+
puts "\ncandidate_paths:"
|
232
|
+
PyCall::LibPython::Finder.candidate_paths(python_config) do |path|
|
233
|
+
puts "- #{path}"
|
234
|
+
end
|
235
|
+
end
|
data/lib/pycall/list.rb
CHANGED
@@ -178,7 +178,7 @@ module PyCall
|
|
178
178
|
def check_isclass(pyptr)
|
179
179
|
pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper
|
180
180
|
return if pyptr.kind_of? LibPython::API::PyType_Type
|
181
|
-
return defined?(LibPython::API::PyClass_Type) && pyptr.kind_of?(LibPython::API::PyClass_Type)
|
181
|
+
return if defined?(LibPython::API::PyClass_Type) && pyptr.kind_of?(LibPython::API::PyClass_Type)
|
182
182
|
raise TypeError, "PyType object is required"
|
183
183
|
end
|
184
184
|
end
|
@@ -1,12 +1,85 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
import ctypes.util
|
4
|
+
from distutils.sysconfig import get_config_var, get_python_version
|
5
|
+
import os
|
2
6
|
import sys
|
3
7
|
|
4
|
-
|
5
|
-
|
8
|
+
is_windows = os.name == "nt"
|
9
|
+
|
10
|
+
def linked_libpython():
|
11
|
+
if is_windows:
|
12
|
+
return _linked_libpython_windows()
|
13
|
+
return _linked_libpython_unix()
|
14
|
+
|
15
|
+
class Dl_info(ctypes.Structure):
|
16
|
+
_fields_ = [
|
17
|
+
("dli_fname", ctypes.c_char_p),
|
18
|
+
("dli_fbase", ctypes.c_void_p),
|
19
|
+
("dli_sname", ctypes.c_char_p),
|
20
|
+
("dli_saddr", ctypes.c_void_p),
|
21
|
+
]
|
22
|
+
|
23
|
+
def _linked_libpython_unix():
|
24
|
+
libdl = ctypes.CDLL(ctypes.util.find_library("dl"))
|
25
|
+
libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)]
|
26
|
+
libdl.dladdr.restype = ctypes.c_int
|
27
|
+
|
28
|
+
dlinfo = Dl_info()
|
29
|
+
retcode = libdl.dladdr(
|
30
|
+
ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p),
|
31
|
+
ctypes.pointer(dlinfo))
|
32
|
+
if retcode == 0: # means error
|
33
|
+
return None
|
34
|
+
path = os.path.realpath(dlinfo.dli_fname.decode())
|
35
|
+
if path == os.path.realpath(sys.executable):
|
36
|
+
return None
|
37
|
+
return path
|
38
|
+
|
39
|
+
def _linked_libpython_windows():
|
40
|
+
# Based on: https://stackoverflow.com/a/16659821
|
41
|
+
from ctypes.wintypes import HANDLE, LPWSTR, DWORD
|
42
|
+
|
43
|
+
GetModuleFileName = ctypes.windll.kernel32.GetModuleFileNameW
|
44
|
+
GetModuleFileName.argtypes = [HANDLE, LPWSTR, DWORD]
|
45
|
+
GetModuleFileName.restype = DWORD
|
46
|
+
|
47
|
+
MAX_PATH = 260
|
48
|
+
try:
|
49
|
+
buf = ctypes.create_unicode_buffer(MAX_PATH)
|
50
|
+
GetModuleFileName(ctypes.pythonapi._handle, buf, MAX_PATH)
|
51
|
+
return buf.value
|
52
|
+
except (ValueError, OSError):
|
53
|
+
return None
|
54
|
+
|
55
|
+
print("linked_libpython: {val}".format(val=(linked_libpython() or "None")))
|
56
|
+
|
57
|
+
sys_keys = [ "executable", "exec_prefix", "prefix" ]
|
58
|
+
|
59
|
+
for var in sys_keys:
|
60
|
+
print("{var}: {val}".format(var=var, val=(getattr(sys, var) or "None")))
|
61
|
+
|
62
|
+
config_keys = [ "INSTSONAME", "LIBDIR", "LIBPL", "LIBRARY", "LDLIBRARY",
|
63
|
+
"MULTIARCH", "PYTHONFRAMEWORKPREFIX", "SHLIB_SUFFIX", "srcdir" ]
|
64
|
+
|
65
|
+
for var in config_keys:
|
66
|
+
print("{var}: {val}".format(var=var, val=(get_config_var(var) or "None")))
|
67
|
+
|
68
|
+
print("ABIFLAGS: {val}".format(val=get_config_var("ABIFLAGS") or get_config_var("abiflags") or "None"))
|
69
|
+
|
70
|
+
version = get_python_version() or \
|
71
|
+
"{v.major}.{v.minor}".format(v=sys.version_info) or \
|
72
|
+
get_config_var("VERSION")
|
73
|
+
print("VERSION: {val}".format(val=version))
|
6
74
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
75
|
+
if is_windows:
|
76
|
+
if hasattr(sys, "base_exec_prefix"):
|
77
|
+
PYTHONHOME = sys.base_exec_prefix
|
78
|
+
else:
|
79
|
+
PYTHONHOME = sys.exec_prefix
|
80
|
+
else:
|
81
|
+
if hasattr(sys, "base_exec_prefix"):
|
82
|
+
PYTHONHOME = ":".join([sys.base_prefix, sys.base_exec_prefix])
|
83
|
+
else:
|
84
|
+
PYTHONHOME = ":".join([sys.prefix, sys.exec_prefix])
|
85
|
+
print("PYTHONHOME: {val}".format(val=PYTHONHOME))
|