Skip to content

Commit 4fa4520

Browse files
committed
support for readonly buffers (#863)
1 parent f7bc18f commit 4fa4520

File tree

5 files changed

+77
-12
lines changed

5 files changed

+77
-12
lines changed

include/pybind11/buffer_info.h

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,38 @@ struct buffer_info {
2222
ssize_t ndim = 0; // Number of dimensions
2323
std::vector<ssize_t> shape; // Shape of the tensor (1 entry per dimension)
2424
std::vector<ssize_t> strides; // Number of entries between adjacent entries (for each per dimension)
25+
bool readonly = false; // flag to indicate if the underlying storage may be written to
2526

2627
buffer_info() { }
2728

2829
buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim,
29-
detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in)
30+
detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly=false)
3031
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
31-
shape(std::move(shape_in)), strides(std::move(strides_in)) {
32+
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
3233
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size())
3334
pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
3435
for (size_t i = 0; i < (size_t) ndim; ++i)
3536
size *= shape[i];
3637
}
3738

3839
template <typename T>
39-
buffer_info(T *ptr, detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in)
40-
: buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor<T>::format(), static_cast<ssize_t>(shape_in->size()), std::move(shape_in), std::move(strides_in)) { }
40+
buffer_info(T *ptr, detail::any_container<ssize_t> shape_in, detail::any_container<ssize_t> strides_in, bool readonly=false)
41+
: buffer_info(private_ctr_tag(), ptr, sizeof(T), format_descriptor<T>::format(), static_cast<ssize_t>(shape_in->size()), std::move(shape_in), std::move(strides_in), readonly) { }
4142

42-
buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size)
43-
: buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}) { }
43+
buffer_info(void *ptr, ssize_t itemsize, const std::string &format, ssize_t size, bool readonly=false)
44+
: buffer_info(ptr, itemsize, format, 1, {size}, {itemsize}, readonly) { }
4445

4546
template <typename T>
46-
buffer_info(T *ptr, ssize_t size)
47-
: buffer_info(ptr, sizeof(T), format_descriptor<T>::format(), size) { }
47+
buffer_info(T *ptr, ssize_t size, bool readonly=false)
48+
: buffer_info(ptr, sizeof(T), format_descriptor<T>::format(), size, readonly) { }
49+
50+
template <typename T>
51+
buffer_info(const T *ptr, ssize_t size, bool readonly=true)
52+
: buffer_info(const_cast<T*>(ptr), sizeof(T), format_descriptor<T>::format(), size, readonly) { }
4853

4954
explicit buffer_info(Py_buffer *view, bool ownview = true)
5055
: buffer_info(view->buf, view->itemsize, view->format, view->ndim,
51-
{view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}) {
56+
{view->shape, view->shape + view->ndim}, {view->strides, view->strides + view->ndim}, view->readonly) {
5257
this->view = view;
5358
this->ownview = ownview;
5459
}
@@ -70,6 +75,7 @@ struct buffer_info {
7075
strides = std::move(rhs.strides);
7176
std::swap(view, rhs.view);
7277
std::swap(ownview, rhs.ownview);
78+
readonly = rhs.readonly;
7379
return *this;
7480
}
7581

@@ -81,8 +87,8 @@ struct buffer_info {
8187
struct private_ctr_tag { };
8288

8389
buffer_info(private_ctr_tag, void *ptr, ssize_t itemsize, const std::string &format, ssize_t ndim,
84-
detail::any_container<ssize_t> &&shape_in, detail::any_container<ssize_t> &&strides_in)
85-
: buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in)) { }
90+
detail::any_container<ssize_t> &&shape_in, detail::any_container<ssize_t> &&strides_in, bool readonly)
91+
: buffer_info(ptr, itemsize, format, ndim, std::move(shape_in), std::move(strides_in), readonly) { }
8692

8793
Py_buffer *view = nullptr;
8894
bool ownview = false;

include/pybind11/detail/class.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,13 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
484484
view->len = view->itemsize;
485485
for (auto s : info->shape)
486486
view->len *= s;
487+
view->readonly = info->readonly;
488+
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
489+
if(view)
490+
view->obj = nullptr;
491+
PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage");
492+
return -1;
493+
}
487494
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
488495
view->format = const_cast<char *>(info->format.c_str());
489496
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {

tests/constructor_stats.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class ConstructorStats {
180180
}
181181
}
182182
}
183-
catch (std::out_of_range) {}
183+
catch (const std::out_of_range&) {}
184184
if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
185185
auto &cs1 = get(*t1);
186186
// If we have both a t1 and t2 match, one is probably the trampoline class; return whichever

tests/test_buffers.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,30 @@ TEST_SUBMODULE(buffers, m) {
166166
.def_readwrite("value", (int32_t DerivedBuffer::*) &DerivedBuffer::value)
167167
.def_buffer(&DerivedBuffer::get_buffer_info);
168168

169+
struct BufferReadOnly {
170+
const uint8_t value = 0;
171+
BufferReadOnly(uint8_t value): value(value) {}
172+
173+
py::buffer_info get_buffer_info() {
174+
return py::buffer_info(&value, 1);
175+
}
176+
};
177+
py::class_<BufferReadOnly>(m, "BufferReadOnly", py::buffer_protocol())
178+
.def(py::init<uint8_t>())
179+
.def_buffer(&BufferReadOnly::get_buffer_info);
180+
181+
struct BufferReadOnlySelect {
182+
uint8_t value = 0;
183+
bool readonly = false;
184+
185+
py::buffer_info get_buffer_info() {
186+
return py::buffer_info(&value, 1, readonly);
187+
}
188+
};
189+
py::class_<BufferReadOnlySelect>(m, "BufferReadOnlySelect", py::buffer_protocol())
190+
.def(py::init<>())
191+
.def_readwrite("value", &BufferReadOnlySelect::value)
192+
.def_readwrite("readonly", &BufferReadOnlySelect::readonly)
193+
.def_buffer(&BufferReadOnlySelect::get_buffer_info);
194+
169195
}

tests/test_buffers.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import io
12
import struct
23
import pytest
34
from pybind11_tests import buffers as m
@@ -85,3 +86,28 @@ def test_pointer_to_member_fn():
8586
buf.value = 0x12345678
8687
value = struct.unpack('i', bytearray(buf))[0]
8788
assert value == 0x12345678
89+
90+
91+
@pytest.unsupported_on_pypy
92+
def test_readonly_buffer():
93+
buf = m.BufferReadOnly(0x64)
94+
view = memoryview(buf)
95+
assert view[0] == 0x64
96+
assert view.readonly
97+
98+
99+
@pytest.unsupported_on_pypy
100+
def test_selective_readonly_buffer():
101+
buf = m.BufferReadOnlySelect()
102+
103+
memoryview(buf)[0] = 0x64
104+
assert buf.value == 0x64
105+
106+
io.BytesIO(b'A').readinto(buf)
107+
assert buf.value == ord(b'A')
108+
109+
buf.readonly = True
110+
with pytest.raises(TypeError):
111+
memoryview(buf)[0] = 0
112+
with pytest.raises(TypeError):
113+
io.BytesIO(b'1').readinto(buf)

0 commit comments

Comments
 (0)