Skip to content

Commit f324967

Browse files
committed
Merge branch 'inheritance_support_in_type_map'
2 parents 631b605 + 50a72a3 commit f324967

File tree

10 files changed

+126
-222
lines changed

10 files changed

+126
-222
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ Ruby to Python.
1717

1818
pycall.rb supports Ruby version 2.3 or higher.
1919

20+
## Supported Python versions
21+
22+
pycall.rb supports Python version 2.7 or higher.
23+
24+
Note that in Python 2.7 old-style class, that is defined without a super class, is not fully supported in pycall.rb.
25+
2026
## Installation
2127

2228
Add this line to your application's Gemfile:

ext/pycall/libpython.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pycall_init_libpython_api_table(VALUE libpython_handle)
9494
INIT_API_TABLE_ENTRY(_PyObject_New, required);
9595
INIT_API_TABLE_ENTRY(PyCallable_Check, required);
9696
INIT_API_TABLE_ENTRY(PyObject_IsInstance, required);
97+
INIT_API_TABLE_ENTRY(PyObject_IsSubclass, required);
9798
INIT_API_TABLE_ENTRY2(PyObject_Hash._hash_t, PyObject_Hash, required);
9899
INIT_API_TABLE_ENTRY(PyObject_RichCompare, required);
99100
INIT_API_TABLE_ENTRY(PyObject_Call, required);

ext/pycall/pycall.c

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ get_pytypeobj_ptr(VALUE obj)
452452
static inline PyTypeObject*
453453
try_get_pytypeobj_ptr(VALUE obj)
454454
{
455-
if (is_pycall_pytypeptr(obj)) return NULL;
455+
if (!is_pycall_pytypeptr(obj)) return NULL;
456456
return (PyTypeObject*)DATA_PTR(obj);
457457
}
458458

@@ -541,6 +541,20 @@ pycall_pytypeptr_eqq(VALUE obj, VALUE other)
541541
return Qfalse;
542542
}
543543

544+
static VALUE
545+
pycall_pytypeptr_subclass_p(VALUE obj, VALUE other)
546+
{
547+
PyTypeObject* pytype = get_pytypeobj_ptr(obj);
548+
if (is_pycall_pyptr(other)) {
549+
PyTypeObject* pytype_other = try_get_pytypeobj_ptr(other);
550+
if (pytype_other) {
551+
int res = Py_API(PyObject_IsSubclass)((PyObject *)pytype, (PyObject *)pytype_other);
552+
return res ? Qtrue : Qfalse;
553+
}
554+
}
555+
return Qfalse;
556+
}
557+
544558
/* ==== PyCall::LibPython::API ==== */
545559

546560
static VALUE
@@ -1277,11 +1291,39 @@ pycall_pyobject_wrapper_check_get_pyobj_ptr(VALUE obj, PyTypeObject *pytypeobj)
12771291

12781292
/* ==== PyCall::Conversion ==== */
12791293

1294+
static int
1295+
get_mapped_ancestor_class_iter(VALUE key, VALUE value, VALUE arg)
1296+
{
1297+
VALUE *args = (VALUE *)arg;
1298+
if (RTEST(pycall_pytypeptr_subclass_p(args[0], key))) {
1299+
args[1] = value;
1300+
return ST_STOP;
1301+
}
1302+
return ST_CONTINUE;
1303+
}
1304+
1305+
static VALUE
1306+
pycall_python_type_mapping_get_mapped_ancestor_class(VALUE pytypeptr)
1307+
{
1308+
VALUE args[2];
1309+
args[0] = pytypeptr;
1310+
args[1] = Qnil;
1311+
1312+
rb_hash_foreach(python_type_mapping, get_mapped_ancestor_class_iter, (VALUE)args);
1313+
1314+
return args[1];
1315+
}
1316+
12801317
static VALUE
12811318
pycall_python_type_mapping_get_mapped_class(VALUE pytypeptr)
12821319
{
1320+
VALUE mapped;
12831321
(void)check_get_pytypeobj_ptr(pytypeptr);
1284-
return rb_hash_lookup(python_type_mapping, pytypeptr);
1322+
mapped = rb_hash_lookup(python_type_mapping, pytypeptr);
1323+
if (NIL_P(mapped)) {
1324+
mapped = pycall_python_type_mapping_get_mapped_ancestor_class(pytypeptr);
1325+
}
1326+
return mapped;
12851327
}
12861328

12871329
static int
@@ -2179,6 +2221,7 @@ Init_pycall(void)
21792221
rb_define_method(cPyTypePtr, "__tp_basicsize__", pycall_pytypeptr_get_tp_basicsize, 0);
21802222
rb_define_method(cPyTypePtr, "__tp_flags__", pycall_pytypeptr_get_tp_flags, 0);
21812223
rb_define_method(cPyTypePtr, "===", pycall_pytypeptr_eqq, 1);
2224+
rb_define_method(cPyTypePtr, "<", pycall_pytypeptr_subclass_p, 1);
21822225

21832226
/* PyCall::LibPython::API */
21842227

ext/pycall/pycall_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ typedef struct {
529529
PyObject * (* _PyObject_New)(PyTypeObject *);
530530
int (* PyCallable_Check)(PyObject *);
531531
int (* PyObject_IsInstance)(PyObject *, PyObject *);
532+
int (* PyObject_IsSubclass)(PyObject *, PyObject *);
532533
union {
533534
long (* _long)(PyObject *);
534535
Py_hash_t (* _hash_t)(PyObject *);

lib/pycall/conversion.rb

Lines changed: 0 additions & 173 deletions
This file was deleted.

lib/pycall/pytypeobject_wrapper.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ def ===(other)
4949
end
5050
end
5151

52+
def <(other)
53+
case other
54+
when PyTypeObjectWrapper
55+
__pyptr__ < other.__pyptr__
56+
else
57+
raise TypeError, "compared with non class/module"
58+
end
59+
end
60+
5261
private
5362

5463
def register_python_type_mapping

lib/pycall/tuple.rb

Lines changed: 0 additions & 46 deletions
This file was deleted.

spec/pycall/conversion_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,42 @@ module PyCall
115115
subject { Conversion.to_ruby(Conversion.from_ruby(large_string)) }
116116
it { is_expected.to eq(large_string) }
117117
end
118+
119+
describe 'inheritance support' do
120+
let(:pymod) { PyCall.import_module('pycall.simple_class') }
121+
let(:simple_class) { pymod.SimpleClass }
122+
let(:simple_sub_class) { pymod.SimpleSubClass }
123+
124+
context 'when the super class was registered to python type mapping' do
125+
before do
126+
Conversion.register_python_type_mapping(simple_class.__pyptr__, simple_class)
127+
end
128+
129+
after do
130+
Conversion.unregister_python_type_mapping(simple_class.__pyptr__)
131+
end
132+
133+
specify do
134+
expect(simple_class.new).to be_instance_of(simple_class)
135+
expect(simple_sub_class.new).to be_instance_of(simple_class)
136+
end
137+
138+
context 'when the subclass was also registered to python type mapping' do
139+
before do
140+
Conversion.register_python_type_mapping(simple_sub_class.__pyptr__, simple_sub_class)
141+
end
142+
143+
after do
144+
Conversion.unregister_python_type_mapping(simple_sub_class.__pyptr__)
145+
end
146+
147+
specify do
148+
expect(simple_class.new).to be_instance_of(simple_class)
149+
expect(simple_sub_class.new).to be_instance_of(simple_sub_class)
150+
end
151+
end
152+
end
153+
end
118154
end
119155

120156
describe '.from_ruby' do

0 commit comments

Comments
 (0)