]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: define Escaping method as returning bytes
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 3 Nov 2022 23:53:20 +0000 (00:53 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 4 Nov 2022 14:05:43 +0000 (15:05 +0100)
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.

docs/news.rst
psycopg/psycopg/pq/abc.py
psycopg/psycopg/pq/pq_ctypes.py
psycopg_c/psycopg_c/pq/escaping.pyx

index 2555faae488083bedc7c1fa4c3df2a5d7f4a03fe..441085f5848eb79cc6c1c59eaa2507bec4d64eb0 100644 (file)
@@ -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
 ---------------
 
index c159fc12581e0e89706bb4ac8248bede62287cbe..c59e43d593486ff50ec1396fe557a8acf17e44a9 100644 (file)
@@ -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:
         ...
index 428c982712d1b6f7c6bbd8254ce0f9479a3217c5..dccb0afbd6c0ba5a8c79f1ac1e0e260286ca9a7c 100644 (file)
@@ -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
index 1959f7096c093fb8b7049c0a7f65e30c9e3349f3..f0a44d37c8937f8f6aec3a7935d411ce452401a4 100644 (file)
@@ -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(<unsigned char *>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(<unsigned char *>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 = <char *>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 = <char *>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