Skip to content

Commit 79777de

Browse files
authored
Feature: Add support for __getattr__ (#234)
1 parent e6842e5 commit 79777de

File tree

6 files changed

+54
-11
lines changed

6 files changed

+54
-11
lines changed

docs/guide/classes.md

+12-11
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,18 @@ const inquiry = fn(*Self) !bool;
173173

174174
### Type Methods
175175

176-
| Method | Signature |
177-
| :--------- | :--------------------------------------- |
178-
| `__init__` | `#!zig fn() void` |
179-
| `__init__` | `#!zig fn(*Self) !void` |
180-
| `__init__` | `#!zig fn(*Self, CallArgs) !void` |
181-
| `__del__` | `#!zig fn(*Self) void` |
182-
| `__repr__` | `#!zig fn(*Self) !py.PyString` |
183-
| `__str__` | `#!zig fn(*Self) !py.PyString` |
184-
| `__call__` | `#!zig fn(*Self, CallArgs) !py.PyObject` |
185-
| `__iter__` | `#!zig fn(*Self) !object` |
186-
| `__next__` | `#!zig fn(*Self) !?object` |
176+
| Method | Signature |
177+
| :------------ | :--------------------------------------- |
178+
| `__init__` | `#!zig fn() void` |
179+
| `__init__` | `#!zig fn(*Self) !void` |
180+
| `__init__` | `#!zig fn(*Self, CallArgs) !void` |
181+
| `__del__` | `#!zig fn(*Self) void` |
182+
| `__repr__` | `#!zig fn(*Self) !py.PyString` |
183+
| `__str__` | `#!zig fn(*Self) !py.PyString` |
184+
| `__call__` | `#!zig fn(*Self, CallArgs) !py.PyObject` |
185+
| `__iter__` | `#!zig fn(*Self) !object` |
186+
| `__next__` | `#!zig fn(*Self) !?object` |
187+
| `__getattr__` | `#!zig fn(*Self, object) !?object` |
187188

188189
### Sequence Methods
189190

example/classes.pyi

+9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ class Counter:
2323

2424
count: ...
2525

26+
class GetAttr:
27+
def __init__(self, /):
28+
pass
29+
def __getattribute__(self, name, /):
30+
"""
31+
Return getattr(self, name).
32+
"""
33+
...
34+
2635
class Hash:
2736
def __init__(self, x, /):
2837
pass

example/classes.zig

+16
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,22 @@ pub const Callable = py.class(struct {
172172
}
173173
});
174174

175+
pub const GetAttr = py.class(struct {
176+
const Self = @This();
177+
178+
pub fn __init__(self: *Self) void {
179+
_ = self;
180+
}
181+
182+
pub fn __getattr__(self: *const Self, attr: py.PyString) !py.PyObject {
183+
const name = try attr.asSlice();
184+
if (std.mem.eql(u8, name, "number")) {
185+
return py.create(42);
186+
}
187+
return py.object(self).getAttribute(name);
188+
}
189+
});
190+
175191
comptime {
176192
py.rootmodule(@This());
177193
}

pydust/src/functions.zig

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub const BinaryOperators = std.ComptimeStringMap(c_int, .{
8181
.{ "__matmul__", ffi.Py_nb_matrix_multiply },
8282
.{ "__imatmul__", ffi.Py_nb_inplace_matrix_multiply },
8383
.{ "__getitem__", ffi.Py_mp_subscript },
84+
.{ "__getattr__", ffi.Py_tp_getattro },
8485
});
8586

8687
// TODO(marko): Move this somewhere.

pydust/src/types/obj.zig

+8
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ pub const PyObject = extern struct {
5555
return .{ .py = ffi.PyObject_GetAttr(self.py, attrStr.obj.py) orelse return PyError.PyRaised };
5656
}
5757

58+
/// Returns a new reference to the attribute of the object using default lookup semantics.
59+
pub fn getAttribute(self: PyObject, attrName: []const u8) !py.PyObject {
60+
const attrStr = try py.PyString.create(attrName);
61+
defer attrStr.decref();
62+
63+
return .{ .py = ffi.PyObject_GenericGetAttr(self.py, attrStr.obj.py) orelse return PyError.PyRaised };
64+
}
65+
5866
/// Returns a new reference to the attribute of the object.
5967
pub fn getAs(self: PyObject, comptime T: type, attrName: []const u8) !T {
6068
return try py.as(T, try self.get(attrName));

test/test_classes.py

+8
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,11 @@ def test_refcnt():
9898
rc = sys.getrefcount(classes)
9999
classes.Hash(42)
100100
assert sys.getrefcount(classes) == rc
101+
102+
103+
def test_getattr():
104+
c = classes.GetAttr()
105+
assert c.number == 42
106+
with pytest.raises(AttributeError) as exc_info:
107+
c.attr
108+
assert str(exc_info.value) == "'example.classes.GetAttr' object has no attribute 'attr'"

0 commit comments

Comments
 (0)