Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add support for __getattr__ #234

Merged
merged 3 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'"