Yes, I think that’s better for starting with the Python behaviour.
It is challenging to follow what exactly is happening with wrapping in a memoryview. However, these specifications always need careful reading, and one’s interpretation to be checked with the reference implementation.
What I expect to see in a hypothetical reference slot_bf_buffer, for example, is code that begins like vectorcall_method(&_Py_ID(__buffer__), ...) and discards the memoryview wrapper it gets back. And in slot_bf_buffer_release I expect code that synthesises a new memoryview wrapper around the passed Py_buffer.
This doesn’t seem quite what is implied by:
When this method is invoked through the buffer API (for example, through memoryview.release), the passed memoryview is the same object as was returned by __buffer__.
The signatures of slot_bf_buffer and slot_bf_buffer_release are:
typedef int (*getbufferproc)(PyObject *, Py_buffer *, int);
typedef void (*releasebufferproc)(PyObject *, Py_buffer *);
so there is no opportunity to guarantee as you have in MyBuffer.__realease_buffer__ that:
def __release_buffer__(self, view: memoryview) -> None:
assert self.view is view # guaranteed to be true
...
If I use the Python API, calling __buffer__and __release_buffer__ myself, then I have to pass exactly the memoryview I received, since I have no other way to be sure I an returning the same Py_buffer. But when memoryview.release does this to a buffer object defined in Python, it will call the slot containing slot_bf_buffer_release and the buffer object in Python will receive the new ephemeral memoryview.