Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions newsfragments/5870.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `obj()` getter to `PyUntypedBuffer` to retrieve the Python object owning the buffer.
41 changes: 41 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,24 @@ impl PyUntypedBuffer {
self.raw().buf
}

///Returns the Python object that owns the buffer data.
///
///This is the object that was passed to [`PyBuffer::get()`] when the buffer was created.
///Returns `None` if the buffer was created without owning object.
///
///This is the object passed to [`PyUntypedBuffer::get()`]
///Calling this before [`release()`][Self::release] allows you to clone an owned reference and
///keeps the object alive after the buffer is released.
pub fn obj<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyAny>> {
let ptr = self.raw().obj;
// SAFETY: Py_buffer.obj is a borrowed reference to a Python object, so it is always valid to create a Bound from it
if ptr.is_null() {
None
} else {
Some(unsafe { Bound::from_borrowed_ptr(py, ptr) })
}
Comment on lines +582 to +588
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use Bound::from_borrowed_ptr_or_opt here to simplify the null checking.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Thanks!

}

/// Gets a pointer to the specified item.
///
/// If `indices.len() < self.dimensions()`, returns the start address of the sub-array at the specified dimension.
Expand Down Expand Up @@ -1044,4 +1062,27 @@ mod tests {
assert_eq!(typed.shape(), [5]);
});
}

#[test]
fn test_obj_getter() {
Python::attach(|py| {
let bytes = PyBytes::new(py, b"hello");
let buf = PyUntypedBuffer::get(bytes.as_any()).unwrap();

// obj() returns the same object that owns the buffer
let owner = buf.obj(py).unwrap();
assert!(owner.is_instance_of::<PyBytes>());
assert!(owner.is(&bytes));

// can keep the owner alive after releasing the buffer
let owner_ref: crate::Py<PyAny> = owner.unbind();
buf.release(py);
drop(bytes);
// owner_ref still valid after buffer and original are dropped
Python::attach(|py| {
let rebound = owner_ref.bind(py);
assert!(rebound.is_instance_of::<PyBytes>());
});
});
}
}
Loading