-
Notifications
You must be signed in to change notification settings - Fork 951
Added obj() getter method to PyUntypedBuffer #5870
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -570,6 +570,18 @@ impl PyUntypedBuffer { | |||||||||||||||||||||||||||||||||||||||||||||
| self.raw().buf | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ///Returns the Python object that owns the buffer data. | ||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||
| ///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>) -> Bound<'py, PyAny> { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Safety: `PyObject_GetBuffer` increments the reference count of `obj` automatically | ||||||||||||||||||||||||||||||||||||||||||||||
| // and `PyBuffer_Release` decrements it on drop. The `obj` is guaranteed to be valid | ||||||||||||||||||||||||||||||||||||||||||||||
| // and non-null for the entire lifetime of `self`. | ||||||||||||||||||||||||||||||||||||||||||||||
| unsafe { Bound::from_borrowed_ptr(py, self.raw().obj) } | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
| pub fn obj<'py>(&self, py: Python<'py>) -> Bound<'py, PyAny> { | |
| // Safety: `PyObject_GetBuffer` increments the reference count of `obj` automatically | |
| // and `PyBuffer_Release` decrements it on drop. The `obj` is guaranteed to be valid | |
| // and non-null for the entire lifetime of `self`. | |
| unsafe { Bound::from_borrowed_ptr(py, self.raw().obj) } | |
| pub fn obj<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> { | |
| let obj_ptr = self.raw().obj; | |
| if obj_ptr.is_null() { | |
| // A misbehaving exporter returned a null obj pointer, which would be UB to use. | |
| return Err(PyBufferError::new_err( | |
| "buffer exporter returned a null obj pointer in Py_buffer", | |
| )); | |
| } | |
| // Safety: `PyObject_GetBuffer` increments the reference count of `obj` automatically | |
| // and `PyBuffer_Release` decrements it on drop. We additionally checked that `obj_ptr` | |
| // is non-null before converting it to a `Bound`. | |
| unsafe { Ok(Bound::from_borrowed_ptr(py, obj_ptr)) } |
Outdated
Copilot
AI
Mar 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new test constructs the bytes object via py.eval(...). For consistency with the other buffer tests in this module (which use PyBytes::new) and to avoid relying on Python code execution/parsing in a unit test, consider creating the bytes object directly with PyBytes::new(py, b"hello").
| let bytes = py.eval(ffi::c_str!("b'hello'"), None, None).unwrap(); | |
| let bytes = PyBytes::new(py, b"hello"); |
Outdated
Copilot
AI
Mar 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_obj_getter only checks that obj() returns the same object, but it doesn’t cover the key behavior described in the docs/PR rationale: that you can keep the owning object alive after releasing the buffer (e.g., by cloning/unbinding the result of obj(), releasing/dropping the buffer, dropping the original bytes, and asserting the cloned reference still resolves). Adding an assertion around release() would protect against regressions in refcount handling.
| let buf = PyUntypedBuffer::get(&bytes).unwrap(); | |
| let owner = buf.obj(py); | |
| assert!(owner.is_instance_of::<crate::types::PyBytes>()); | |
| //owner and bytes should point to the same object | |
| assert!(owner.is(&bytes)); | |
| let mut buf = PyUntypedBuffer::get(&bytes).unwrap(); | |
| let owner = buf.obj(py); | |
| assert!(owner.is_instance_of::<crate::types::PyBytes>()); | |
| //owner and bytes should point to the same object | |
| assert!(owner.is(&bytes)); | |
| // Clone/unbind the owner so it can outlive the buffer and original bytes | |
| let owner_handle = owner.unbind(); | |
| // Explicitly release the buffer and drop it, then drop the original bytes | |
| buf.release(py); | |
| drop(buf); | |
| drop(bytes); | |
| // The unbound handle should still keep the owning object alive and usable | |
| let bound_owner = owner_handle.bind(py); | |
| assert!(bound_owner.is_instance_of::<crate::types::PyBytes>()); |
Uh oh!
There was an error while loading. Please reload this page.