Skip to content

Commit

Permalink
Add PyCall.setattr and PyCall.delattr
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkn committed Sep 9, 2021
1 parent df21b83 commit 69cf4dd
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 0 deletions.
1 change: 1 addition & 0 deletions ext/pycall/libpython.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pycall_init_libpython_api_table(VALUE libpython_handle)
INIT_API_TABLE_ENTRY(PyObject_GetAttrString, required);
INIT_API_TABLE_ENTRY(PyObject_SetAttrString, required);
INIT_API_TABLE_ENTRY(PyObject_HasAttrString, required);
INIT_API_TABLE_ENTRY(PyObject_DelAttrString, optional);
INIT_API_TABLE_ENTRY(PyObject_GetItem, required);
INIT_API_TABLE_ENTRY(PyObject_SetItem, required);
INIT_API_TABLE_ENTRY(PyObject_DelItem, required);
Expand Down
60 changes: 60 additions & 0 deletions ext/pycall/pycall.c
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,51 @@ pycall_libpython_helpers_m_hasattr_p(VALUE mod, VALUE pyptr, VALUE name)
return res ? Qtrue : Qfalse;
}

static VALUE
pycall_libpython_helpers_m_setattr(VALUE mod, VALUE pyptr, VALUE name, VALUE val)
{
PyObject *pyobj, *pyval;

if (!is_pycall_pyptr(pyptr)) {
rb_raise(rb_eTypeError, "PyCall::PyPtr is required");
}

pyobj = get_pyobj_ptr(pyptr);

if (RB_TYPE_P(name, T_SYMBOL)) {
name = rb_sym_to_s(name);
}

pyval = pycall_pyobject_from_ruby(val);
if (Py_API(PyObject_SetAttrString)(pyobj, StringValueCStr(name), pyval) == -1) {
pycall_pyerror_fetch_and_raise("PyObject_SetAttrString");
}

return Qnil;
}

static VALUE
pycall_libpython_helpers_m_delattr(VALUE mod, VALUE pyptr, VALUE name)
{
PyObject *pyobj;

if (!is_pycall_pyptr(pyptr)) {
rb_raise(rb_eTypeError, "PyCall::PyPtr is required");
}

pyobj = get_pyobj_ptr(pyptr);

if (RB_TYPE_P(name, T_SYMBOL)) {
name = rb_sym_to_s(name);
}

if (Py_API(PyObject_DelAttrString)(pyobj, StringValueCStr(name)) == -1) {
pycall_pyerror_fetch_and_raise("PyObject_DelAttrString");
}

return Qnil;
}

static VALUE
pycall_libpython_helpers_m_callable_p(VALUE mod, VALUE pyptr)
{
Expand Down Expand Up @@ -2074,11 +2119,24 @@ pycall_pystring_from_formatv(char const *format, va_list vargs)

/* ==== Python ==== */

int
pycall_PyObject_DelAttrString(PyObject *pyobj, const char *attr_name)
{
/* PyObject_DelAttrString is defined by using PyObject_SetAttrString in CPython's abstract.h */
return Py_API(PyObject_SetAttrString)(pyobj, attr_name, NULL);
}

static void
init_python(void)
{
static char const *argv[1] = { "" };

/* optional functions */
if (! Py_API(PyObject_DelAttrString)) {
/* The case of PyObject_DelAttrString as a macro */
Py_API(PyObject_DelAttrString) = pycall_PyObject_DelAttrString;
}

Py_API(Py_InitializeEx)(0);
Py_API(PySys_SetArgvEx)(0, (char **)argv, 0);

Expand Down Expand Up @@ -2301,6 +2359,8 @@ Init_pycall(void)
rb_define_module_function(mHelpers, "compare", pycall_libpython_helpers_m_compare, 3);
rb_define_module_function(mHelpers, "getattr", pycall_libpython_helpers_m_getattr, -1);
rb_define_module_function(mHelpers, "hasattr?", pycall_libpython_helpers_m_hasattr_p, 2);
rb_define_module_function(mHelpers, "setattr", pycall_libpython_helpers_m_setattr, 3);
rb_define_module_function(mHelpers, "delattr", pycall_libpython_helpers_m_delattr, 2);
rb_define_module_function(mHelpers, "callable?", pycall_libpython_helpers_m_callable_p, 1);
rb_define_module_function(mHelpers, "call_object", pycall_libpython_helpers_m_call_object, -1);
rb_define_module_function(mHelpers, "getitem", pycall_libpython_helpers_m_getitem, 2);
Expand Down
1 change: 1 addition & 0 deletions ext/pycall/pycall_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ typedef struct {
PyObject * (* PyObject_GetAttrString)(PyObject *, char const *);
int (* PyObject_SetAttrString)(PyObject *, char const *, PyObject *);
int (* PyObject_HasAttrString)(PyObject *, char const *);
int (* PyObject_DelAttrString)(PyObject *, char const *);
PyObject * (* PyObject_GetItem)(PyObject *, PyObject *);
int (* PyObject_SetItem)(PyObject *obj, PyObject *key, PyObject *value);
int (* PyObject_DelItem)(PyObject *, PyObject *);
Expand Down
8 changes: 8 additions & 0 deletions lib/pycall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ def hasattr?(obj, name)
LibPython::Helpers.hasattr?(obj.__pyptr__, name)
end

def setattr(obj, name, val)
LibPython::Helpers.setattr(obj.__pyptr__, name, val)
end

def delattr(obj, name)
LibPython::Helpers.delattr(obj.__pyptr__, name)
end

def same?(left, right)
case left
when PyObjectWrapper
Expand Down
13 changes: 13 additions & 0 deletions test/python/pycall/simple_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class SimpleClass(object):
class NestedClass:
pass

def __init__(self, x=0):
self.x = x

def initialize(self, x):
self.x = x
return 'initialized'

class SimpleSubClass(SimpleClass):
pass
18 changes: 18 additions & 0 deletions test/test-pycall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ def test_VERSION
assert_not_nil(PyCall::VERSION)
end

def test_setattr
simple_class = PyCall.import_module("pycall.simple_class").SimpleClass
pyobj = simple_class.new(42)
before = pyobj.x
PyCall.setattr(pyobj, :x, 1)
assert_equal([42, 1],
[before, pyobj.x])
end

def test_delattr
simple_class = PyCall.import_module("pycall.simple_class").SimpleClass
pyobj = simple_class.new(42)
PyCall.delattr(pyobj, :x)
assert do
not PyCall.hasattr?(pyobj, :x)
end
end

def test_iterable
simple_iterable = PyCall.import_module("pycall.simple_iterable")
int_gen = simple_iterable.IntGenerator.new(10, 25)
Expand Down

0 comments on commit 69cf4dd

Please sign in to comment.