Skip to content

Commit a861756

Browse files
author
Erlend Egeberg Aasland
authored
gh-69093: Add context manager support to sqlite3.Blob (GH-91562)
1 parent 4e661cd commit a861756

File tree

6 files changed

+135
-11
lines changed

6 files changed

+135
-11
lines changed

Doc/includes/sqlite3/blob.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
con.execute("create table test(blob_col blob)")
55
con.execute("insert into test(blob_col) values (zeroblob(10))")
66

7-
blob = con.blobopen("test", "blob_col", 1)
8-
blob.write(b"Hello")
9-
blob.write(b"World")
10-
blob.seek(0)
11-
print(blob.read()) # will print b"HelloWorld"
12-
blob.close()
7+
# Write to our blob, using two write operations:
8+
with con.blobopen("test", "blob_col", 1) as blob:
9+
blob.write(b"Hello")
10+
blob.write(b"World")
11+
12+
# Read the contents of our blob
13+
with con.blobopen("test", "blob_col", 1) as blob:
14+
greeting = blob.read()
15+
16+
print(greeting) # outputs "b'HelloWorld'"

Doc/library/sqlite3.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,11 @@ Blob Objects
11151115
data in an SQLite :abbr:`BLOB (Binary Large OBject)`. Call ``len(blob)`` to
11161116
get the size (number of bytes) of the blob.
11171117

1118+
Use the :class:`Blob` as a :term:`context manager` to ensure that the blob
1119+
handle is closed after use.
1120+
1121+
.. literalinclude:: ../includes/sqlite3/blob.py
1122+
11181123
.. method:: close()
11191124

11201125
Close the blob.
@@ -1149,10 +1154,6 @@ Blob Objects
11491154
current position) and :data:`os.SEEK_END` (seek relative to the blob’s
11501155
end).
11511156

1152-
:class:`Blob` example:
1153-
1154-
.. literalinclude:: ../includes/sqlite3/blob.py
1155-
11561157

11571158
.. _sqlite3-types:
11581159

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,25 @@ def test_blob_sequence_not_supported(self):
11701170
with self.assertRaises(TypeError):
11711171
b"a" in self.blob
11721172

1173+
def test_blob_context_manager(self):
1174+
data = b"a" * 50
1175+
with self.cx.blobopen("test", "b", 1) as blob:
1176+
blob.write(data)
1177+
actual = self.cx.execute("select b from test").fetchone()[0]
1178+
self.assertEqual(actual, data)
1179+
1180+
# Check that __exit__ closed the blob
1181+
with self.assertRaisesRegex(sqlite.ProgrammingError, "closed blob"):
1182+
blob.read()
1183+
1184+
def test_blob_context_manager_reraise_exceptions(self):
1185+
class DummyException(Exception):
1186+
pass
1187+
with self.assertRaisesRegex(DummyException, "reraised"):
1188+
with self.cx.blobopen("test", "b", 1) as blob:
1189+
raise DummyException("reraised")
1190+
1191+
11731192
def test_blob_closed(self):
11741193
with memory_database() as cx:
11751194
cx.execute("create table test(b blob)")
@@ -1186,6 +1205,10 @@ def test_blob_closed(self):
11861205
blob.seek(0)
11871206
with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
11881207
blob.tell()
1208+
with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
1209+
blob.__enter__()
1210+
with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
1211+
blob.__exit__(None, None, None)
11891212

11901213
def test_blob_closed_db_read(self):
11911214
with memory_database() as cx:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :term:`context manager` support to :class:`sqlite3.Blob`.
2+
Patch by Aviv Palivoda and Erlend E. Aasland.

Modules/_sqlite/blob.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,51 @@ blob_tell_impl(pysqlite_Blob *self)
307307
}
308308

309309

310+
/*[clinic input]
311+
_sqlite3.Blob.__enter__ as blob_enter
312+
313+
Blob context manager enter.
314+
[clinic start generated code]*/
315+
316+
static PyObject *
317+
blob_enter_impl(pysqlite_Blob *self)
318+
/*[clinic end generated code: output=4fd32484b071a6cd input=fe4842c3c582d5a7]*/
319+
{
320+
if (!check_blob(self)) {
321+
return NULL;
322+
}
323+
return Py_NewRef(self);
324+
}
325+
326+
327+
/*[clinic input]
328+
_sqlite3.Blob.__exit__ as blob_exit
329+
330+
type: object
331+
val: object
332+
tb: object
333+
/
334+
335+
Blob context manager exit.
336+
[clinic start generated code]*/
337+
338+
static PyObject *
339+
blob_exit_impl(pysqlite_Blob *self, PyObject *type, PyObject *val,
340+
PyObject *tb)
341+
/*[clinic end generated code: output=fc86ceeb2b68c7b2 input=575d9ecea205f35f]*/
342+
{
343+
if (!check_blob(self)) {
344+
return NULL;
345+
}
346+
close_blob(self);
347+
Py_RETURN_FALSE;
348+
}
349+
350+
310351
static PyMethodDef blob_methods[] = {
311352
BLOB_CLOSE_METHODDEF
353+
BLOB_ENTER_METHODDEF
354+
BLOB_EXIT_METHODDEF
312355
BLOB_READ_METHODDEF
313356
BLOB_SEEK_METHODDEF
314357
BLOB_TELL_METHODDEF

Modules/_sqlite/clinic/blob.c.h

Lines changed: 52 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)