Skip to content

Commit 69cf4dd

Browse files
committed
Add PyCall.setattr and PyCall.delattr
1 parent df21b83 commit 69cf4dd

File tree

6 files changed

+101
-0
lines changed

6 files changed

+101
-0
lines changed

ext/pycall/libpython.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ pycall_init_libpython_api_table(VALUE libpython_handle)
110110
INIT_API_TABLE_ENTRY(PyObject_GetAttrString, required);
111111
INIT_API_TABLE_ENTRY(PyObject_SetAttrString, required);
112112
INIT_API_TABLE_ENTRY(PyObject_HasAttrString, required);
113+
INIT_API_TABLE_ENTRY(PyObject_DelAttrString, optional);
113114
INIT_API_TABLE_ENTRY(PyObject_GetItem, required);
114115
INIT_API_TABLE_ENTRY(PyObject_SetItem, required);
115116
INIT_API_TABLE_ENTRY(PyObject_DelItem, required);

ext/pycall/pycall.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,51 @@ pycall_libpython_helpers_m_hasattr_p(VALUE mod, VALUE pyptr, VALUE name)
861861
return res ? Qtrue : Qfalse;
862862
}
863863

864+
static VALUE
865+
pycall_libpython_helpers_m_setattr(VALUE mod, VALUE pyptr, VALUE name, VALUE val)
866+
{
867+
PyObject *pyobj, *pyval;
868+
869+
if (!is_pycall_pyptr(pyptr)) {
870+
rb_raise(rb_eTypeError, "PyCall::PyPtr is required");
871+
}
872+
873+
pyobj = get_pyobj_ptr(pyptr);
874+
875+
if (RB_TYPE_P(name, T_SYMBOL)) {
876+
name = rb_sym_to_s(name);
877+
}
878+
879+
pyval = pycall_pyobject_from_ruby(val);
880+
if (Py_API(PyObject_SetAttrString)(pyobj, StringValueCStr(name), pyval) == -1) {
881+
pycall_pyerror_fetch_and_raise("PyObject_SetAttrString");
882+
}
883+
884+
return Qnil;
885+
}
886+
887+
static VALUE
888+
pycall_libpython_helpers_m_delattr(VALUE mod, VALUE pyptr, VALUE name)
889+
{
890+
PyObject *pyobj;
891+
892+
if (!is_pycall_pyptr(pyptr)) {
893+
rb_raise(rb_eTypeError, "PyCall::PyPtr is required");
894+
}
895+
896+
pyobj = get_pyobj_ptr(pyptr);
897+
898+
if (RB_TYPE_P(name, T_SYMBOL)) {
899+
name = rb_sym_to_s(name);
900+
}
901+
902+
if (Py_API(PyObject_DelAttrString)(pyobj, StringValueCStr(name)) == -1) {
903+
pycall_pyerror_fetch_and_raise("PyObject_DelAttrString");
904+
}
905+
906+
return Qnil;
907+
}
908+
864909
static VALUE
865910
pycall_libpython_helpers_m_callable_p(VALUE mod, VALUE pyptr)
866911
{
@@ -2074,11 +2119,24 @@ pycall_pystring_from_formatv(char const *format, va_list vargs)
20742119

20752120
/* ==== Python ==== */
20762121

2122+
int
2123+
pycall_PyObject_DelAttrString(PyObject *pyobj, const char *attr_name)
2124+
{
2125+
/* PyObject_DelAttrString is defined by using PyObject_SetAttrString in CPython's abstract.h */
2126+
return Py_API(PyObject_SetAttrString)(pyobj, attr_name, NULL);
2127+
}
2128+
20772129
static void
20782130
init_python(void)
20792131
{
20802132
static char const *argv[1] = { "" };
20812133

2134+
/* optional functions */
2135+
if (! Py_API(PyObject_DelAttrString)) {
2136+
/* The case of PyObject_DelAttrString as a macro */
2137+
Py_API(PyObject_DelAttrString) = pycall_PyObject_DelAttrString;
2138+
}
2139+
20822140
Py_API(Py_InitializeEx)(0);
20832141
Py_API(PySys_SetArgvEx)(0, (char **)argv, 0);
20842142

@@ -2301,6 +2359,8 @@ Init_pycall(void)
23012359
rb_define_module_function(mHelpers, "compare", pycall_libpython_helpers_m_compare, 3);
23022360
rb_define_module_function(mHelpers, "getattr", pycall_libpython_helpers_m_getattr, -1);
23032361
rb_define_module_function(mHelpers, "hasattr?", pycall_libpython_helpers_m_hasattr_p, 2);
2362+
rb_define_module_function(mHelpers, "setattr", pycall_libpython_helpers_m_setattr, 3);
2363+
rb_define_module_function(mHelpers, "delattr", pycall_libpython_helpers_m_delattr, 2);
23042364
rb_define_module_function(mHelpers, "callable?", pycall_libpython_helpers_m_callable_p, 1);
23052365
rb_define_module_function(mHelpers, "call_object", pycall_libpython_helpers_m_call_object, -1);
23062366
rb_define_module_function(mHelpers, "getitem", pycall_libpython_helpers_m_getitem, 2);

ext/pycall/pycall_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ typedef struct {
568568
PyObject * (* PyObject_GetAttrString)(PyObject *, char const *);
569569
int (* PyObject_SetAttrString)(PyObject *, char const *, PyObject *);
570570
int (* PyObject_HasAttrString)(PyObject *, char const *);
571+
int (* PyObject_DelAttrString)(PyObject *, char const *);
571572
PyObject * (* PyObject_GetItem)(PyObject *, PyObject *);
572573
int (* PyObject_SetItem)(PyObject *obj, PyObject *key, PyObject *value);
573574
int (* PyObject_DelItem)(PyObject *, PyObject *);

lib/pycall.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ def hasattr?(obj, name)
5959
LibPython::Helpers.hasattr?(obj.__pyptr__, name)
6060
end
6161

62+
def setattr(obj, name, val)
63+
LibPython::Helpers.setattr(obj.__pyptr__, name, val)
64+
end
65+
66+
def delattr(obj, name)
67+
LibPython::Helpers.delattr(obj.__pyptr__, name)
68+
end
69+
6270
def same?(left, right)
6371
case left
6472
when PyObjectWrapper

test/python/pycall/simple_class.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class SimpleClass(object):
2+
class NestedClass:
3+
pass
4+
5+
def __init__(self, x=0):
6+
self.x = x
7+
8+
def initialize(self, x):
9+
self.x = x
10+
return 'initialized'
11+
12+
class SimpleSubClass(SimpleClass):
13+
pass

test/test-pycall.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ def test_VERSION
33
assert_not_nil(PyCall::VERSION)
44
end
55

6+
def test_setattr
7+
simple_class = PyCall.import_module("pycall.simple_class").SimpleClass
8+
pyobj = simple_class.new(42)
9+
before = pyobj.x
10+
PyCall.setattr(pyobj, :x, 1)
11+
assert_equal([42, 1],
12+
[before, pyobj.x])
13+
end
14+
15+
def test_delattr
16+
simple_class = PyCall.import_module("pycall.simple_class").SimpleClass
17+
pyobj = simple_class.new(42)
18+
PyCall.delattr(pyobj, :x)
19+
assert do
20+
not PyCall.hasattr?(pyobj, :x)
21+
end
22+
end
23+
624
def test_iterable
725
simple_iterable = PyCall.import_module("pycall.simple_iterable")
826
int_gen = simple_iterable.IntGenerator.new(10, 25)

0 commit comments

Comments
 (0)