Skip to content

Commit 8e5fd14

Browse files
noahbkimwjakob
authored andcommitted
Add bytearray wrapper type (#654)
* Add bytearray wrapper type
1 parent 87f7fe2 commit 8e5fd14

9 files changed

+155
-3
lines changed

docs/api_core.rst

+37-1
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,7 @@ Wrapper classes
10281028

10291029
.. cpp:function:: bytes(handle h)
10301030

1031-
Performs a cast within Python. This is equivalent equivalent to
1031+
Performs a cast within Python. This is equivalent to
10321032
the Python expression ``bytes(h)``.
10331033

10341034
.. cpp:function:: bytes(const char * s)
@@ -1052,6 +1052,42 @@ Wrapper classes
10521052
Convert a Python ``bytes`` object into a byte buffer of length :cpp:func:`bytes::size()` bytes.
10531053

10541054

1055+
.. cpp:class:: bytearray: public object
1056+
1057+
This wrapper class represents Python ``bytearray`` instances.
1058+
1059+
.. cpp:function:: bytearray()
1060+
1061+
Create an empty ``bytearray``.
1062+
1063+
.. cpp:function:: bytearray(handle h)
1064+
1065+
Performs a cast within Python. This is equivalent to
1066+
the Python expression ``bytearray(h)``.
1067+
1068+
.. cpp:function:: bytearray(const void * buf, size_t n)
1069+
1070+
Convert a byte buffer ``buf`` of length ``n`` bytes into a Python ``bytearray`` object. The buffer can contain embedded null bytes.
1071+
1072+
.. cpp:function:: const char * c_str() const
1073+
1074+
Convert a Python ``bytearray`` object into a null-terminated C-style string.
1075+
1076+
.. cpp:function:: size_t size() const
1077+
1078+
Return the size in bytes.
1079+
1080+
.. cpp:function:: const void * data() const
1081+
1082+
Convert a Python ``bytearray`` object into a byte buffer of length :cpp:func:`bytearray::size()` bytes.
1083+
1084+
.. cpp:function:: void resize(size_t n)
1085+
1086+
Resize the internal buffer of a Python ``bytearray`` object to ``n``. Any
1087+
space added by this method, which calls `PyByteArray_Resize`, will not be
1088+
initialized and may contain random data.
1089+
1090+
10551091
.. cpp:class:: type_object: public object
10561092

10571093
Wrapper class representing Python ``type`` instances.

docs/changelog.rst

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@ case, both modules must use the same nanobind ABI version, or they will be
1515
isolated from each other. Releases that don't explicitly mention an ABI version
1616
below inherit that of the preceding release.
1717

18+
Version 2.1.1 (TBA)
19+
-------------------
20+
21+
* Added the :cpp:class:`bytearray` wrapper type. (PR `#654
22+
<https://github.com/wjakob/nanobind/pull/654>`__)
23+
24+
1825
Version 2.1.0 (Aug 11, 2024)
1926
----------------------------
2027

21-
* Temporary workaround for a internal compiler error in version 17.10 the MSVC
28+
* Temporary workaround for a internal compiler error in version 17.10 of the MSVC
2229
compiler. This workaround will be removed once fixed versions are deployed on
2330
GitHub actions. (issue `#613
2431
<https://github.com/wjakob/nanobind/issues/613>`__, commit `f2438b

docs/exchanging.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ multithreaded computations.
389389
The following wrappers are available and require no additional include
390390
directives:
391391
:cpp:class:`any`,
392-
:cpp:class:`bytes`, :cpp:class:`callable`, :cpp:class:`capsule`,
392+
:cpp:class:`bytearray`, :cpp:class:`bytes`, :cpp:class:`callable`, :cpp:class:`capsule`,
393393
:cpp:class:`dict`, :cpp:class:`ellipsis`, :cpp:class:`handle`,
394394
:cpp:class:`handle_t\<T\> <handle_t>`,
395395
:cpp:class:`bool_`, :cpp:class:`int_`, :cpp:class:`float_`,

include/nanobind/nb_lib.h

+8
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ NB_CORE PyObject *bytes_from_cstr_and_size(const void *c, size_t n);
134134

135135
// ========================================================================
136136

137+
/// Convert a Python object into a Python byte array
138+
NB_CORE PyObject *bytearray_from_obj(PyObject *o);
139+
140+
/// Convert a memory region into a Python byte array
141+
NB_CORE PyObject *bytearray_from_cstr_and_size(const void *c, size_t n);
142+
143+
// ========================================================================
144+
137145
/// Convert a Python object into a Python boolean object
138146
NB_CORE PyObject *bool_from_obj(PyObject *o);
139147

include/nanobind/nb_types.h

+29
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,35 @@ class bytes : public object {
443443
size_t size() const { return (size_t) PyBytes_Size(m_ptr); }
444444
};
445445

446+
class bytearray : public object {
447+
NB_OBJECT(bytearray, object, "bytearray", PyByteArray_Check)
448+
449+
#if PY_VERSION_HEX >= 0x03090000
450+
bytearray()
451+
: object(PyObject_CallNoArgs((PyObject *)&PyByteArray_Type), detail::steal_t{}) { }
452+
#else
453+
bytearray()
454+
: object(PyObject_CallObject((PyObject *)&PyByteArray_Type, NULL), detail::steal_t{}) { }
455+
#endif
456+
457+
explicit bytearray(handle h)
458+
: object(detail::bytearray_from_obj(h.ptr()), detail::steal_t{}) { }
459+
460+
explicit bytearray(const void *s, size_t n)
461+
: object(detail::bytearray_from_cstr_and_size(s, n), detail::steal_t{}) { }
462+
463+
const char *c_str() const { return PyByteArray_AsString(m_ptr); }
464+
465+
const void *data() const { return (const void *) PyByteArray_AsString(m_ptr); }
466+
467+
size_t size() const { return (size_t) PyByteArray_Size(m_ptr); }
468+
469+
void resize(size_t n) {
470+
if (PyByteArray_Resize(m_ptr, (Py_ssize_t) n) != 0)
471+
detail::raise_python_error();
472+
}
473+
};
474+
446475
class tuple : public object {
447476
NB_OBJECT(tuple, object, "tuple", PyTuple_Check)
448477
tuple() : object(PyTuple_New(0), detail::steal_t()) { }

src/common.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,23 @@ PyObject *bytes_from_cstr_and_size(const void *str, size_t size) {
579579
return result;
580580
}
581581

582+
// ========================================================================
583+
584+
PyObject *bytearray_from_obj(PyObject *o) {
585+
PyObject *result = PyByteArray_FromObject(o);
586+
if (!result)
587+
raise_python_error();
588+
return result;
589+
}
590+
591+
PyObject *bytearray_from_cstr_and_size(const void *str, size_t size) {
592+
PyObject *result = PyByteArray_FromStringAndSize((const char *) str, (Py_ssize_t) size);
593+
if (!result)
594+
raise_python_error();
595+
return result;
596+
}
597+
598+
582599
// ========================================================================
583600

584601
PyObject *bool_from_obj(PyObject *o) {

tests/test_functions.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,12 @@ NB_MODULE(test_functions_ext, m) {
361361
});
362362

363363
m.def("hash_it", [](nb::handle h) { return nb::hash(h); });
364+
365+
// Test bytearray type
366+
m.def("test_bytearray_new", []() { return nb::bytearray(); });
367+
m.def("test_bytearray_new", [](const char *c, int size) { return nb::bytearray(c, size); });
368+
m.def("test_bytearray_copy", [](nb::bytearray o) { return nb::bytearray(o.c_str(), o.size()); });
369+
m.def("test_bytearray_c_str", [](nb::bytearray o) -> const char * { return o.c_str(); });
370+
m.def("test_bytearray_size", [](nb::bytearray o) { return o.size(); });
371+
m.def("test_bytearray_resize", [](nb::bytearray c, int size) { return c.resize(size); });
364372
}

tests/test_functions.py

+33
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,36 @@ def test43_wrappers_set():
598598
def test44_hash():
599599
value = (1, 2, 3)
600600
assert t.hash_it(value) == hash(value);
601+
602+
603+
def test45_new():
604+
assert t.test_bytearray_new() == bytearray()
605+
assert t.test_bytearray_new("\x00\x01\x02\x03", 4) == bytearray(
606+
b"\x00\x01\x02\x03"
607+
)
608+
assert t.test_bytearray_new("", 0) == bytearray()
609+
610+
611+
def test46_copy():
612+
o = bytearray(b"\x00\x01\x02\x03")
613+
c = t.test_bytearray_copy(o)
614+
assert c == o
615+
o.clear()
616+
assert c != o
617+
618+
619+
def test47_c_str():
620+
o = bytearray(b"Hello, world!")
621+
assert t.test_bytearray_c_str(o) == "Hello, world!"
622+
623+
624+
def test48_size():
625+
o = bytearray(b"Hello, world!")
626+
assert t.test_bytearray_size(o) == len(o)
627+
628+
629+
def test49_resize():
630+
o = bytearray(b"\x00\x01\x02\x03")
631+
assert len(o) == 4
632+
t.test_bytearray_resize(o, 8)
633+
assert len(o) == 8

tests/test_functions_ext.pyi.ref

+14
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,20 @@ def test_args_kwonly_kwargs(i: int, j: float, *args, z: int, **kwargs) -> tuple:
140140

141141
def test_bad_tuple() -> tuple: ...
142142

143+
def test_bytearray_c_str(arg: bytearray, /) -> str: ...
144+
145+
def test_bytearray_copy(arg: bytearray, /) -> bytearray: ...
146+
147+
@overload
148+
def test_bytearray_new() -> bytearray: ...
149+
150+
@overload
151+
def test_bytearray_new(arg0: str, arg1: int, /) -> bytearray: ...
152+
153+
def test_bytearray_resize(arg0: bytearray, arg1: int, /) -> None: ...
154+
155+
def test_bytearray_size(arg: bytearray, /) -> int: ...
156+
143157
def test_call_1(arg: object, /) -> object: ...
144158

145159
def test_call_2(arg: object, /) -> object: ...

0 commit comments

Comments
 (0)