Skip to content
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

Add pybind11::bytearray #2799

Merged
merged 15 commits into from
Feb 14, 2021
29 changes: 29 additions & 0 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,35 @@ inline str::str(const bytes& b) {
m_ptr = obj.release().ptr();
}

/// \addtogroup pytypes
/// @{
class bytearray : public object {
public:
PYBIND11_OBJECT(bytearray, object, PyByteArray_Check)
YannickJadoul marked this conversation as resolved.
Show resolved Hide resolved

bytearray()
YannickJadoul marked this conversation as resolved.
Show resolved Hide resolved
: object(PyByteArray_FromStringAndSize("", (ssize_t) 0), stolen_t{}) {
if (!m_ptr) pybind11_fail("Could not allocate bytearray object!");
}

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!");
}

// Allow implicit conversion:
bytearray(const std::string &s) : bytearray(s.data(), s.size()) { }
YannickJadoul marked this conversation as resolved.
Show resolved Hide resolved

operator std::string() const {
YannickJadoul marked this conversation as resolved.
Show resolved Hide resolved
char *buffer = PyByteArray_AS_STRING(m_ptr);
YannickJadoul marked this conversation as resolved.
Show resolved Hide resolved
YannickJadoul marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -90,6 +90,7 @@ TEST_SUBMODULE(pytypes, m) {

// test_bytes
m.def("bytes_from_string", []() { return py::bytes(std::string("foo")); });
m.def("bytearray_from_string", []() { return py::bytearray(std::string("foo")); });
m.def("bytes_from_str", []() { return py::bytes(py::str("bar", 3)); });

// test_capsule
Expand Down Expand Up @@ -210,6 +211,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 +226,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 +243,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 All @@ -258,6 +262,9 @@ TEST_SUBMODULE(pytypes, m) {
if (type == "bytes") {
return py::bytes(value);
}
if (type == "bytearray") {
return py::bytearray(value);
}
else if (type == "none") {
return py::none(value);
}
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,10 @@ def test_bytes(doc):
)


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


def test_capsule(capture):
pytest.gc_collect()
with capture:
Expand Down Expand Up @@ -220,7 +224,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 +235,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 Expand Up @@ -266,6 +271,7 @@ def test_constructors():
def test_non_converting_constructors():
non_converting_test_cases = [
("bytes", range(10)),
("bytearray", range(10)),
("none", 42),
("ellipsis", 42),
]
Expand Down