pycall 1.2.0.beta1 → 1.3.1

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 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