pycall 1.0.3 → 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 +5 -5
- data/.github/workflows/ci.yml +67 -0
- data/.travis.yml +46 -21
- data/CHANGES.md +62 -0
- data/README.md +66 -2
- data/Rakefile +2 -0
- data/appveyor.yml +8 -54
- 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/ext/pycall/libpython.c +12 -1
- data/ext/pycall/pycall.c +149 -5
- data/ext/pycall/pycall.h +23 -0
- data/ext/pycall/pycall_internal.h +35 -1
- data/ext/pycall/ruby_wrapper.c +171 -79
- data/ext/pycall/thread.c +36 -0
- data/images/pycallrb_logo.png +0 -0
- data/images/pycallrb_logo_200.png +0 -0
- data/lib/pycall.rb +10 -0
- data/lib/pycall/dict.rb +2 -2
- data/lib/pycall/iruby_helper.rb +1 -1
- data/lib/pycall/libpython/finder.rb +23 -22
- data/lib/pycall/list.rb +2 -2
- data/lib/pycall/pymodule_wrapper.rb +46 -0
- data/lib/pycall/pyobject_wrapper.rb +4 -32
- data/lib/pycall/python/investigator.py +0 -4
- data/lib/pycall/pytypeobject_wrapper.rb +19 -0
- data/lib/pycall/version.rb +7 -1
- data/pycall.gemspec +11 -4
- metadata +21 -18
- data/lib/pycall/conversion.rb +0 -173
- data/lib/pycall/tuple.rb +0 -46
@@ -2,7 +2,7 @@ require 'pycall/import'
|
|
2
2
|
include PyCall::Import
|
3
3
|
|
4
4
|
pyimport 'numpy', as: :np
|
5
|
-
pyfrom 'sklearn.
|
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
|
data/examples/hist.rb
CHANGED
@@ -38,7 +38,7 @@
|
|
38
38
|
"include PyCall::Import\n",
|
39
39
|
"\n",
|
40
40
|
"pyimport 'numpy', as: :np\n",
|
41
|
-
"pyfrom 'sklearn.
|
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",
|
data/ext/pycall/libpython.c
CHANGED
@@ -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
|
-
|
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)
|
@@ -94,6 +100,7 @@ pycall_init_libpython_api_table(VALUE libpython_handle)
|
|
94
100
|
INIT_API_TABLE_ENTRY(_PyObject_New, required);
|
95
101
|
INIT_API_TABLE_ENTRY(PyCallable_Check, required);
|
96
102
|
INIT_API_TABLE_ENTRY(PyObject_IsInstance, required);
|
103
|
+
INIT_API_TABLE_ENTRY(PyObject_IsSubclass, required);
|
97
104
|
INIT_API_TABLE_ENTRY2(PyObject_Hash._hash_t, PyObject_Hash, required);
|
98
105
|
INIT_API_TABLE_ENTRY(PyObject_RichCompare, required);
|
99
106
|
INIT_API_TABLE_ENTRY(PyObject_Call, required);
|
@@ -154,12 +161,16 @@ pycall_init_libpython_api_table(VALUE libpython_handle)
|
|
154
161
|
|
155
162
|
INIT_API_TABLE_ENTRY(PyIter_Next, required);
|
156
163
|
|
164
|
+
INIT_API_TABLE_ENTRY(PyEval_ThreadsInitialized, required);
|
165
|
+
INIT_API_TABLE_ENTRY(PyEval_InitThreads, required);
|
166
|
+
|
157
167
|
INIT_API_TABLE_ENTRY(PyErr_Occurred, required);
|
158
168
|
INIT_API_TABLE_ENTRY(PyErr_Fetch, required);
|
159
169
|
INIT_API_TABLE_ENTRY(PyErr_Restore, required);
|
160
170
|
INIT_API_TABLE_ENTRY(PyErr_Clear, required);
|
161
171
|
INIT_API_TABLE_ENTRY(PyErr_SetString, required);
|
162
172
|
INIT_API_TABLE_ENTRY(PyErr_Format, required);
|
173
|
+
INIT_API_TABLE_ENTRY(PyErr_SetInterrupt, required);
|
163
174
|
|
164
175
|
INIT_API_TABLE_ENTRY(PyImport_ImportModule, required);
|
165
176
|
INIT_API_TABLE_ENTRY(PyImport_ImportModuleLevel, required);
|
data/ext/pycall/pycall.c
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#include "pycall_internal.h"
|
2
|
+
#include "pycall.h"
|
2
3
|
#include <ruby/encoding.h>
|
3
4
|
|
4
5
|
#include <stdarg.h>
|
@@ -69,6 +70,52 @@ pycall_after_fork(VALUE mod)
|
|
69
70
|
return Qnil;
|
70
71
|
}
|
71
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
|
+
|
72
119
|
/* ==== PyCall::PyPtr ==== */
|
73
120
|
|
74
121
|
const rb_data_type_t pycall_pyptr_data_type = {
|
@@ -143,6 +190,12 @@ get_pyobj_ptr(VALUE obj)
|
|
143
190
|
return pyobj;
|
144
191
|
}
|
145
192
|
|
193
|
+
PyObject *
|
194
|
+
pycall_pyptr_get_pyobj_ptr(VALUE pyptr)
|
195
|
+
{
|
196
|
+
return get_pyobj_ptr(pyptr);
|
197
|
+
}
|
198
|
+
|
146
199
|
static inline PyObject*
|
147
200
|
try_get_pyobj_ptr(VALUE obj)
|
148
201
|
{
|
@@ -445,7 +498,7 @@ get_pytypeobj_ptr(VALUE obj)
|
|
445
498
|
static inline PyTypeObject*
|
446
499
|
try_get_pytypeobj_ptr(VALUE obj)
|
447
500
|
{
|
448
|
-
if (is_pycall_pytypeptr(obj)) return NULL;
|
501
|
+
if (!is_pycall_pytypeptr(obj)) return NULL;
|
449
502
|
return (PyTypeObject*)DATA_PTR(obj);
|
450
503
|
}
|
451
504
|
|
@@ -534,6 +587,20 @@ pycall_pytypeptr_eqq(VALUE obj, VALUE other)
|
|
534
587
|
return Qfalse;
|
535
588
|
}
|
536
589
|
|
590
|
+
static VALUE
|
591
|
+
pycall_pytypeptr_subclass_p(VALUE obj, VALUE other)
|
592
|
+
{
|
593
|
+
PyTypeObject* pytype = get_pytypeobj_ptr(obj);
|
594
|
+
if (is_pycall_pyptr(other)) {
|
595
|
+
PyTypeObject* pytype_other = try_get_pytypeobj_ptr(other);
|
596
|
+
if (pytype_other) {
|
597
|
+
int res = Py_API(PyObject_IsSubclass)((PyObject *)pytype, (PyObject *)pytype_other);
|
598
|
+
return res ? Qtrue : Qfalse;
|
599
|
+
}
|
600
|
+
}
|
601
|
+
return Qfalse;
|
602
|
+
}
|
603
|
+
|
537
604
|
/* ==== PyCall::LibPython::API ==== */
|
538
605
|
|
539
606
|
static VALUE
|
@@ -727,7 +794,6 @@ pycall_libpython_helpers_m_compare(VALUE mod, VALUE op, VALUE pyptr_a, VALUE pyp
|
|
727
794
|
}
|
728
795
|
|
729
796
|
static int is_pyobject_wrapper(VALUE obj);
|
730
|
-
static PyObject * pycall_pyobject_wrapper_get_pyobj_ptr(VALUE obj);
|
731
797
|
|
732
798
|
VALUE
|
733
799
|
pycall_getattr_default(VALUE obj, char const *name, VALUE default_value)
|
@@ -858,6 +924,47 @@ pycall_extract_kwargs_from_ruby_hash(VALUE key, VALUE value, VALUE arg)
|
|
858
924
|
return ST_CONTINUE;
|
859
925
|
}
|
860
926
|
|
927
|
+
static void
|
928
|
+
pycall_interrupt_python_thread(void *ptr)
|
929
|
+
{
|
930
|
+
Py_API(PyErr_SetInterrupt)();
|
931
|
+
}
|
932
|
+
|
933
|
+
struct call_pyobject_call_params {
|
934
|
+
PyObject *pycallable;
|
935
|
+
PyObject *args;
|
936
|
+
PyObject *kwargs;
|
937
|
+
};
|
938
|
+
|
939
|
+
static inline PyObject *
|
940
|
+
call_pyobject_call(struct call_pyobject_call_params *params)
|
941
|
+
{
|
942
|
+
PyObject *res;
|
943
|
+
res = Py_API(PyObject_Call)(params->pycallable, params->args, params->kwargs); /* New reference */
|
944
|
+
return res;
|
945
|
+
}
|
946
|
+
|
947
|
+
PyObject *
|
948
|
+
pyobject_call(PyObject *pycallable, PyObject *args, PyObject *kwargs)
|
949
|
+
{
|
950
|
+
PyObject *res;
|
951
|
+
struct call_pyobject_call_params params;
|
952
|
+
params.pycallable = pycallable;
|
953
|
+
params.args = args;
|
954
|
+
params.kwargs = kwargs;
|
955
|
+
|
956
|
+
if (pycall_without_gvl_p()) {
|
957
|
+
res = (PyObject *)rb_thread_call_without_gvl(
|
958
|
+
(void * (*)(void *))call_pyobject_call, (void *)¶ms,
|
959
|
+
(rb_unblock_function_t *)pycall_interrupt_python_thread, NULL);
|
960
|
+
}
|
961
|
+
else {
|
962
|
+
res = call_pyobject_call(¶ms);
|
963
|
+
}
|
964
|
+
|
965
|
+
return res;
|
966
|
+
}
|
967
|
+
|
861
968
|
static VALUE
|
862
969
|
pycall_call_python_callable(PyObject *pycallable, int argc, VALUE *argv)
|
863
970
|
{
|
@@ -905,7 +1012,7 @@ pycall_call_python_callable(PyObject *pycallable, int argc, VALUE *argv)
|
|
905
1012
|
}
|
906
1013
|
}
|
907
1014
|
|
908
|
-
res =
|
1015
|
+
res = pyobject_call(pycallable, args, kwargs); /* New reference */
|
909
1016
|
if (!res) {
|
910
1017
|
pycall_pyerror_fetch_and_raise("PyObject_Call in pycall_call_python_callable");
|
911
1018
|
}
|
@@ -1213,7 +1320,7 @@ pycall_pyobject_wrapper_get_pyptr(VALUE obj)
|
|
1213
1320
|
return rb_funcall(obj, rb_intern("__pyptr__"), 0);
|
1214
1321
|
}
|
1215
1322
|
|
1216
|
-
|
1323
|
+
PyObject *
|
1217
1324
|
pycall_pyobject_wrapper_get_pyobj_ptr(VALUE obj)
|
1218
1325
|
{
|
1219
1326
|
VALUE pyptr = pycall_pyobject_wrapper_get_pyptr(obj);
|
@@ -1235,11 +1342,39 @@ pycall_pyobject_wrapper_check_get_pyobj_ptr(VALUE obj, PyTypeObject *pytypeobj)
|
|
1235
1342
|
|
1236
1343
|
/* ==== PyCall::Conversion ==== */
|
1237
1344
|
|
1345
|
+
static int
|
1346
|
+
get_mapped_ancestor_class_iter(VALUE key, VALUE value, VALUE arg)
|
1347
|
+
{
|
1348
|
+
VALUE *args = (VALUE *)arg;
|
1349
|
+
if (RTEST(pycall_pytypeptr_subclass_p(args[0], key))) {
|
1350
|
+
args[1] = value;
|
1351
|
+
return ST_STOP;
|
1352
|
+
}
|
1353
|
+
return ST_CONTINUE;
|
1354
|
+
}
|
1355
|
+
|
1356
|
+
static VALUE
|
1357
|
+
pycall_python_type_mapping_get_mapped_ancestor_class(VALUE pytypeptr)
|
1358
|
+
{
|
1359
|
+
VALUE args[2];
|
1360
|
+
args[0] = pytypeptr;
|
1361
|
+
args[1] = Qnil;
|
1362
|
+
|
1363
|
+
rb_hash_foreach(python_type_mapping, get_mapped_ancestor_class_iter, (VALUE)args);
|
1364
|
+
|
1365
|
+
return args[1];
|
1366
|
+
}
|
1367
|
+
|
1238
1368
|
static VALUE
|
1239
1369
|
pycall_python_type_mapping_get_mapped_class(VALUE pytypeptr)
|
1240
1370
|
{
|
1371
|
+
VALUE mapped;
|
1241
1372
|
(void)check_get_pytypeobj_ptr(pytypeptr);
|
1242
|
-
|
1373
|
+
mapped = rb_hash_lookup(python_type_mapping, pytypeptr);
|
1374
|
+
if (NIL_P(mapped)) {
|
1375
|
+
mapped = pycall_python_type_mapping_get_mapped_ancestor_class(pytypeptr);
|
1376
|
+
}
|
1377
|
+
return mapped;
|
1243
1378
|
}
|
1244
1379
|
|
1245
1380
|
static int
|
@@ -1947,6 +2082,10 @@ init_python(void)
|
|
1947
2082
|
Py_API(Py_InitializeEx)(0);
|
1948
2083
|
Py_API(PySys_SetArgvEx)(0, (char **)argv, 0);
|
1949
2084
|
|
2085
|
+
if (!Py_API(PyEval_ThreadsInitialized)()) {
|
2086
|
+
Py_API(PyEval_InitThreads)();
|
2087
|
+
}
|
2088
|
+
|
1950
2089
|
/* check the availability of stackless extension */
|
1951
2090
|
python_has_stackless_extension = (Py_API(PyImport_ImportModule)("stackless") != NULL);
|
1952
2091
|
if (!python_has_stackless_extension) {
|
@@ -2078,6 +2217,7 @@ init_tuple(void)
|
|
2078
2217
|
rb_define_singleton_method(cTuple, "new", pycall_tuple_s_new, -1);
|
2079
2218
|
rb_define_method(cTuple, "length", pycall_tuple_length, 0);
|
2080
2219
|
rb_define_method(cTuple, "to_a", pycall_tuple_to_a, 0);
|
2220
|
+
rb_define_alias(cTuple, "to_ary", "to_a");
|
2081
2221
|
}
|
2082
2222
|
|
2083
2223
|
void
|
@@ -2096,6 +2236,9 @@ Init_pycall(void)
|
|
2096
2236
|
|
2097
2237
|
rb_define_module_function(mPyCall, "after_fork", pycall_after_fork, 0);
|
2098
2238
|
|
2239
|
+
pycall_tls_create(&without_gvl_key);
|
2240
|
+
rb_define_module_function(mPyCall, "without_gvl", pycall_m_without_gvl, 0);
|
2241
|
+
|
2099
2242
|
/* PyCall::PyPtr */
|
2100
2243
|
|
2101
2244
|
cPyPtr = rb_define_class_under(mPyCall, "PyPtr", rb_cBasicObject);
|
@@ -2132,6 +2275,7 @@ Init_pycall(void)
|
|
2132
2275
|
rb_define_method(cPyTypePtr, "__tp_basicsize__", pycall_pytypeptr_get_tp_basicsize, 0);
|
2133
2276
|
rb_define_method(cPyTypePtr, "__tp_flags__", pycall_pytypeptr_get_tp_flags, 0);
|
2134
2277
|
rb_define_method(cPyTypePtr, "===", pycall_pytypeptr_eqq, 1);
|
2278
|
+
rb_define_method(cPyTypePtr, "<", pycall_pytypeptr_subclass_p, 1);
|
2135
2279
|
|
2136
2280
|
/* PyCall::LibPython::API */
|
2137
2281
|
|
data/ext/pycall/pycall.h
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#ifndef PYCALL_H
|
2
|
+
#define PYCALL_H 1
|
3
|
+
|
4
|
+
#if defined(__cplusplus)
|
5
|
+
extern "C" {
|
6
|
+
#if 0
|
7
|
+
} /* satisfy cc-mode */
|
8
|
+
#endif
|
9
|
+
#endif
|
10
|
+
|
11
|
+
VALUE pycall_pyptr_new(PyObject *pyobj);
|
12
|
+
PyObject *pycall_pyptr_get_pyobj_ptr(VALUE pyptr);
|
13
|
+
VALUE pycall_pyobject_to_ruby(PyObject *pyobj);
|
14
|
+
PyObject *pycall_pyobject_wrapper_get_pyobj_ptr(VALUE obj);
|
15
|
+
|
16
|
+
#if defined(__cplusplus)
|
17
|
+
#if 0
|
18
|
+
{ /* satisfy cc-mode */
|
19
|
+
#endif
|
20
|
+
} /* extern "C" { */
|
21
|
+
#endif
|
22
|
+
|
23
|
+
#endif /* PYCALL_H */
|
@@ -10,10 +10,20 @@ extern "C" {
|
|
10
10
|
|
11
11
|
#include <ruby.h>
|
12
12
|
#include <ruby/encoding.h>
|
13
|
+
#include <ruby/thread.h>
|
14
|
+
|
13
15
|
#include <assert.h>
|
14
16
|
#include <inttypes.h>
|
15
17
|
#include <limits.h>
|
16
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
|
+
|
17
27
|
#if SIZEOF_LONG == SIZEOF_VOIDP
|
18
28
|
# define PTR2NUM(x) (LONG2NUM((long)(x)))
|
19
29
|
# define NUM2PTR(x) ((void*)(NUM2ULONG(x)))
|
@@ -34,6 +44,9 @@ pycall_integer_type_p(VALUE obj)
|
|
34
44
|
#endif
|
35
45
|
|
36
46
|
void ruby_debug_breakpoint();
|
47
|
+
int ruby_thread_has_gvl_p(void);
|
48
|
+
|
49
|
+
#define CALL_WITH_GVL(func, data) rb_thread_call_with_gvl((void * (*)(void *))(func), (void *)(data))
|
37
50
|
|
38
51
|
/* ==== python ==== */
|
39
52
|
|
@@ -488,6 +501,23 @@ extern PyTypeObject PyRuby_Type;
|
|
488
501
|
|
489
502
|
PyObject * PyRuby_New(VALUE ruby_object);
|
490
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
|
+
|
491
521
|
/* ==== pycall ==== */
|
492
522
|
|
493
523
|
typedef struct {
|
@@ -525,6 +555,7 @@ typedef struct {
|
|
525
555
|
PyObject * (* _PyObject_New)(PyTypeObject *);
|
526
556
|
int (* PyCallable_Check)(PyObject *);
|
527
557
|
int (* PyObject_IsInstance)(PyObject *, PyObject *);
|
558
|
+
int (* PyObject_IsSubclass)(PyObject *, PyObject *);
|
528
559
|
union {
|
529
560
|
long (* _long)(PyObject *);
|
530
561
|
Py_hash_t (* _hash_t)(PyObject *);
|
@@ -581,12 +612,16 @@ typedef struct {
|
|
581
612
|
|
582
613
|
PyObject * (* PyIter_Next)(PyObject *);
|
583
614
|
|
615
|
+
int (* PyEval_ThreadsInitialized)(void);
|
616
|
+
void (* PyEval_InitThreads)(void);
|
617
|
+
|
584
618
|
PyObject * (* PyErr_Occurred)(void);
|
585
619
|
void (* PyErr_Fetch)(PyObject **, PyObject **, PyObject **);
|
586
620
|
void (* PyErr_Restore)(PyObject *, PyObject *, PyObject *);
|
587
621
|
void (* PyErr_Clear)(void);
|
588
622
|
void (* PyErr_SetString)(PyObject *, const char *); /* decoded from utf-8 */
|
589
623
|
void (* PyErr_Format)(PyObject *, const char *, ...); /* ASCII-encoded string */
|
624
|
+
void (* PyErr_SetInterrupt)(void);
|
590
625
|
|
591
626
|
PyObject * (* PyImport_ImportModule)(char const*);
|
592
627
|
PyObject * (* PyImport_ImportModuleLevel)(char const*, PyObject *, PyObject *, PyObject *, int);
|
@@ -637,7 +672,6 @@ VALUE pycall_import_module_level(char const *name, VALUE globals, VALUE locals,
|
|
637
672
|
VALUE pycall_getattr_default(VALUE pyobj, char const *name, VALUE default_value);
|
638
673
|
VALUE pycall_getattr(VALUE pyobj, char const *name);
|
639
674
|
|
640
|
-
VALUE pycall_pyobject_to_ruby(PyObject *);
|
641
675
|
VALUE pycall_pytype_to_ruby(PyObject *);
|
642
676
|
VALUE pycall_pymodule_to_ruby(PyObject *);
|
643
677
|
VALUE pycall_pybool_to_ruby(PyObject *);
|
data/ext/pycall/ruby_wrapper.c
CHANGED
@@ -1,33 +1,35 @@
|
|
1
1
|
#include "pycall_internal.h"
|
2
|
+
#include "pycall.h"
|
2
3
|
|
3
4
|
static PyMemberDef PyRuby_members[] = {
|
4
5
|
{"ruby_object_ptr", Py_T_PYSSIZET, offsetof(PyRubyObject, ruby_object), Py_READONLY},
|
5
6
|
{NULL} /* sentinel */
|
6
7
|
};
|
7
8
|
|
8
|
-
static
|
9
|
-
static
|
10
|
-
static PyObject *
|
11
|
-
static PyObject *
|
9
|
+
static VALUE PyRuby_get_ruby_object_and_set_pyerr(PyObject *pyobj);
|
10
|
+
static void PyRuby_dealloc_with_gvl(PyRubyObject *);
|
11
|
+
static PyObject * PyRuby_repr_with_gvl(PyRubyObject *);
|
12
|
+
static PyObject * PyRuby_call_with_gvl(PyRubyObject *, PyObject *, PyObject *);
|
13
|
+
static PyObject * PyRuby_getattro_with_gvl(PyRubyObject *, PyObject *);
|
12
14
|
|
13
15
|
PyTypeObject PyRuby_Type = {
|
14
16
|
PyVarObject_HEAD_INIT(NULL, 0)
|
15
17
|
"PyCall.ruby_object", /* tp_name */
|
16
18
|
sizeof(PyRubyObject), /* tp_basicsize */
|
17
19
|
0, /* tp_itemsize */
|
18
|
-
(destructor)
|
20
|
+
(destructor)PyRuby_dealloc_with_gvl, /* tp_dealloc */
|
19
21
|
0, /* tp_print */
|
20
22
|
0, /* tp_getattr */
|
21
23
|
0, /* tp_setattr */
|
22
24
|
0, /* tp_reserved */
|
23
|
-
(reprfunc)
|
25
|
+
(reprfunc)PyRuby_repr_with_gvl, /* tp_repr */
|
24
26
|
0, /* tp_as_number */
|
25
27
|
0, /* tp_as_sequence */
|
26
28
|
0, /* tp_as_mapping */
|
27
29
|
{0}, /* tp_hash */
|
28
|
-
(ternaryfunc)
|
30
|
+
(ternaryfunc)PyRuby_call_with_gvl, /* tp_call */
|
29
31
|
0, /* tp_str */
|
30
|
-
(getattrofunc)
|
32
|
+
(getattrofunc)PyRuby_getattro_with_gvl, /* tp_getattro */
|
31
33
|
0, /* tp_setattro */
|
32
34
|
0, /* tp_as_buffer */
|
33
35
|
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
@@ -42,8 +44,8 @@ PyTypeObject PyRuby_Type = {
|
|
42
44
|
PyRuby_members, /*tp_members*/
|
43
45
|
};
|
44
46
|
|
45
|
-
PyObject *
|
46
|
-
|
47
|
+
static PyObject *
|
48
|
+
PyRuby_New_impl(VALUE ruby_object)
|
47
49
|
{
|
48
50
|
PyRubyObject *op;
|
49
51
|
|
@@ -53,59 +55,17 @@ PyRuby_New(VALUE ruby_object)
|
|
53
55
|
return (PyObject *)op;
|
54
56
|
}
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
{
|
59
|
-
VALUE rb_mObjSpace;
|
60
|
-
object_id = rb_check_to_integer(object_id, "to_int");
|
61
|
-
rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
|
62
|
-
return rb_funcall(rb_mObjSpace, rb_intern("_id2ref"), 1, object_id);
|
63
|
-
}
|
64
|
-
|
65
|
-
static VALUE
|
66
|
-
protect_id2ref(VALUE object_id)
|
67
|
-
{
|
68
|
-
VALUE obj;
|
69
|
-
int state;
|
70
|
-
|
71
|
-
obj = rb_protect((VALUE (*)(VALUE))funcall_id2ref, object_id, &state);
|
72
|
-
if (state)
|
73
|
-
return Qundef;
|
74
|
-
|
75
|
-
return obj;
|
76
|
-
}
|
77
|
-
|
78
|
-
static VALUE
|
79
|
-
protect_id2ref_and_set_pyerr(VALUE object_id)
|
58
|
+
PyObject *
|
59
|
+
PyRuby_New(VALUE ruby_object)
|
80
60
|
{
|
81
|
-
|
82
|
-
|
83
|
-
return obj;
|
84
|
-
|
85
|
-
obj = rb_errinfo();
|
86
|
-
if (RTEST(rb_obj_is_kind_of(obj, rb_eRangeError))) {
|
87
|
-
Py_API(PyErr_SetString)(Py_API(PyExc_RuntimeError), "[BUG] referenced object was garbage-collected");
|
61
|
+
if (!ruby_thread_has_gvl_p()) {
|
62
|
+
CALL_WITH_GVL(PyRuby_New_impl, ruby_object);
|
88
63
|
}
|
89
|
-
else {
|
90
|
-
VALUE emesg = rb_check_funcall(obj, rb_intern("message"), 0, 0);
|
91
|
-
Py_API(PyErr_Format)(Py_API(PyExc_RuntimeError),
|
92
|
-
"[BUG] Unable to obtain ruby object from ID: %s (%s)",
|
93
|
-
StringValueCStr(emesg), rb_class2name(CLASS_OF(obj)));
|
94
|
-
}
|
95
|
-
return Qundef;
|
96
|
-
}
|
97
64
|
|
98
|
-
|
99
|
-
PyRuby_get_ruby_object_and_set_pyerr(PyObject *pyobj)
|
100
|
-
{
|
101
|
-
VALUE obj_id;
|
102
|
-
if (!PyRuby_Check(pyobj))
|
103
|
-
return Qundef;
|
104
|
-
obj_id = rb_obj_id(PyRuby_get_ruby_object(pyobj));
|
105
|
-
return protect_id2ref_and_set_pyerr(obj_id);
|
65
|
+
return PyRuby_New_impl(ruby_object);
|
106
66
|
}
|
107
67
|
|
108
|
-
static void
|
68
|
+
static void *
|
109
69
|
PyRuby_dealloc(PyRubyObject *pyro)
|
110
70
|
{
|
111
71
|
VALUE obj;
|
@@ -117,9 +77,20 @@ PyRuby_dealloc(PyRubyObject *pyro)
|
|
117
77
|
#endif /* PYCALL_DEBUG_DUMP_REFCNT */
|
118
78
|
|
119
79
|
if (obj == Qundef)
|
120
|
-
return;
|
80
|
+
return NULL;
|
121
81
|
|
122
82
|
pycall_gcguard_unregister_pyrubyobj((PyObject *)pyro);
|
83
|
+
|
84
|
+
return NULL;
|
85
|
+
}
|
86
|
+
|
87
|
+
static void
|
88
|
+
PyRuby_dealloc_with_gvl(PyRubyObject *pyro)
|
89
|
+
{
|
90
|
+
if (!ruby_thread_has_gvl_p()) {
|
91
|
+
CALL_WITH_GVL(PyRuby_dealloc, pyro);
|
92
|
+
}
|
93
|
+
PyRuby_dealloc(pyro);
|
123
94
|
}
|
124
95
|
|
125
96
|
static PyObject *
|
@@ -137,6 +108,15 @@ PyRuby_repr(PyRubyObject *pyro)
|
|
137
108
|
return res;
|
138
109
|
}
|
139
110
|
|
111
|
+
static PyObject *
|
112
|
+
PyRuby_repr_with_gvl(PyRubyObject *pyro)
|
113
|
+
{
|
114
|
+
if (!ruby_thread_has_gvl_p()) {
|
115
|
+
return CALL_WITH_GVL(PyRuby_repr, pyro);
|
116
|
+
}
|
117
|
+
return PyRuby_repr(pyro);
|
118
|
+
}
|
119
|
+
|
140
120
|
#if SIZEOF_SSIZE_T < 8
|
141
121
|
/* int64to32hash from src/support/hashing.c in Julia */
|
142
122
|
static inline uint32_t
|
@@ -152,7 +132,7 @@ int64to32hash(uint64_t key)
|
|
152
132
|
}
|
153
133
|
#endif
|
154
134
|
|
155
|
-
static
|
135
|
+
static void *
|
156
136
|
PyRuby_hash_long(PyRubyObject *pyro)
|
157
137
|
{
|
158
138
|
VALUE obj, rbhash;
|
@@ -160,15 +140,24 @@ PyRuby_hash_long(PyRubyObject *pyro)
|
|
160
140
|
|
161
141
|
obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
|
162
142
|
if (obj == Qundef)
|
163
|
-
return -1;
|
143
|
+
return (void *)-1;
|
164
144
|
|
165
145
|
rbhash = rb_hash(obj);
|
166
146
|
h = FIX2LONG(rbhash); /* Ruby's hash value is a Fixnum */
|
167
147
|
|
168
|
-
return h == -1 ? pycall_hash_salt : h;
|
148
|
+
return (void *)(h == -1 ? pycall_hash_salt : h);
|
169
149
|
}
|
170
150
|
|
171
|
-
static
|
151
|
+
static long
|
152
|
+
PyRuby_hash_long_with_gvl(PyRubyObject *pyro)
|
153
|
+
{
|
154
|
+
if (!ruby_thread_has_gvl_p()) {
|
155
|
+
return (long)CALL_WITH_GVL(PyRuby_hash_long, pyro);
|
156
|
+
}
|
157
|
+
return (long)PyRuby_hash_long(pyro);
|
158
|
+
}
|
159
|
+
|
160
|
+
static void *
|
172
161
|
PyRuby_hash_hash_t(PyRubyObject *pyro)
|
173
162
|
{
|
174
163
|
VALUE obj, rbhash;
|
@@ -176,20 +165,29 @@ PyRuby_hash_hash_t(PyRubyObject *pyro)
|
|
176
165
|
|
177
166
|
obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
|
178
167
|
if (obj == Qundef)
|
179
|
-
return -1;
|
168
|
+
return (void *)-1;
|
180
169
|
|
181
170
|
rbhash = rb_hash(obj);
|
182
171
|
#if SIZEOF_PY_HASH_T == SIZEOF_LONG
|
183
172
|
/* In this case, we can assume sizeof(Py_hash_t) == sizeof(long) */
|
184
173
|
h = NUM2SSIZET(rbhash);
|
185
|
-
return h == -1 ? (Py_hash_t)pycall_hash_salt : h;
|
174
|
+
return (void *)(h == -1 ? (Py_hash_t)pycall_hash_salt : h);
|
186
175
|
#else
|
187
176
|
/* In this case, we can assume sizeof(long) == 4 and sizeof(Py_hash_t) == 8 */
|
188
177
|
h = (pycall_hash_salt_32 << 32) | FIX2LONG(rbhash);
|
189
|
-
return h == -1 ? ((pycall_hash_salt << 32) | pycall_hash_salt) : h;
|
178
|
+
return (void *)(h == -1 ? ((pycall_hash_salt << 32) | pycall_hash_salt) : h);
|
190
179
|
#endif
|
191
180
|
}
|
192
181
|
|
182
|
+
static Py_hash_t
|
183
|
+
PyRuby_hash_hash_t_with_gvl(PyRubyObject *pyro)
|
184
|
+
{
|
185
|
+
if (!ruby_thread_has_gvl_p()) {
|
186
|
+
return (Py_hash_t)CALL_WITH_GVL(PyRuby_hash_hash_t, pyro);
|
187
|
+
}
|
188
|
+
return (Py_hash_t)PyRuby_hash_hash_t(pyro);
|
189
|
+
}
|
190
|
+
|
193
191
|
struct call_rb_funcallv_params {
|
194
192
|
VALUE recv;
|
195
193
|
ID meth;
|
@@ -221,15 +219,21 @@ rb_protect_funcallv(VALUE recv, ID meth, int argc, VALUE *argv, int *pstate)
|
|
221
219
|
return res;
|
222
220
|
}
|
223
221
|
|
222
|
+
struct PyRuby_call_params {
|
223
|
+
PyRubyObject *pyro;
|
224
|
+
PyObject *pyobj_args;
|
225
|
+
PyObject *pyobj_kwargs;
|
226
|
+
};
|
227
|
+
|
224
228
|
static PyObject *
|
225
|
-
PyRuby_call(
|
229
|
+
PyRuby_call(struct PyRuby_call_params *params)
|
226
230
|
{
|
227
231
|
ID id_call;
|
228
232
|
VALUE obj, args, kwargs, res;
|
229
233
|
PyObject *pyobj_res;
|
230
234
|
int state;
|
231
235
|
|
232
|
-
obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
|
236
|
+
obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)params->pyro);
|
233
237
|
if (obj == Qundef)
|
234
238
|
return NULL;
|
235
239
|
|
@@ -239,9 +243,9 @@ PyRuby_call(PyRubyObject *pyro, PyObject *pyobj_args, PyObject *pyobj_kwargs)
|
|
239
243
|
return NULL;
|
240
244
|
}
|
241
245
|
|
242
|
-
args = pycall_pyobject_to_a(pyobj_args);
|
243
|
-
if (pyobj_kwargs) {
|
244
|
-
kwargs = pycall_pyobject_to_ruby(pyobj_kwargs);
|
246
|
+
args = pycall_pyobject_to_a(params->pyobj_args);
|
247
|
+
if (params->pyobj_kwargs) {
|
248
|
+
kwargs = pycall_pyobject_to_ruby(params->pyobj_kwargs);
|
245
249
|
rb_ary_push(args, kwargs);
|
246
250
|
}
|
247
251
|
|
@@ -255,18 +259,38 @@ PyRuby_call(PyRubyObject *pyro, PyObject *pyobj_args, PyObject *pyobj_kwargs)
|
|
255
259
|
}
|
256
260
|
|
257
261
|
static PyObject *
|
258
|
-
|
262
|
+
PyRuby_call_with_gvl(PyRubyObject *pyro, PyObject *pyobj_args, PyObject *pyobj_kwargs)
|
263
|
+
{
|
264
|
+
struct PyRuby_call_params params;
|
265
|
+
params.pyro = pyro;
|
266
|
+
params.pyobj_args = pyobj_args;
|
267
|
+
params.pyobj_kwargs = pyobj_kwargs;
|
268
|
+
|
269
|
+
if (!ruby_thread_has_gvl_p()) {
|
270
|
+
return CALL_WITH_GVL(PyRuby_call, ¶ms);
|
271
|
+
}
|
272
|
+
|
273
|
+
return PyRuby_call(¶ms);
|
274
|
+
}
|
275
|
+
|
276
|
+
struct PyRuby_getattro_params {
|
277
|
+
PyRubyObject *pyro;
|
278
|
+
PyObject *pyobj_name;
|
279
|
+
};
|
280
|
+
|
281
|
+
static PyObject *
|
282
|
+
PyRuby_getattro(struct PyRuby_getattro_params *params)
|
259
283
|
{
|
260
284
|
VALUE obj, name, res;
|
261
285
|
char const *name_cstr;
|
262
286
|
ID name_id;
|
263
287
|
PyObject *pyobj_res;
|
264
288
|
|
265
|
-
obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
|
289
|
+
obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)params->pyro);
|
266
290
|
if (obj == Qundef)
|
267
291
|
return NULL;
|
268
292
|
|
269
|
-
name = pycall_pyobject_to_ruby(pyobj_name);
|
293
|
+
name = pycall_pyobject_to_ruby(params->pyobj_name);
|
270
294
|
name_cstr = StringValueCStr(name);
|
271
295
|
name_id = rb_intern(name_cstr);
|
272
296
|
|
@@ -299,21 +323,35 @@ PyRuby_getattro(PyRubyObject *pyro, PyObject *pyobj_name)
|
|
299
323
|
else if (name_cstr[0] == '_' && name_cstr[1] == '_') {
|
300
324
|
/* name.start_with? "__" */
|
301
325
|
/* TODO: handle `__code__` and `func_code` */
|
302
|
-
return Py_API(PyObject_GenericGetAttr)((PyObject *)pyro, pyobj_name);
|
326
|
+
return Py_API(PyObject_GenericGetAttr)((PyObject *)params->pyro, params->pyobj_name);
|
303
327
|
}
|
304
328
|
else {
|
305
329
|
/* TODO: handle `__code__` and `func_code` */
|
306
330
|
if (rb_respond_to(obj, name_id)) {
|
307
331
|
VALUE method = rb_obj_method(obj, name);
|
308
|
-
return
|
332
|
+
return PyRuby_New_impl(method);
|
309
333
|
}
|
310
|
-
return Py_API(PyObject_GenericGetAttr)((PyObject *)pyro, pyobj_name);
|
334
|
+
return Py_API(PyObject_GenericGetAttr)((PyObject *)params->pyro, params->pyobj_name);
|
311
335
|
}
|
312
336
|
|
313
337
|
pyobj_res = pycall_pyobject_from_ruby(res);
|
314
338
|
return pyobj_res;
|
315
339
|
}
|
316
340
|
|
341
|
+
static PyObject *
|
342
|
+
PyRuby_getattro_with_gvl(PyRubyObject *pyro, PyObject *pyobj_name)
|
343
|
+
{
|
344
|
+
struct PyRuby_getattro_params params;
|
345
|
+
params.pyro = pyro;
|
346
|
+
params.pyobj_name = pyobj_name;
|
347
|
+
|
348
|
+
if (!ruby_thread_has_gvl_p()) {
|
349
|
+
return CALL_WITH_GVL(PyRuby_getattro, ¶ms);
|
350
|
+
}
|
351
|
+
|
352
|
+
return PyRuby_getattro(¶ms);
|
353
|
+
}
|
354
|
+
|
317
355
|
/* ==== PyCall::PyRubyPtr ==== */
|
318
356
|
|
319
357
|
VALUE cPyRubyPtr;
|
@@ -411,9 +449,9 @@ pycall_init_ruby_wrapper(void)
|
|
411
449
|
PyRuby_Type.tp_flags |= pycall_default_tp_flags();
|
412
450
|
PyRuby_Type.tp_new = Py_API(PyType_GenericNew);
|
413
451
|
if (pycall_python_long_hash)
|
414
|
-
PyRuby_Type.tp_hash._long = (hashfunc_long)
|
452
|
+
PyRuby_Type.tp_hash._long = (hashfunc_long)PyRuby_hash_long_with_gvl;
|
415
453
|
else
|
416
|
-
PyRuby_Type.tp_hash._hash_t = (hashfunc_hash_t)
|
454
|
+
PyRuby_Type.tp_hash._hash_t = (hashfunc_hash_t)PyRuby_hash_hash_t_with_gvl;
|
417
455
|
|
418
456
|
if (Py_API(PyType_Ready)(&PyRuby_Type) < 0) {
|
419
457
|
pycall_pyerror_fetch_and_raise("PyType_Ready in pycall_init_ruby_wrapper");
|
@@ -430,3 +468,57 @@ pycall_init_ruby_wrapper(void)
|
|
430
468
|
|
431
469
|
rb_define_module_function(mPyCall, "wrap_ruby_object", pycall_m_wrap_ruby_object, 1);
|
432
470
|
}
|
471
|
+
|
472
|
+
/* --- File internal utilities --- */
|
473
|
+
|
474
|
+
static VALUE
|
475
|
+
funcall_id2ref(VALUE object_id)
|
476
|
+
{
|
477
|
+
VALUE rb_mObjSpace;
|
478
|
+
object_id = rb_check_to_integer(object_id, "to_int");
|
479
|
+
rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
|
480
|
+
return rb_funcall(rb_mObjSpace, rb_intern("_id2ref"), 1, object_id);
|
481
|
+
}
|
482
|
+
|
483
|
+
static VALUE
|
484
|
+
protect_id2ref(VALUE object_id)
|
485
|
+
{
|
486
|
+
VALUE obj;
|
487
|
+
int state;
|
488
|
+
|
489
|
+
obj = rb_protect((VALUE (*)(VALUE))funcall_id2ref, object_id, &state);
|
490
|
+
if (state)
|
491
|
+
return Qundef;
|
492
|
+
|
493
|
+
return obj;
|
494
|
+
}
|
495
|
+
|
496
|
+
static VALUE
|
497
|
+
protect_id2ref_and_set_pyerr(VALUE object_id)
|
498
|
+
{
|
499
|
+
VALUE obj = protect_id2ref(object_id);
|
500
|
+
if (obj != Qundef)
|
501
|
+
return obj;
|
502
|
+
|
503
|
+
obj = rb_errinfo();
|
504
|
+
if (RTEST(rb_obj_is_kind_of(obj, rb_eRangeError))) {
|
505
|
+
Py_API(PyErr_SetString)(Py_API(PyExc_RuntimeError), "[BUG] referenced object was garbage-collected");
|
506
|
+
}
|
507
|
+
else {
|
508
|
+
VALUE emesg = rb_check_funcall(obj, rb_intern("message"), 0, 0);
|
509
|
+
Py_API(PyErr_Format)(Py_API(PyExc_RuntimeError),
|
510
|
+
"[BUG] Unable to obtain ruby object from ID: %s (%s)",
|
511
|
+
StringValueCStr(emesg), rb_class2name(CLASS_OF(obj)));
|
512
|
+
}
|
513
|
+
return Qundef;
|
514
|
+
}
|
515
|
+
|
516
|
+
static VALUE
|
517
|
+
PyRuby_get_ruby_object_and_set_pyerr(PyObject *pyobj)
|
518
|
+
{
|
519
|
+
VALUE obj_id;
|
520
|
+
if (!PyRuby_Check(pyobj))
|
521
|
+
return Qundef;
|
522
|
+
obj_id = rb_obj_id(PyRuby_get_ruby_object(pyobj));
|
523
|
+
return protect_id2ref_and_set_pyerr(obj_id);
|
524
|
+
}
|