Also manage these objects when they rich the libp param passing.
alenghts: Optional[Array[c_int]]
if param_values:
nparams = len(param_values)
- aparams = (c_char_p * nparams)(*param_values)
+ aparams = (c_char_p * nparams)(
+ *(
+ # convert bytearray/memoryview to bytes
+ # TODO: avoid copy, at least in the C implementation.
+ b
+ if b is None or isinstance(b, bytes)
+ else bytes(b) # type: ignore[arg-type]
+ for b in param_values
+ )
+ )
alenghts = (c_int * nparams)(
*(len(p) if p else 0 for p in param_values)
)
return data.decode(self.encoding)
-@Dumper.text(bytes)
-class BytesDumper(Dumper):
-
+class _BinaryDumper(Dumper):
oid = builtins["bytea"].oid
def __init__(self, src: type, context: AdaptContext = None):
self.connection.pgconn if self.connection else None
)
+
+@Dumper.text(bytes)
+class BytesDumper(_BinaryDumper):
def dump(self, obj: bytes) -> bytes:
return self.esc.escape_bytea(obj)
+@Dumper.text(bytearray)
+class BytearrayDumper(_BinaryDumper):
+ def dump(self, obj: bytearray) -> bytes:
+ return self.esc.escape_bytea(bytes(obj))
+
+
+@Dumper.text(memoryview)
+class MemoryviewDumper(_BinaryDumper):
+ def dump(self, obj: memoryview) -> bytes:
+ return self.esc.escape_bytea(bytes(obj))
+
+
@Dumper.binary(bytes)
+@Dumper.binary(bytearray)
+@Dumper.binary(memoryview)
class BytesBinaryDumper(Dumper):
oid = builtins["bytea"].oid
- def dump(self, b: bytes) -> bytes:
- return b
+ def dump(
+ self, obj: Union[bytes, bytearray, memoryview]
+ ) -> Union[bytes, bytearray, memoryview]:
+ return obj
@Loader.text(builtins["bytea"].oid)
from posix.unistd cimport getpid
from cpython.mem cimport PyMem_Malloc, PyMem_Free
-from cpython.bytes cimport PyBytes_AsString
+from cpython.bytes cimport PyBytes_AsString, PyBytes_AsStringAndSize
+from cpython.buffer cimport PyObject_CheckBuffer, PyBUF_SIMPLE
+from cpython.buffer cimport PyObject_GetBuffer, PyBuffer_Release
import logging
from typing import List, Optional, Sequence, Tuple
cdef (int, Oid *, char * const*, int *, int *) _query_params_args(
- param_values: Optional[Sequence[Optional[bytes]]],
- param_types: Optional[Sequence[int]],
- param_formats: Optional[Sequence[Format]],
+ list param_values: Optional[Sequence[Optional[bytes]]],
+ list param_types: Optional[Sequence[int]],
+ list param_formats: Optional[Sequence[Format]],
) except *:
cdef int i
cdef char **aparams = NULL
cdef int *alenghts = NULL
+ cdef char *ptr
+ cdef Py_ssize_t length
+ cdef Py_buffer buf
+
if nparams:
aparams = <char **>PyMem_Malloc(nparams * sizeof(char *))
alenghts = <int *>PyMem_Malloc(nparams * sizeof(int))
for i in range(nparams):
- if param_values[i] is not None:
- aparams[i] = param_values[i]
- alenghts[i] = len(param_values[i])
- else:
+ obj = param_values[i]
+ if obj is None:
aparams[i] = NULL
alenghts[i] = 0
+ elif isinstance(obj, bytes):
+ PyBytes_AsStringAndSize(obj, &ptr, &length)
+ aparams[i] = ptr
+ alenghts[i] = length
+ elif PyObject_CheckBuffer(obj):
+ PyObject_GetBuffer(obj, &buf, PyBUF_SIMPLE)
+ aparams[i] = <char *>buf.buf
+ alenghts[i] = buf.len
+ PyBuffer_Release(&buf)
+ else:
+ raise TypeError(f"bytes or buffer expected, got {type(obj)}")
cdef Oid *atypes = NULL
if param_types is not None:
@pytest.mark.parametrize("fmt_in", [Format.TEXT, Format.BINARY])
-def test_dump_1byte(conn, fmt_in):
+@pytest.mark.parametrize("pytype", [bytes, bytearray, memoryview])
+def test_dump_1byte(conn, fmt_in, pytype):
cur = conn.cursor()
ph = "%s" if fmt_in == Format.TEXT else "%b"
for i in range(0, 256):
- cur.execute(f"select {ph} = %s::bytea", (bytes([i]), fr"\x{i:02x}"))
+ obj = pytype(bytes([i]))
+ cur.execute(f"select {ph} = %s::bytea", (obj, fr"\x{i:02x}"))
assert cur.fetchone()[0] is True, i