Add pybind11::bytearray (#2799)

* Add initial implementation

* Add few more methods

* Add tests

* Fix a typo

* Use std::string constructor which takes size

* Fix implicit sign conversion error

* Add size method and test

* Remove implicit conversion

* Fix bytearray constructors and operator std::string()

* Make implicit bytearray constructor explicit

* Rerun tests

* Add null check

* Rerun tests

* Rerun tests - 2

* Remove NULL check
This commit is contained in:
Vikram Pal 2021-02-14 20:21:13 +05:30 committed by GitHub
parent cbae6d55c2
commit 417067eeb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 1 deletions

View File

@ -1070,6 +1070,34 @@ inline str::str(const bytes& b) {
m_ptr = obj.release().ptr(); 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 /// \addtogroup pytypes
/// @{ /// @{
class none : public object { class none : public object {

View File

@ -92,6 +92,10 @@ TEST_SUBMODULE(pytypes, m) {
m.def("bytes_from_string", []() { return py::bytes(std::string("foo")); }); m.def("bytes_from_string", []() { return py::bytes(std::string("foo")); });
m.def("bytes_from_str", []() { return py::bytes(py::str("bar", 3)); }); 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 // test_capsule
m.def("return_capsule_with_destructor", []() { m.def("return_capsule_with_destructor", []() {
py::print("creating capsule"); py::print("creating capsule");
@ -210,6 +214,7 @@ TEST_SUBMODULE(pytypes, m) {
m.def("default_constructors", []() { m.def("default_constructors", []() {
return py::dict( return py::dict(
"bytes"_a=py::bytes(), "bytes"_a=py::bytes(),
"bytearray"_a=py::bytearray(),
"str"_a=py::str(), "str"_a=py::str(),
"bool"_a=py::bool_(), "bool"_a=py::bool_(),
"int"_a=py::int_(), "int"_a=py::int_(),
@ -224,6 +229,7 @@ TEST_SUBMODULE(pytypes, m) {
m.def("converting_constructors", [](py::dict d) { m.def("converting_constructors", [](py::dict d) {
return py::dict( return py::dict(
"bytes"_a=py::bytes(d["bytes"]), "bytes"_a=py::bytes(d["bytes"]),
"bytearray"_a=py::bytearray(d["bytearray"]),
"str"_a=py::str(d["str"]), "str"_a=py::str(d["str"]),
"bool"_a=py::bool_(d["bool"]), "bool"_a=py::bool_(d["bool"]),
"int"_a=py::int_(d["int"]), "int"_a=py::int_(d["int"]),
@ -240,6 +246,7 @@ TEST_SUBMODULE(pytypes, m) {
// When converting between Python types, obj.cast<T>() should be the same as T(obj) // When converting between Python types, obj.cast<T>() should be the same as T(obj)
return py::dict( return py::dict(
"bytes"_a=d["bytes"].cast<py::bytes>(), "bytes"_a=d["bytes"].cast<py::bytes>(),
"bytearray"_a=d["bytearray"].cast<py::bytearray>(),
"str"_a=d["str"].cast<py::str>(), "str"_a=d["str"].cast<py::str>(),
"bool"_a=d["bool"].cast<py::bool_>(), "bool"_a=d["bool"].cast<py::bool_>(),
"int"_a=d["int"].cast<py::int_>(), "int"_a=d["int"].cast<py::int_>(),

View File

@ -143,6 +143,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): def test_capsule(capture):
pytest.gc_collect() pytest.gc_collect()
with capture: with capture:
@ -223,7 +228,7 @@ def test_accessors():
def test_constructors(): def test_constructors():
"""C++ default and converting constructors are equivalent to type calls in Python""" """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} expected = {t.__name__: t() for t in types}
if env.PY2: if env.PY2:
# Note that bytes.__name__ == 'str' in Python 2. # Note that bytes.__name__ == 'str' in Python 2.
@ -234,6 +239,7 @@ def test_constructors():
data = { data = {
bytes: b"41", # Currently no supported or working conversions. bytes: b"41", # Currently no supported or working conversions.
bytearray: bytearray(b"41"),
str: 42, str: 42,
bool: "Not empty", bool: "Not empty",
int: "42", int: "42",