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 faaa3713cb5107..39e57a3829e379 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1166,6 +1166,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)") @@ -1182,6 +1201,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 821295cee813fd..aa244a5c6b7500 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]*/ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy