pycall 1.2.0.beta1 → 1.3.1

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
  SHA256:
3
- metadata.gz: 4ee819add9bcf3e4c3a3fb4f4fdc7b94abe6ccf1a969a8aabe9bd7edf9aa6701
4
- data.tar.gz: '03839cbba19832eca0cf90ee7e85cf4cd953f2f5bba2dd12bcc9005097393482'
3
+ metadata.gz: ba0b7938e482d87e6a6eb540b92b2e581974fc6e894e674c7da00b4b68807b15
4
+ data.tar.gz: ad5ce0352f59744abf9193e69cf33719f9314aad45e9c24fd6584fe73873330e
5
5
  SHA512:
6
- metadata.gz: d22a8905b5d7c9f9f2793fd8bb0ed003fa418ab35f8048faced61145d577502571ab0d0bbfb187bb689ee59f8e99539b3de9789d4905584340041e801e951c0f
7
- data.tar.gz: d382691fcdd3995347d126ff70fa0dfc1e877b9136bfce4ca6ac6d783aa7802da544250b03601ae9d335ae4fd9c72af15ab412450e14c2d20a05a29b16b58763
6
+ metadata.gz: cc2eb8963c91d69086eccab17cb06ebea072908f77f3e4f85a23f328f858f7cda29ebec70c519fa70f3fc88eaf5e37b61b88136515f2326e0c239467a522067b
7
+ data.tar.gz: cfb14589abee3741fcec45f31d8d94dc076341ae425ee0302e50628e6ffafdfff665a6e90c67a80ce10df6a8866282bc221ecddf198d0049ee85d1055bb50255
@@ -0,0 +1,67 @@
1
+ name: CI
2
+
3
+ on:
4
+ - push
5
+
6
+ jobs:
7
+ test:
8
+ name: Test
9
+ runs-on: ${{ matrix.os }}
10
+
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os:
15
+ - ubuntu-18.04
16
+ - macos-latest
17
+ ruby_version:
18
+ - 2.7.x
19
+ - 2.6.x
20
+ - 2.5.x
21
+ - 2.4.x
22
+ python_version:
23
+ - 3.8.x
24
+ - 3.7.x
25
+ - 3.6.x
26
+ - 2.7.x
27
+ python_architecture:
28
+ - x64
29
+
30
+ steps:
31
+ - name: Setup Ruby
32
+ if: matrix.ruby_version != 'master-nightly'
33
+ uses: actions/setup-ruby@v1
34
+ with:
35
+ ruby-version: ${{ matrix.ruby_version }}
36
+
37
+ - name: Setup Python
38
+ uses: actions/setup-python@v1
39
+ with:
40
+ python-version: ${{ matrix.python_version }}
41
+ architecture: ${{ matrix.python_architecture }}
42
+
43
+ - name: Checkout
44
+ uses: actions/checkout@v1
45
+ with:
46
+ fetch-depth: 1
47
+
48
+ - name: Prepare environment
49
+ run: |
50
+ gem install bundler
51
+
52
+ - name: Install requirements
53
+ run: |
54
+ pip install --user numpy
55
+ bundle install
56
+
57
+ - name: Compile pycall.so
58
+ run: |
59
+ bundle exec rake compile
60
+
61
+ - name: Python investigator
62
+ run: |
63
+ python lib/pycall/python/investigator.py
64
+
65
+ - name: Test
66
+ run: |
67
+ PYTHON=python bundle exec rake
@@ -2,48 +2,74 @@ language: ruby
2
2
 
3
3
  os: linux
4
4
 
5
- dist: trusty
5
+ dist: bionic
6
6
  sudo: required
7
7
 
8
8
  rvm:
9
9
  - ruby-head
10
- - 2.5.0
11
- - 2.4.3
12
- - 2.3.5
10
+ - 2.7
11
+ - 2.6
12
+ - 2.5
13
+ - 2.4
13
14
 
14
15
  env:
15
16
  global:
16
17
  - PYCALL_DEBUG_FIND_LIBPYTHON=1
17
18
  matrix:
18
- - PYENV_VERSION=2.7.13
19
- - PYENV_VERSION=3.6.2
20
- - PYENV_VERSION=system LIBPYTHON=versions/3.6.2/lib/libpython3.6m.so
21
- - PYENV_VERSION=miniconda2-4.1.11
22
- - PYENV_VERSION=miniconda3-4.3.11
19
+ - PYENV_VERSION=3.8.0
20
+ - PYENV_VERSION=3.7.5
21
+ - PYENV_VERSION=2.7.17
22
+ - PYENV_VERSION=system LIBPYTHON=/usr/lib/x86_64-linux-gnu/libpython3.6m.so
23
+ - PYENV_VERSION=system LIBPYTHON=/usr/lib/x86_64-linux-gnu/libpython2.7.so
24
+ - PYENV_VERSION=miniconda2-4.3.30
25
+ - PYENV_VERSION=miniconda3-4.3.30
23
26
 
24
27
  matrix:
25
28
  include:
26
29
  - os: osx
27
- osx_image: xcode9
30
+ osx_image: xcode11.2
28
31
  compiler: clang
29
- rvm: 2.4.1
30
- env: PYENV_VERSION=3.6.2
32
+ rvm: 2.7
33
+ env: PYENV_VERSION=3.8.0
31
34
  - os: osx
32
- osx_image: xcode9
35
+ osx_image: xcode11.2
33
36
  compiler: clang
34
- rvm: 2.4.1
35
- env: PYENV_VERSION=system LIBPYTHON=versions/3.6.2/lib/libpython3.6m.so
37
+ rvm: 2.6
38
+ env: PYENV_VERSION=3.8.0
36
39
  - os: osx
37
- osx_image: xcode9
40
+ osx_image: xcode11.2
38
41
  compiler: clang
39
- rvm: 2.4.1
42
+ rvm: 2.5
43
+ env: PYENV_VERSION=3.8.0
44
+ - os: osx
45
+ osx_image: xcode11.2
46
+ compiler: clang
47
+ rvm: 2.4
48
+ env: PYENV_VERSION=3.8.0
49
+ - os: osx
50
+ osx_image: xcode11.2
51
+ compiler: clang
52
+ rvm: 2.7
53
+ env: PYENV_VERSION=miniconda3-4.3.11
54
+ - os: osx
55
+ osx_image: xcode11.2
56
+ compiler: clang
57
+ rvm: 2.6
58
+ env: PYENV_VERSION=miniconda3-4.3.11
59
+ - os: osx
60
+ osx_image: xcode11.2
61
+ compiler: clang
62
+ rvm: 2.5
63
+ env: PYENV_VERSION=miniconda3-4.3.11
64
+ - os: osx
65
+ osx_image: xcode11.2
66
+ compiler: clang
67
+ rvm: 2.4
40
68
  env: PYENV_VERSION=miniconda3-4.3.11
41
69
  allow_failures:
42
70
  - os: osx
43
71
 
44
72
  before_install:
45
- - gem update --system
46
- - gem update bundler
47
73
  - export PATH="$(pyenv root)/bin:$PATH"
48
74
  - eval "$(pyenv init -)"
49
75
 
data/CHANGES.md CHANGED
@@ -1,6 +1,36 @@
1
1
  # The change history of PyCall
2
2
 
3
- ## master
3
+ ## 1.3.1
4
+
5
+ * Stop using `&proc` idiom to prevent warnings
6
+
7
+ *Kenta Murata*
8
+
9
+ ## 1.3.0
10
+
11
+ * Add `PyCall.without_gvl` for explicitly releasing the RubyVM GVL
12
+
13
+ * Fix for missing if in PyObjectWrapper
14
+
15
+ *Kouhei Sutou*
16
+
17
+ * Fix for Anaconda environment
18
+
19
+ *Ryo MATSUMIYA*
20
+
21
+ * Fix against `unknown symbol "PyInt_AsSsize_t"` (Fiddle::DLError)
22
+
23
+ *Kouhei Sutou*
24
+
25
+ * Fix for `TypeError: Compared with non class/module`
26
+
27
+ *Archonic*
28
+
29
+ ## 1.2.1
30
+
31
+ * Prevent circular require in pycall/iruby.rb
32
+
33
+ ## 1.2.0
4
34
 
5
35
  * Add `PyCall::Tuple#to_ary`
6
36
 
data/README.md CHANGED
@@ -5,8 +5,9 @@
5
5
 
6
6
  # PyCall: Calling Python functions from the Ruby language
7
7
 
8
+ [![Build Status](https://github.com/mrkn/pycall.rb/workflows/CI/badge.svg)](https://github.com/mrkn/pycall.rb/actions?query=workflow%3ACI)
8
9
  [![Build Status](https://travis-ci.org/mrkn/pycall.rb.svg?branch=master)](https://travis-ci.org/mrkn/pycall.rb)
9
- [![Build status](https://ci.appveyor.com/api/projects/status/071is0f4iu0vy8lp/branch/master?svg=true)](https://ci.appveyor.com/project/mrkn/pycall.rb/branch/master)
10
+ [![Build status](https://ci.appveyor.com/api/projects/status/0fad23u4qj1yr49e/branch/master?svg=true)](https://ci.appveyor.com/project/mrkn/pycall-rb/branch/master)
10
11
 
11
12
  This library provides the features to directly call and partially interoperate
12
13
  with Python from the Ruby language. You can import arbitrary Python modules
@@ -23,6 +24,15 @@ pycall.rb supports Python version 2.7 or higher.
23
24
 
24
25
  Note that in Python 2.7 old-style class, that is defined without a super class, is not fully supported in pycall.rb.
25
26
 
27
+ ## Note for pyenv users
28
+
29
+ pycall.rb requires Python's shared library (e.g. `libpython3.7m.so`).
30
+ pyenv does not build the shared library in default, so you need to specify `--enable-shared` option at the installation like below:
31
+
32
+ ```
33
+ $ env PYTHON_CONFIGURE_OPTS='--enable-shared' pyenv install 3.7.2
34
+ ```
35
+
26
36
  ## Installation
27
37
 
28
38
  Add this line to your application's Gemfile:
@@ -52,6 +62,45 @@ the `Math.sin` in Ruby:
52
62
  Type conversions from Ruby to Python are automatically performed for numeric,
53
63
  boolean, string, arrays, and hashes.
54
64
 
65
+ ### Releasing the RubyVM GVL during Python function calls
66
+
67
+ You may want to release the RubyVM GVL when you call a Python function that takes very long runtime.
68
+ PyCall provides `PyCall.without_gvl` method for such purpose. When PyCall performs python function call,
69
+ PyCall checks the current context, and then it releases the RubyVM GVL when the current context is in a `PyCall.without_gvl`'s block.
70
+
71
+ ```ruby
72
+ PyCall.without_gvl do
73
+ # In this block, all Python function calls are performed without
74
+ # the GVL acquisition.
75
+ pyobj.long_running_function()
76
+ end
77
+
78
+ # Outside of PyCall.without_gvl block,
79
+ # all Python function calls are performed with the GVL acquisition.
80
+ pyobj.long_running_function()
81
+ ```
82
+
83
+ ### Debugging python finder
84
+
85
+ When you encounter `PyCall::PythonNotFound` error, you can investigate PyCall's python finder by setting `PYCALL_DEBUG_FIND_LIBPYTHON` environment variable to `1`. You can see the log like below:
86
+
87
+ ```
88
+ $ PYCALL_DEBUG_FIND_LIBPYTHON=1 ruby -rpycall -ePyCall.builtins
89
+ DEBUG(find_libpython) find_libpython(nil)
90
+ DEBUG(find_libpython) investigate_python_config("python3")
91
+ DEBUG(find_libpython) libs: ["Python.framework/Versions/3.7/Python", "Python", "libpython3.7m", "libpython3.7", "libpython"]
92
+ DEBUG(find_libpython) libpaths: ["/opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib", "/opt/brew/opt/python/lib", "/opt/brew/opt/python/Frameworks", "/opt/brew/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7", "/opt/brew/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib"]
93
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/Python.framework/Versions/3.7/Python
94
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/Python.framework/Versions/3.7/Python.dylib
95
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/darwin/Python.framework/Versions/3.7/Python
96
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/darwin/Python.framework/Versions/3.7/Python.dylib
97
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/Python.framework/Versions/3.7/Python
98
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/Python.framework/Versions/3.7/Python.dylib
99
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/darwin/Python.framework/Versions/3.7/Python
100
+ DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/darwin/Python.framework/Versions/3.7/Python.dylib
101
+ DEBUG(find_libpython) dlopen("/opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/Python") = #<Fiddle::Handle:0x00007fc012048650>
102
+ ```
103
+
55
104
  ## PyCall object system
56
105
 
57
106
  PyCall wraps pointers of Python objects in `PyCall::PyPtr` objects.
@@ -6,10 +6,6 @@ environment:
6
6
  PYTHONDIR: "C:\\Python27"
7
7
  PYTHON: "C:\\Python27\\python.exe"
8
8
 
9
- - ruby_version: "24"
10
- PYTHONDIR: "C:\\Python34"
11
- PYTHON: "C:\\Python34\\python.exe"
12
-
13
9
  - ruby_version: "24"
14
10
  PYTHONDIR: "C:\\Python35"
15
11
  PYTHON: "C:\\Python35\\python.exe"
@@ -23,10 +19,6 @@ environment:
23
19
  PYTHONDIR: "C:\\Python27-x64"
24
20
  PYTHON: "C:\\Python27-x64\\python.exe"
25
21
 
26
- - ruby_version: "24-x64"
27
- PYTHONDIR: "C:\\Python34-x64"
28
- PYTHON: "C:\\Python34-x64\\python.exe"
29
-
30
22
  - ruby_version: "24-x64"
31
23
  PYTHONDIR: "C:\\Python35-x64"
32
24
  PYTHON: "C:\\Python35-x64\\python.exe"
@@ -57,10 +49,6 @@ environment:
57
49
  PYTHONDIR: "C:\\Python27-x64"
58
50
  PYTHON: "C:\\Python27-x64\\python.exe"
59
51
 
60
- - ruby_version: "23-x64"
61
- PYTHONDIR: "C:\\Python34-x64"
62
- PYTHON: "C:\\Python34-x64\\python.exe"
63
-
64
52
  - ruby_version: "23-x64"
65
53
  PYTHONDIR: "C:\\Python35-x64"
66
54
  PYTHON: "C:\\Python35-x64\\python.exe"
@@ -10,19 +10,35 @@ if test -z "$PYENV_VERSION"; then
10
10
  exit 1
11
11
  fi
12
12
 
13
+ pyenv_root=$(pyenv root)
14
+
13
15
  if test -n "$LIBPYTHON"; then
14
- export LIBPYTHON=$(pyenv root)/$LIBPYTHON
16
+ if test ! -f $LIBPYTHON; then
17
+ if test -f ${pyenv_root}/$LIBPYTHON; then
18
+ export LIBPYTHON=${pyenv_root}/$LIBPYTHON
19
+ else
20
+ echo "Invalid value in LIBPYTHON: ${LIBPYTHON}" >&2
21
+ exit 1
22
+ fi
23
+ fi
15
24
  fi
16
25
 
17
- if test "$PYENV_VERSION" = "system"; then
18
- if test -z "$LIBPYTHON"; then
19
- echo "ERROR: LIBPYTHON is not provided for PYENV_VERSION=system" >2
20
- exit 1
26
+ (
27
+ cd $(pyenv root)
28
+ if [ -d .git ]; then
29
+ git fetch origin
30
+ git checkout master
31
+ git reset --hard origin/master
21
32
  fi
22
- # NOTE: PYENV_VERSION should be the version of LIBPYTHON during install script
23
- PYENV_VERSION=$(basename $(dirname $(dirname $LIBPYTHON)))
24
- fi
25
- PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install -f $PYENV_VERSION
33
+ )
34
+
35
+ case $PYENV_VERSION in
36
+ system)
37
+ ;;
38
+ *)
39
+ PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install -f $PYENV_VERSION
40
+ ;;
41
+ esac
26
42
 
27
43
  case "$PYENV_VERSION" in
28
44
  *conda*)
@@ -40,6 +56,11 @@ case "$PYENV_VERSION" in
40
56
  travis_retry conda create -q -n test-environment python=$python_version numpy
41
57
  source $(pyenv prefix)/bin/activate test-environment
42
58
  ;;
59
+ system)
60
+ travis_retry pip install --user numpy
61
+ sudo sh -c "apt-get update && apt-get install --no-install-recommends -y python3-pip"
62
+ travis_retry python3.6 -m pip install --user numpy
63
+ ;;
43
64
  *)
44
65
  travis_retry pip install --user numpy
45
66
  ;;
@@ -2,7 +2,7 @@ require 'pycall/import'
2
2
  include PyCall::Import
3
3
 
4
4
  pyimport 'numpy', as: :np
5
- pyfrom 'sklearn.cross_validation', import: :train_test_split
5
+ pyfrom 'sklearn.model_selection', import: :train_test_split
6
6
  pyfrom 'sklearn.preprocessing', import: :StandardScaler
7
7
  pyfrom 'sklearn.datasets', import: %i(make_moons make_circles make_classification)
8
8
  pyfrom 'sklearn.neighbors', import: :KNeighborsClassifier
@@ -20,7 +20,7 @@ num_bins = 50
20
20
 
21
21
  fig, ax = *plt.subplots
22
22
 
23
- n, bins, patches = *ax.hist(x, num_bins, normed: 1)
23
+ n, bins, patches = *ax.hist(x, num_bins, density: 1)
24
24
 
25
25
  y = mlab.normpdf(bins, mu, sigma)
26
26
  ax.plot(bins, y, '--')
@@ -38,7 +38,7 @@
38
38
  "include PyCall::Import\n",
39
39
  "\n",
40
40
  "pyimport 'numpy', as: :np\n",
41
- "pyfrom 'sklearn.cross_validation', import: :train_test_split\n",
41
+ "pyfrom 'sklearn.model_selection', import: :train_test_split\n",
42
42
  "pyfrom 'sklearn.preprocessing', import: :StandardScaler\n",
43
43
  "pyfrom 'sklearn.datasets', import: %i(make_moons make_circles make_classification)\n",
44
44
  "pyfrom 'sklearn.neighbors', import: :KNeighborsClassifier\n",
@@ -30,7 +30,13 @@ lookup_libpython_api(VALUE libpython_handle, char const *name)
30
30
  arg.libpython_handle = libpython_handle;
31
31
  arg.name = name;
32
32
  addr = rb_protect((VALUE (*)(VALUE))lookup_libpython_api_0, (VALUE)&arg, &state);
33
- return (state || NIL_P(addr)) ? NULL : NUM2PTR(addr);
33
+ if (state) {
34
+ rb_set_errinfo(Qnil);
35
+ return NULL;
36
+ }
37
+ else {
38
+ return NIL_P(addr) ? NULL : NUM2PTR(addr);
39
+ }
34
40
  }
35
41
 
36
42
  #define LOOKUP_API_ENTRY(api_name) lookup_libpython_api(libpython_handle, #api_name)
@@ -70,6 +70,52 @@ pycall_after_fork(VALUE mod)
70
70
  return Qnil;
71
71
  }
72
72
 
73
+ static volatile pycall_tls_key without_gvl_key;
74
+
75
+ int
76
+ pycall_without_gvl_p(void)
77
+ {
78
+ /*
79
+ * In pthread, the default value is NULL (== 0).
80
+ *
81
+ * In Win32 thread, the default value is 0 (initialized by TlsAlloc).
82
+ */
83
+ return (int)pycall_tls_get(without_gvl_key);
84
+ }
85
+
86
+ static inline int
87
+ pycall_set_without_gvl(void)
88
+ {
89
+ return pycall_tls_set(without_gvl_key, (void *)1);
90
+ }
91
+
92
+ static inline int
93
+ pycall_set_with_gvl(void)
94
+ {
95
+ return pycall_tls_set(without_gvl_key, (void *)0);
96
+ }
97
+
98
+ VALUE
99
+ pycall_without_gvl(VALUE (* func)(VALUE), VALUE arg)
100
+ {
101
+ int state;
102
+ VALUE result;
103
+
104
+ pycall_set_without_gvl();
105
+
106
+ result = rb_protect(func, arg, &state);
107
+
108
+ pycall_set_with_gvl();
109
+
110
+ return result;
111
+ }
112
+
113
+ static VALUE
114
+ pycall_m_without_gvl(VALUE mod)
115
+ {
116
+ return pycall_without_gvl(rb_yield, Qnil);
117
+ }
118
+
73
119
  /* ==== PyCall::PyPtr ==== */
74
120
 
75
121
  const rb_data_type_t pycall_pyptr_data_type = {
@@ -890,7 +936,7 @@ struct call_pyobject_call_params {
890
936
  PyObject *kwargs;
891
937
  };
892
938
 
893
- PyObject *
939
+ static inline PyObject *
894
940
  call_pyobject_call(struct call_pyobject_call_params *params)
895
941
  {
896
942
  PyObject *res;
@@ -899,7 +945,7 @@ call_pyobject_call(struct call_pyobject_call_params *params)
899
945
  }
900
946
 
901
947
  PyObject *
902
- pyobject_call_without_gvl(PyObject *pycallable, PyObject *args, PyObject *kwargs)
948
+ pyobject_call(PyObject *pycallable, PyObject *args, PyObject *kwargs)
903
949
  {
904
950
  PyObject *res;
905
951
  struct call_pyobject_call_params params;
@@ -907,9 +953,14 @@ pyobject_call_without_gvl(PyObject *pycallable, PyObject *args, PyObject *kwargs
907
953
  params.args = args;
908
954
  params.kwargs = kwargs;
909
955
 
910
- res = (PyObject *)rb_thread_call_without_gvl(
911
- (void * (*)(void *))call_pyobject_call, (void *)&params,
912
- (rb_unblock_function_t *)pycall_interrupt_python_thread, NULL);
956
+ if (pycall_without_gvl_p()) {
957
+ res = (PyObject *)rb_thread_call_without_gvl(
958
+ (void * (*)(void *))call_pyobject_call, (void *)&params,
959
+ (rb_unblock_function_t *)pycall_interrupt_python_thread, NULL);
960
+ }
961
+ else {
962
+ res = call_pyobject_call(&params);
963
+ }
913
964
 
914
965
  return res;
915
966
  }
@@ -961,7 +1012,7 @@ pycall_call_python_callable(PyObject *pycallable, int argc, VALUE *argv)
961
1012
  }
962
1013
  }
963
1014
 
964
- res = pyobject_call_without_gvl(pycallable, args, kwargs); /* New reference */
1015
+ res = pyobject_call(pycallable, args, kwargs); /* New reference */
965
1016
  if (!res) {
966
1017
  pycall_pyerror_fetch_and_raise("PyObject_Call in pycall_call_python_callable");
967
1018
  }
@@ -2185,6 +2236,9 @@ Init_pycall(void)
2185
2236
 
2186
2237
  rb_define_module_function(mPyCall, "after_fork", pycall_after_fork, 0);
2187
2238
 
2239
+ pycall_tls_create(&without_gvl_key);
2240
+ rb_define_module_function(mPyCall, "without_gvl", pycall_m_without_gvl, 0);
2241
+
2188
2242
  /* PyCall::PyPtr */
2189
2243
 
2190
2244
  cPyPtr = rb_define_class_under(mPyCall, "PyPtr", rb_cBasicObject);
@@ -11,10 +11,19 @@ extern "C" {
11
11
  #include <ruby.h>
12
12
  #include <ruby/encoding.h>
13
13
  #include <ruby/thread.h>
14
+
14
15
  #include <assert.h>
15
16
  #include <inttypes.h>
16
17
  #include <limits.h>
17
18
 
19
+ #if defined(_WIN32)
20
+ # define PYCALL_THREAD_WIN32
21
+ # include <ruby/win32.h>
22
+ #elif defined(HAVE_PTHREAD_H)
23
+ # define PYCALL_THREAD_PTHREAD
24
+ # include <pthread.h>
25
+ #endif
26
+
18
27
  #if SIZEOF_LONG == SIZEOF_VOIDP
19
28
  # define PTR2NUM(x) (LONG2NUM((long)(x)))
20
29
  # define NUM2PTR(x) ((void*)(NUM2ULONG(x)))
@@ -492,6 +501,23 @@ extern PyTypeObject PyRuby_Type;
492
501
 
493
502
  PyObject * PyRuby_New(VALUE ruby_object);
494
503
 
504
+ /* ==== thread support ==== */
505
+
506
+ #if defined(PYCALL_THREAD_WIN32)
507
+ typedef DWORD pycall_tls_key;
508
+ #elif defined(PYCALL_THREAD_PTHREAD)
509
+ typedef pthread_key_t pycall_tls_key;
510
+ #else
511
+ # error "unsupported thread type"
512
+ #endif
513
+
514
+ int pycall_tls_create(pycall_tls_key* tls_key);
515
+ void *pycall_tls_get(pycall_tls_key tls_key);
516
+ int pycall_tls_set(pycall_tls_key tls_key, void *ptr);
517
+
518
+ int pycall_without_gvl_p(void);
519
+ VALUE pycall_without_gvl(VALUE (* func)(VALUE), VALUE arg);
520
+
495
521
  /* ==== pycall ==== */
496
522
 
497
523
  typedef struct {
@@ -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
@@ -4,6 +4,7 @@ module PyCall
4
4
  require 'pycall/pyerror'
5
5
  require 'pycall/pyobject_wrapper'
6
6
  require 'pycall/pytypeobject_wrapper'
7
+ require 'pycall/pymodule_wrapper'
7
8
  require 'pycall/init'
8
9
 
9
10
  module_function
@@ -48,6 +49,15 @@ module PyCall
48
49
  end
49
50
  end
50
51
 
52
+ def getattr(*args)
53
+ obj, *rest = args
54
+ LibPython::Helpers.getattr(obj.__pyptr__, *rest)
55
+ end
56
+
57
+ def hasattr?(obj, name)
58
+ LibPython::Helpers.hasattr?(obj.__pyptr__, name)
59
+ end
60
+
51
61
  def import_module(name)
52
62
  LibPython::Helpers.import_module(name)
53
63
  end
@@ -33,9 +33,9 @@ module PyCall
33
33
  v
34
34
  end
35
35
 
36
- def each
36
+ def each(&block)
37
37
  return enum_for unless block_given?
38
- LibPython::Helpers.dict_each(__pyptr__, &proc)
38
+ LibPython::Helpers.dict_each(__pyptr__, &block)
39
39
  self
40
40
  end
41
41
 
@@ -1,4 +1,4 @@
1
- require 'pycall'
1
+ require 'pycall' unless defined?(::PyCall)
2
2
  require 'iruby'
3
3
 
4
4
  module PyCall
@@ -27,12 +27,12 @@ module PyCall
27
27
  def find_python_config(python = nil)
28
28
  python ||= DEFAULT_PYTHON
29
29
  Array(python).each do |python_cmd|
30
- python_config = investigate_python_config(python_cmd)
31
- return [python_cmd, python_config] unless python_config.empty?
30
+ begin
31
+ python_config = investigate_python_config(python_cmd)
32
+ return [python_cmd, python_config] unless python_config.empty?
33
+ rescue
34
+ end
32
35
  end
33
- rescue
34
- raise ::PyCall::PythonNotFound
35
- else
36
36
  raise ::PyCall::PythonNotFound
37
37
  end
38
38
 
@@ -40,10 +40,6 @@ module PyCall
40
40
  debug_report("find_libpython(#{python.inspect})")
41
41
  python, python_config = find_python_config(python)
42
42
 
43
- set_PYTHONHOME(python_config)
44
- libs = make_libs(python_config)
45
- libpaths = make_libpaths(python_config)
46
-
47
43
  # Try LIBPYTHON environment variable first.
48
44
  if (libpython = ENV['LIBPYTHON'])
49
45
  if File.file?(libpython)
@@ -59,6 +55,9 @@ module PyCall
59
55
  end
60
56
 
61
57
  # Find libpython (we hope):
58
+ set_PYTHONHOME(python_config)
59
+ libs = make_libs(python_config)
60
+ libpaths = make_libpaths(python_config)
62
61
  multiarch = python_config[:MULTIARCH] || python_config[:multiarch]
63
62
  libs.each do |lib|
64
63
  libpaths.each do |libpath|
@@ -121,7 +120,7 @@ module PyCall
121
120
  end
122
121
 
123
122
  def set_PYTHONHOME(python_config)
124
- if !ENV.has_key?('PYTHONHOME') && python_config[:conda]
123
+ if !ENV.has_key?('PYTHONHOME')
125
124
  case RUBY_PLATFORM
126
125
  when /mingw32/, /cygwin/, /mswin/
127
126
  ENV['PYTHONHOME'] = python_config[:exec_prefix]
@@ -13,9 +13,9 @@ module PyCall
13
13
  PyCall.len(self)
14
14
  end
15
15
 
16
- def each
16
+ def each(&block)
17
17
  return enum_for unless block_given?
18
- LibPython::Helpers.sequence_each(__pyptr__, &proc)
18
+ LibPython::Helpers.sequence_each(__pyptr__, &block)
19
19
  self
20
20
  end
21
21
 
@@ -0,0 +1,46 @@
1
+ require 'pycall/pyobject_wrapper'
2
+
3
+ module PyCall
4
+ module PyModuleWrapper
5
+ include PyObjectWrapper
6
+
7
+ def [](*args)
8
+ case args[0]
9
+ when String, Symbol
10
+ PyCall.getattr(self, args[0])
11
+ else
12
+ super
13
+ end
14
+ end
15
+ end
16
+
17
+ module_function
18
+
19
+ class WrapperModuleCache < WrapperObjectCache
20
+ def initialize
21
+ super(LibPython::API::PyModule_Type)
22
+ end
23
+
24
+ def check_wrapper_object(wrapper_object)
25
+ unless wrapper_object.kind_of?(Module) && wrapper_object.kind_of?(PyObjectWrapper)
26
+ raise TypeError, "unexpected type #{wrapper_object.class} (expected Module extended by PyObjectWrapper)"
27
+ end
28
+ end
29
+
30
+ def self.instance
31
+ @instance ||= self.new
32
+ end
33
+ end
34
+
35
+ private_constant :WrapperModuleCache
36
+
37
+ def wrap_module(pymodptr)
38
+ check_ismodule(pymodptr)
39
+ WrapperModuleCache.instance.lookup(pymodptr) do
40
+ Module.new do |mod|
41
+ mod.instance_variable_set(:@__pyptr__, pymodptr)
42
+ mod.extend PyModuleWrapper
43
+ end
44
+ end
45
+ end
46
+ end
@@ -170,43 +170,15 @@ module PyCall
170
170
 
171
171
  module_function
172
172
 
173
- class WrapperModuleCache < WrapperObjectCache
174
- def initialize
175
- super(LibPython::API::PyModule_Type)
176
- end
177
-
178
- def check_wrapper_object(wrapper_object)
179
- unless wrapper_object.kind_of?(Module) && wrapper_object.kind_of?(PyObjectWrapper)
180
- raise TypeError, "unexpected type #{wrapper_object.class} (expected Module extended by PyObjectWrapper)"
181
- end
182
- end
183
-
184
- def self.instance
185
- @instance ||= self.new
186
- end
187
- end
188
-
189
- private_constant :WrapperModuleCache
190
-
191
- def wrap_module(pymodptr)
192
- check_ismodule(pymodptr)
193
- WrapperModuleCache.instance.lookup(pymodptr) do
194
- Module.new do |mod|
195
- mod.instance_variable_set(:@__pyptr__, pymodptr)
196
- mod.extend PyObjectWrapper
197
- end
198
- end
173
+ def check_ismodule(pyptr)
174
+ return if pyptr.kind_of? LibPython::API::PyModule_Type
175
+ raise TypeError, "PyModule object is required"
199
176
  end
200
177
 
201
178
  def check_isclass(pyptr)
202
179
  pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper
203
180
  return if pyptr.kind_of? LibPython::API::PyType_Type
204
- 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)
205
182
  raise TypeError, "PyType object is required"
206
183
  end
207
-
208
- def check_ismodule(pyptr)
209
- return if pyptr.kind_of? LibPython::API::PyModule_Type
210
- raise TypeError, "PyModule object is required"
211
- end
212
184
  end
@@ -1,12 +1,8 @@
1
1
  from distutils.sysconfig import get_config_var
2
2
  import sys
3
3
 
4
- def conda():
5
- return 'conda' in sys.version or 'Continuum' in sys.version
6
-
7
4
  for var in ('executable', 'exec_prefix', 'prefix'):
8
5
  print(var + ': ' + str(getattr(sys, var)))
9
- print('conda: ' + ('true' if conda() else 'false'))
10
6
  print('multiarch: ' + str(getattr(getattr(sys, 'implementation', sys), '_multiarch', None)))
11
7
  for var in ('VERSION', 'INSTSONAME', 'LIBRARY', 'LDLIBRARY', 'LIBDIR', 'PYTHONFRAMEWORKPREFIX', 'MULTIARCH'):
12
8
  print(var + ': ' + str(get_config_var(var)))
@@ -51,8 +51,18 @@ module PyCall
51
51
 
52
52
  def <(other)
53
53
  case other
54
+ when self
55
+ false
54
56
  when PyTypeObjectWrapper
55
57
  __pyptr__ < other.__pyptr__
58
+ when Class
59
+ false if other.ancestors.include?(self)
60
+ when Module
61
+ if ancestors.include?(other)
62
+ true
63
+ elsif other.ancestors.include?(self)
64
+ false
65
+ end
56
66
  else
57
67
  raise TypeError, "compared with non class/module"
58
68
  end
@@ -1,3 +1,9 @@
1
1
  module PyCall
2
- VERSION = "1.2.0.beta1"
2
+ VERSION = "1.3.1"
3
+
4
+ module Version
5
+ numbers, TAG = VERSION.split("-")
6
+ MAJOR, MINOR, MICRO = numbers.split(".").map(&:to_i)
7
+ STRING = VERSION
8
+ end
3
9
  end
@@ -5,7 +5,13 @@ require 'pycall/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "pycall"
8
- spec.version = PyCall::VERSION
8
+ version_components = [
9
+ PyCall::Version::MAJOR.to_s,
10
+ PyCall::Version::MINOR.to_s,
11
+ PyCall::Version::MICRO.to_s,
12
+ PyCall::Version::TAG,
13
+ ]
14
+ spec.version = version_components.compact.join(".")
9
15
  spec.authors = ["Kenta Murata"]
10
16
  spec.email = ["mrkn@mrkn.jp"]
11
17
 
@@ -30,11 +36,11 @@ Gem::Specification.new do |spec|
30
36
  spec.require_paths = ["lib"]
31
37
  spec.extensions = ["ext/pycall/extconf.rb"]
32
38
 
33
- spec.add_development_dependency "bundler", "~> 1.13"
34
- spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "bundler"
40
+ spec.add_development_dependency "rake"
35
41
  spec.add_development_dependency "rake-compiler"
36
42
  spec.add_development_dependency "rake-compiler-dock"
37
- spec.add_development_dependency "rspec", "~> 3.0"
43
+ spec.add_development_dependency "rspec"
38
44
  spec.add_development_dependency "launchy"
39
45
  spec.add_development_dependency "pry"
40
46
  spec.add_development_dependency "pry-byebug"
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pycall
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0.beta1
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenta Murata
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-27 00:00:00.000000000 Z
11
+ date: 2020-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.13'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.13'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake-compiler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,16 +70,16 @@ dependencies:
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '3.0'
75
+ version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '3.0'
82
+ version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: launchy
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +130,7 @@ extensions:
130
130
  - ext/pycall/extconf.rb
131
131
  extra_rdoc_files: []
132
132
  files:
133
+ - ".github/workflows/ci.yml"
133
134
  - ".gitignore"
134
135
  - ".rspec"
135
136
  - ".travis.yml"
@@ -171,6 +172,7 @@ files:
171
172
  - ext/pycall/pycall_internal.h
172
173
  - ext/pycall/range.c
173
174
  - ext/pycall/ruby_wrapper.c
175
+ - ext/pycall/thread.c
174
176
  - images/pycallrb_logo.png
175
177
  - images/pycallrb_logo_200.png
176
178
  - lib/pycall.rb
@@ -187,6 +189,7 @@ files:
187
189
  - lib/pycall/list.rb
188
190
  - lib/pycall/pretty_print.rb
189
191
  - lib/pycall/pyerror.rb
192
+ - lib/pycall/pymodule_wrapper.rb
190
193
  - lib/pycall/pyobject_wrapper.rb
191
194
  - lib/pycall/python/PyCall/__init__.py
192
195
  - lib/pycall/python/PyCall/six.py
@@ -214,12 +217,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
214
217
  version: '0'
215
218
  required_rubygems_version: !ruby/object:Gem::Requirement
216
219
  requirements:
217
- - - ">"
220
+ - - ">="
218
221
  - !ruby/object:Gem::Version
219
- version: 1.3.1
222
+ version: '0'
220
223
  requirements: []
221
- rubyforge_project:
222
- rubygems_version: 2.7.6
224
+ rubygems_version: 3.1.2
223
225
  signing_key:
224
226
  specification_version: 4
225
227
  summary: pycall