Skip to content

Commit

Permalink
Feature: Add support for __getattr__ (#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
robert3005 committed Oct 31, 2023
1 parent e6842e5 commit 79777de
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 11 deletions.
23 changes: 12 additions & 11 deletions docs/guide/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,18 @@ const inquiry = fn(*Self) !bool;

### Type Methods

| Method | Signature |
| :--------- | :--------------------------------------- |
| `__init__` | `#!zig fn() void` |
| `__init__` | `#!zig fn(*Self) !void` |
| `__init__` | `#!zig fn(*Self, CallArgs) !void` |
| `__del__` | `#!zig fn(*Self) void` |
| `__repr__` | `#!zig fn(*Self) !py.PyString` |
| `__str__` | `#!zig fn(*Self) !py.PyString` |
| `__call__` | `#!zig fn(*Self, CallArgs) !py.PyObject` |
| `__iter__` | `#!zig fn(*Self) !object` |
| `__next__` | `#!zig fn(*Self) !?object` |
| Method | Signature |
| :------------ | :--------------------------------------- |
| `__init__` | `#!zig fn() void` |
| `__init__` | `#!zig fn(*Self) !void` |
| `__init__` | `#!zig fn(*Self, CallArgs) !void` |
| `__del__` | `#!zig fn(*Self) void` |
| `__repr__` | `#!zig fn(*Self) !py.PyString` |
| `__str__` | `#!zig fn(*Self) !py.PyString` |
| `__call__` | `#!zig fn(*Self, CallArgs) !py.PyObject` |
| `__iter__` | `#!zig fn(*Self) !object` |
| `__next__` | `#!zig fn(*Self) !?object` |
| `__getattr__` | `#!zig fn(*Self, object) !?object` |

### Sequence Methods

Expand Down
9 changes: 9 additions & 0 deletions example/classes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ class Counter:

count: ...

class GetAttr:
def __init__(self, /):
pass
def __getattribute__(self, name, /):
"""
Return getattr(self, name).
"""
...

class Hash:
def __init__(self, x, /):
pass
Expand Down
16 changes: 16 additions & 0 deletions example/classes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,22 @@ pub const Callable = py.class(struct {
}
});

pub const GetAttr = py.class(struct {
const Self = @This();

pub fn __init__(self: *Self) void {
_ = self;
}

pub fn __getattr__(self: *const Self, attr: py.PyString) !py.PyObject {
const name = try attr.asSlice();
if (std.mem.eql(u8, name, "number")) {
return py.create(42);
}
return py.object(self).getAttribute(name);
}
});

comptime {
py.rootmodule(@This());
}
1 change: 1 addition & 0 deletions pydust/src/functions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub const BinaryOperators = std.ComptimeStringMap(c_int, .{
.{ "__matmul__", ffi.Py_nb_matrix_multiply },
.{ "__imatmul__", ffi.Py_nb_inplace_matrix_multiply },
.{ "__getitem__", ffi.Py_mp_subscript },
.{ "__getattr__", ffi.Py_tp_getattro },
});

// TODO(marko): Move this somewhere.
Expand Down
8 changes: 8 additions & 0 deletions pydust/src/types/obj.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ pub const PyObject = extern struct {
return .{ .py = ffi.PyObject_GetAttr(self.py, attrStr.obj.py) orelse return PyError.PyRaised };
}

/// Returns a new reference to the attribute of the object using default lookup semantics.
pub fn getAttribute(self: PyObject, attrName: []const u8) !py.PyObject {
const attrStr = try py.PyString.create(attrName);
defer attrStr.decref();

return .{ .py = ffi.PyObject_GenericGetAttr(self.py, attrStr.obj.py) orelse return PyError.PyRaised };
}

/// Returns a new reference to the attribute of the object.
pub fn getAs(self: PyObject, comptime T: type, attrName: []const u8) !T {
return try py.as(T, try self.get(attrName));
Expand Down
8 changes: 8 additions & 0 deletions test/test_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,11 @@ def test_refcnt():
rc = sys.getrefcount(classes)
classes.Hash(42)
assert sys.getrefcount(classes) == rc


def test_getattr():
c = classes.GetAttr()
assert c.number == 42
with pytest.raises(AttributeError) as exc_info:
c.attr
assert str(exc_info.value) == "'example.classes.GetAttr' object has no attribute 'attr'"

0 comments on commit 79777de

Please sign in to comment.