[Python-checkins] gh-69093: Add context manager support to sqlite3.Blob (GH-91562)

JelleZijlstra webhook-mailer at python.org
Sat Apr 16 00:21:17 EDT 2022


https://github.com/python/cpython/commit/a8617566759a07c67d14c9b6ed663e32d3b3f5e7
commit: a8617566759a07c67d14c9b6ed663e32d3b3f5e7
branch: main
author: Erlend Egeberg Aasland <erlend.aasland at innova.no>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2022年04月15日T21:21:12-07:00
summary:
gh-69093: Add context manager support to sqlite3.Blob (GH-91562)
files:
A Misc/NEWS.d/next/Library/2022-04-14-00-59-01.gh-issue-69093.bmlMwI.rst
M Doc/includes/sqlite3/blob.py
M Doc/library/sqlite3.rst
M Lib/test/test_sqlite3/test_dbapi.py
M Modules/_sqlite/blob.c
M Modules/_sqlite/clinic/blob.c.h
diff --git a/Doc/includes/sqlite3/blob.py b/Doc/includes/sqlite3/blob.py
index 61994fb82dd72a..b3694ad08af46b 100644
--- a/Doc/includes/sqlite3/blob.py
+++ b/Doc/includes/sqlite3/blob.py
@@ -4,9 +4,13 @@
 con.execute("create table test(blob_col blob)")
 con.execute("insert into test(blob_col) values (zeroblob(10))")
 
-blob = con.blobopen("test", "blob_col", 1)
-blob.write(b"Hello")
-blob.write(b"World")
-blob.seek(0)
-print(blob.read()) # will print b"HelloWorld"
-blob.close()
+# Write to our blob, using two write operations:
+with con.blobopen("test", "blob_col", 1) as blob:
+ blob.write(b"Hello")
+ blob.write(b"World")
+
+# Read the contents of our blob
+with con.blobopen("test", "blob_col", 1) as blob:
+ greeting = blob.read()
+
+print(greeting) # outputs "b'HelloWorld'"
diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst
index d0274fb79744d4..4838db01669e66 100644
--- a/Doc/library/sqlite3.rst
+++ b/Doc/library/sqlite3.rst
@@ -1115,6 +1115,11 @@ Blob Objects
 data in an SQLite :abbr:`BLOB (Binary Large OBject)`. Call ``len(blob)`` to
 get the size (number of bytes) of the blob.
 
+ Use the :class:`Blob` as a :term:`context manager` to ensure that the blob
+ handle is closed after use.
+
+ .. literalinclude:: ../includes/sqlite3/blob.py
+
 .. method:: close()
 
 Close the blob.
@@ -1149,10 +1154,6 @@ Blob Objects
 current position) and :data:`os.SEEK_END` (seek relative to the blob’s
 end).
 
- :class:`Blob` example:
-
- .. literalinclude:: ../includes/sqlite3/blob.py
-
 
 .. _sqlite3-types:
 
diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py
index b010813fff7c58..79dcb3ef8954a0 100644
--- a/Lib/test/test_sqlite3/test_dbapi.py
+++ b/Lib/test/test_sqlite3/test_dbapi.py
@@ -1170,6 +1170,25 @@ def test_blob_sequence_not_supported(self):
 with self.assertRaises(TypeError):
 b"a" in self.blob
 
+ def test_blob_context_manager(self):
+ data = b"a" * 50
+ with self.cx.blobopen("test", "b", 1) as blob:
+ blob.write(data)
+ actual = self.cx.execute("select b from test").fetchone()[0]
+ self.assertEqual(actual, data)
+
+ # Check that __exit__ closed the blob
+ with self.assertRaisesRegex(sqlite.ProgrammingError, "closed blob"):
+ blob.read()
+
+ def test_blob_context_manager_reraise_exceptions(self):
+ class DummyException(Exception):
+ pass
+ with self.assertRaisesRegex(DummyException, "reraised"):
+ with self.cx.blobopen("test", "b", 1) as blob:
+ raise DummyException("reraised")
+
+
 def test_blob_closed(self):
 with memory_database() as cx:
 cx.execute("create table test(b blob)")
@@ -1186,6 +1205,10 @@ def test_blob_closed(self):
 blob.seek(0)
 with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
 blob.tell()
+ with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
+ blob.__enter__()
+ with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
+ blob.__exit__(None, None, None)
 
 def test_blob_closed_db_read(self):
 with memory_database() as cx:
diff --git a/Misc/NEWS.d/next/Library/2022-04-14-00-59-01.gh-issue-69093.bmlMwI.rst b/Misc/NEWS.d/next/Library/2022-04-14-00-59-01.gh-issue-69093.bmlMwI.rst
new file mode 100644
index 00000000000000..d45a139b50e821
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-04-14-00-59-01.gh-issue-69093.bmlMwI.rst
@@ -0,0 +1,2 @@
+Add :term:`context manager` support to :class:`sqlite3.Blob`.
+Patch by Aviv Palivoda and Erlend E. Aasland.
diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c
index c4f8be45b2f941..3f766302d62517 100644
--- a/Modules/_sqlite/blob.c
+++ b/Modules/_sqlite/blob.c
@@ -307,8 +307,51 @@ blob_tell_impl(pysqlite_Blob *self)
 }
 
 
+/*[clinic input]
+_sqlite3.Blob.__enter__ as blob_enter
+
+Blob context manager enter.
+[clinic start generated code]*/
+
+static PyObject *
+blob_enter_impl(pysqlite_Blob *self)
+/*[clinic end generated code: output=4fd32484b071a6cd input=fe4842c3c582d5a7]*/
+{
+ if (!check_blob(self)) {
+ return NULL;
+ }
+ return Py_NewRef(self);
+}
+
+
+/*[clinic input]
+_sqlite3.Blob.__exit__ as blob_exit
+
+ type: object
+ val: object
+ tb: object
+ /
+
+Blob context manager exit.
+[clinic start generated code]*/
+
+static PyObject *
+blob_exit_impl(pysqlite_Blob *self, PyObject *type, PyObject *val,
+ PyObject *tb)
+/*[clinic end generated code: output=fc86ceeb2b68c7b2 input=575d9ecea205f35f]*/
+{
+ if (!check_blob(self)) {
+ return NULL;
+ }
+ close_blob(self);
+ Py_RETURN_FALSE;
+}
+
+
 static PyMethodDef blob_methods[] = {
 BLOB_CLOSE_METHODDEF
+ BLOB_ENTER_METHODDEF
+ BLOB_EXIT_METHODDEF
 BLOB_READ_METHODDEF
 BLOB_SEEK_METHODDEF
 BLOB_TELL_METHODDEF
diff --git a/Modules/_sqlite/clinic/blob.c.h b/Modules/_sqlite/clinic/blob.c.h
index 30b3e3c194739f..237877a9b37f13 100644
--- a/Modules/_sqlite/clinic/blob.c.h
+++ b/Modules/_sqlite/clinic/blob.c.h
@@ -162,4 +162,55 @@ blob_tell(pysqlite_Blob *self, PyObject *Py_UNUSED(ignored))
 {
 return blob_tell_impl(self);
 }
-/*[clinic end generated code: output=d3a02b127f2cfa58 input=a9049054013a1b77]*/
+
+PyDoc_STRVAR(blob_enter__doc__,
+"__enter__($self, /)\n"
+"--\n"
+"\n"
+"Blob context manager enter.");
+
+#define BLOB_ENTER_METHODDEF \
+ {"__enter__", (PyCFunction)blob_enter, METH_NOARGS, blob_enter__doc__},
+
+static PyObject *
+blob_enter_impl(pysqlite_Blob *self);
+
+static PyObject *
+blob_enter(pysqlite_Blob *self, PyObject *Py_UNUSED(ignored))
+{
+ return blob_enter_impl(self);
+}
+
+PyDoc_STRVAR(blob_exit__doc__,
+"__exit__($self, type, val, tb, /)\n"
+"--\n"
+"\n"
+"Blob context manager exit.");
+
+#define BLOB_EXIT_METHODDEF \
+ {"__exit__", (PyCFunction)(void(*)(void))blob_exit, METH_FASTCALL, blob_exit__doc__},
+
+static PyObject *
+blob_exit_impl(pysqlite_Blob *self, PyObject *type, PyObject *val,
+ PyObject *tb);
+
+static PyObject *
+blob_exit(pysqlite_Blob *self, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ PyObject *type;
+ PyObject *val;
+ PyObject *tb;
+
+ if (!_PyArg_CheckPositional("__exit__", nargs, 3, 3)) {
+ goto exit;
+ }
+ type = args[0];
+ val = args[1];
+ tb = args[2];
+ return_value = blob_exit_impl(self, type, val, tb);
+
+exit:
+ return return_value;
+}
+/*[clinic end generated code: output=ca2400862c18dadb input=a9049054013a1b77]*/


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /