Skip to content
28 changes: 28 additions & 0 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,34 @@ inline str::str(const bytes& b) {
m_ptr = obj.release().ptr();
}

/// \addtogroup pytypes
/// @{
class bytearray : public object {
public:
PYBIND11_OBJECT_CVT(bytearray, object, PyByteArray_Check, PyByteArray_FromObject)

bytearray(const char *c, size_t n)
: object(PyByteArray_FromStringAndSize(c, (ssize_t) n), stolen_t{}) {
if (!m_ptr) pybind11_fail("Could not allocate bytearray object!");
}

bytearray()
: bytearray("", 0) {}

explicit bytearray(const std::string &s) : bytearray(s.data(), s.size()) { }

size_t size() const { return static_cast<size_t>(PyByteArray_Size(m_ptr)); }

explicit operator std::string() const {
char *buffer = PyByteArray_AS_STRING(m_ptr);
ssize_t size = PyByteArray_GET_SIZE(m_ptr);
return std::string(buffer, static_cast<size_t>(size));
}
};
// Note: breathe >= 4.17.0 will fail to build docs if the below two constructors
// are included in the doxygen group; close here and reopen after as a workaround
/// @} pytypes

/// \addtogroup pytypes
/// @{
class none : public object {
Expand Down
7 changes: 7 additions & 0 deletions tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ TEST_SUBMODULE(pytypes, m) {
m.def("bytes_from_string", []() { return py::bytes(std::string("foo")); });
m.def("bytes_from_str", []() { return py::bytes(py::str("bar", 3)); });

// test bytearray
m.def("bytearray_from_string", []() { return py::bytearray(std::string("foo")); });
m.def("bytearray_size", []() { return py::bytearray("foo").size(); });

// test_capsule
m.def("return_capsule_with_destructor", []() {
py::print("creating capsule");
Expand Down Expand Up @@ -210,6 +214,7 @@ TEST_SUBMODULE(pytypes, m) {
m.def("default_constructors", []() {
return py::dict(
"bytes"_a=py::bytes(),
"bytearray"_a=py::bytearray(),
"str"_a=py::str(),
"bool"_a=py::bool_(),
"int"_a=py::int_(),
Expand All @@ -224,6 +229,7 @@ TEST_SUBMODULE(pytypes, m) {
m.def("converting_constructors", [](py::dict d) {
return py::dict(
"bytes"_a=py::bytes(d["bytes"]),
"bytearray"_a=py::bytearray(d["bytearray"]),
"str"_a=py::str(d["str"]),
"bool"_a=py::bool_(d["bool"]),
"int"_a=py::int_(d["int"]),
Expand All @@ -240,6 +246,7 @@ TEST_SUBMODULE(pytypes, m) {
// When converting between Python types, obj.cast<T>() should be the same as T(obj)
return py::dict(
"bytes"_a=d["bytes"].cast<py::bytes>(),
"bytearray"_a=d["bytearray"].cast<py::bytearray>(),
"str"_a=d["str"].cast<py::str>(),
"bool"_a=d["bool"].cast<py::bool_>(),
"int"_a=d["int"].cast<py::int_>(),
Expand Down
8 changes: 7 additions & 1 deletion tests/test_pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ def test_bytes(doc):
)


def test_bytearray(doc):
assert m.bytearray_from_string().decode() == "foo"
assert m.bytearray_size() == len("foo")


def test_capsule(capture):
pytest.gc_collect()
with capture:
Expand Down Expand Up @@ -220,7 +225,7 @@ def func(self, x, *args):

def test_constructors():
"""C++ default and converting constructors are equivalent to type calls in Python"""
types = [bytes, str, bool, int, float, tuple, list, dict, set]
types = [bytes, bytearray, str, bool, int, float, tuple, list, dict, set]
expected = {t.__name__: t() for t in types}
if env.PY2:
# Note that bytes.__name__ == 'str' in Python 2.
Expand All @@ -231,6 +236,7 @@ def test_constructors():

data = {
bytes: b"41", # Currently no supported or working conversions.
bytearray: bytearray(b"41"),
str: 42,
bool: "Not empty",
int: "42",
Expand Down