From: Daniele Varrazzo Date: Thu, 3 Nov 2022 23:53:20 +0000 (+0100) Subject: fix: define Escaping method as returning bytes X-Git-Tag: pool-3.1.4~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b290b2e4ab82a4de28849b85da4b6f10c393ff67;p=thirdparty%2Fpsycopg.git fix: define Escaping method as returning bytes This change is backward compatible because bytes has a superset of the memoryview interface and, in Python, to make pretty much any use of it, it must be converted to bytes beforehand. Close #422. --- diff --git a/docs/news.rst b/docs/news.rst index 2555faae4..441085f58 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -7,6 +7,16 @@ ``psycopg`` release notes ========================= +Future releases +--------------- + +Psycopg 3.1.5 (unreleased) +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Return `!bytes` instead of `!memoryview` from `pq.Encoding` methods + (:ticket:`#422`). + + Current release --------------- diff --git a/psycopg/psycopg/pq/abc.py b/psycopg/psycopg/pq/abc.py index c159fc125..c59e43d59 100644 --- a/psycopg/psycopg/pq/abc.py +++ b/psycopg/psycopg/pq/abc.py @@ -368,17 +368,17 @@ class Escaping(Protocol): def __init__(self, conn: Optional[PGconn] = None): ... - def escape_literal(self, data: Buffer) -> memoryview: + def escape_literal(self, data: Buffer) -> bytes: ... - def escape_identifier(self, data: Buffer) -> memoryview: + def escape_identifier(self, data: Buffer) -> bytes: ... - def escape_string(self, data: Buffer) -> memoryview: + def escape_string(self, data: Buffer) -> bytes: ... - def escape_bytea(self, data: Buffer) -> memoryview: + def escape_bytea(self, data: Buffer) -> bytes: ... - def unescape_bytea(self, data: bytes) -> memoryview: + def unescape_bytea(self, data: bytes) -> bytes: ... diff --git a/psycopg/psycopg/pq/pq_ctypes.py b/psycopg/psycopg/pq/pq_ctypes.py index 428c98271..dccb0afbd 100644 --- a/psycopg/psycopg/pq/pq_ctypes.py +++ b/psycopg/psycopg/pq/pq_ctypes.py @@ -955,7 +955,7 @@ class Escaping: def __init__(self, conn: Optional[PGconn] = None): self.conn = conn - def escape_literal(self, data: "abc.Buffer") -> memoryview: + def escape_literal(self, data: "abc.Buffer") -> bytes: if not self.conn: raise e.OperationalError("escape_literal failed: no connection provided") @@ -970,9 +970,9 @@ class Escaping: ) rv = string_at(out) impl.PQfreemem(out) - return memoryview(rv) + return rv - def escape_identifier(self, data: "abc.Buffer") -> memoryview: + def escape_identifier(self, data: "abc.Buffer") -> bytes: if not self.conn: raise e.OperationalError("escape_identifier failed: no connection provided") @@ -987,9 +987,9 @@ class Escaping: ) rv = string_at(out) impl.PQfreemem(out) - return memoryview(rv) + return rv - def escape_string(self, data: "abc.Buffer") -> memoryview: + def escape_string(self, data: "abc.Buffer") -> bytes: if not isinstance(data, bytes): data = bytes(data) @@ -1018,9 +1018,9 @@ class Escaping: len(data), ) - return memoryview(out.value) + return out.value - def escape_bytea(self, data: "abc.Buffer") -> memoryview: + def escape_bytea(self, data: "abc.Buffer") -> bytes: len_out = c_size_t() # TODO: might be able to do without a copy but it's a mess. # the C library does it better anyway, so maybe not worth optimising @@ -1048,9 +1048,9 @@ class Escaping: rv = string_at(out, len_out.value - 1) # out includes final 0 impl.PQfreemem(out) - return memoryview(rv) + return rv - def unescape_bytea(self, data: bytes) -> memoryview: + def unescape_bytea(self, data: bytes) -> bytes: # not needed, but let's keep it symmetric with the escaping: # if a connection is passed in, it must be valid. if self.conn: @@ -1068,7 +1068,7 @@ class Escaping: rv = string_at(out, len_out.value) impl.PQfreemem(out) - return memoryview(rv) + return rv # importing the ssl module sets up Python's libcrypto callbacks diff --git a/psycopg_c/psycopg_c/pq/escaping.pyx b/psycopg_c/psycopg_c/pq/escaping.pyx index 1959f7096..f0a44d37c 100644 --- a/psycopg_c/psycopg_c/pq/escaping.pyx +++ b/psycopg_c/psycopg_c/pq/escaping.pyx @@ -5,9 +5,7 @@ psycopg_c.pq.Escaping object implementation. # Copyright (C) 2020 The Psycopg Team from libc.string cimport strlen -from cpython.bytearray cimport PyByteArray_FromStringAndSize, PyByteArray_Resize -from cpython.bytearray cimport PyByteArray_AS_STRING -from cpython.memoryview cimport PyMemoryView_FromObject +from cpython.mem cimport PyMem_Malloc, PyMem_Free cdef class Escaping: @@ -16,7 +14,6 @@ cdef class Escaping: cpdef escape_literal(self, data): cdef char *out - cdef bytes rv cdef char *ptr cdef Py_ssize_t length @@ -33,9 +30,9 @@ cdef class Escaping: f"escape_literal failed: {error_message(self.conn)}" ) - return PyMemoryView_FromObject( - PQBuffer._from_buffer(out, strlen(out)) - ) + rv = out[:strlen(out)] + libpq.PQfreemem(out) + return rv cpdef escape_identifier(self, data): cdef char *out @@ -55,41 +52,40 @@ cdef class Escaping: f"escape_identifier failed: {error_message(self.conn)}" ) - return PyMemoryView_FromObject( - PQBuffer._from_buffer(out, strlen(out)) - ) + rv = out[:strlen(out)] + libpq.PQfreemem(out) + return rv cpdef escape_string(self, data): cdef int error cdef size_t len_out cdef char *ptr + cdef char *buf_out cdef Py_ssize_t length - cdef bytearray rv _buffer_as_string_and_size(data, &ptr, &length) - rv = PyByteArray_FromStringAndSize("", 0) - PyByteArray_Resize(rv, length * 2 + 1) - if self.conn is not None: if self.conn._pgconn_ptr is NULL: raise e.OperationalError("the connection is closed") + buf_out = PyMem_Malloc(length * 2 + 1) len_out = libpq.PQescapeStringConn( - self.conn._pgconn_ptr, PyByteArray_AS_STRING(rv), - ptr, length, &error + self.conn._pgconn_ptr, buf_out, ptr, length, &error ) if error: + PyMem_Free(buf_out) raise e.OperationalError( f"escape_string failed: {error_message(self.conn)}" ) else: - len_out = libpq.PQescapeString(PyByteArray_AS_STRING(rv), ptr, length) + buf_out = PyMem_Malloc(length * 2 + 1) + len_out = libpq.PQescapeString(buf_out, ptr, length) - # shrink back or the length will be reported different - PyByteArray_Resize(rv, len_out) - return PyMemoryView_FromObject(rv) + rv = buf_out[:len_out] + PyMem_Free(buf_out) + return rv cpdef escape_bytea(self, data): cdef size_t len_out @@ -113,9 +109,9 @@ cdef class Escaping: f"couldn't allocate for escape_bytea of {len(data)} bytes" ) - return PyMemoryView_FromObject( - PQBuffer._from_buffer(out, len_out - 1) # out includes final 0 - ) + rv = out[:len_out - 1] # out includes final 0 + libpq.PQfreemem(out) + return rv cpdef unescape_bytea(self, const unsigned char *data): # not needed, but let's keep it symmetric with the escaping: @@ -131,4 +127,6 @@ cdef class Escaping: f"couldn't allocate for unescape_bytea of {len(data)} bytes" ) - return PyMemoryView_FromObject(PQBuffer._from_buffer(out, len_out)) + rv = out[:len_out] + libpq.PQfreemem(out) + return rv