apsw-sqlite3mc 3.53.3.1__tar.gz → 3.53.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/PKG-INFO +2 -2
  2. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/README.rst +1 -1
  3. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/__init__.pyi +116 -6
  4. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/__main__.py +107 -34
  5. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/async_meta.py +13 -6
  6. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw_sqlite3mc.egg-info/PKG-INFO +2 -2
  7. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/checksums +2 -0
  8. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/sqlite3/sqlite3.c +1024 -92
  9. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/apsw.c +20 -0
  10. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/apsw.docstrings +49 -4
  11. apsw_sqlite3mc-3.53.4.0/src/apswversion.h +1 -0
  12. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/blob.c +255 -37
  13. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/connection.c +2 -2
  14. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/fts.c +10 -1
  15. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/stringconstants.c +16 -0
  16. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/unicode.c +1 -0
  17. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/vfs.c +5 -1
  18. apsw_sqlite3mc-3.53.3.1/src/apswversion.h +0 -1
  19. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/LICENSE +0 -0
  20. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/MANIFEST.in +0 -0
  21. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/__main__.py +0 -0
  22. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/aio.py +0 -0
  23. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/bestpractice.py +0 -0
  24. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/ext.py +0 -0
  25. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/fts5.py +0 -0
  26. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/fts5aux.py +0 -0
  27. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/fts5query.py +0 -0
  28. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/fts_test_strings +0 -0
  29. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/mcall.py +0 -0
  30. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/py.typed +0 -0
  31. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/shell.py +0 -0
  32. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/speedtest.py +0 -0
  33. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/sqlite_extra.json +0 -0
  34. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/sqlite_extra.py +0 -0
  35. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/sqlite_extra_binaries/README.md +0 -0
  36. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/aiotest.py +0 -0
  37. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/carray.py +0 -0
  38. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/extratest.py +0 -0
  39. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/fork_checker.py +0 -0
  40. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/ftstests.py +0 -0
  41. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/jsonb.py +0 -0
  42. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/mctests.py +0 -0
  43. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/sessiontests.py +0 -0
  44. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/tests/shelltest.py +0 -0
  45. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/trace.py +0 -0
  46. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw/unicode.py +0 -0
  47. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw_sqlite3mc.egg-info/SOURCES.txt +0 -0
  48. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw_sqlite3mc.egg-info/dependency_links.txt +0 -0
  49. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw_sqlite3mc.egg-info/entry_points.txt +0 -0
  50. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/apsw_sqlite3mc.egg-info/top_level.txt +0 -0
  51. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/pyproject.toml +0 -0
  52. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/setup.apsw +0 -0
  53. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/setup.cfg +0 -0
  54. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/setup.py +0 -0
  55. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/_unicodedb.c +0 -0
  56. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/argparse.c +0 -0
  57. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/async.c +0 -0
  58. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/backup.c +0 -0
  59. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/carray.c +0 -0
  60. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/constants.c +0 -0
  61. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/cursor.c +0 -0
  62. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/exceptions.c +0 -0
  63. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/faultinject.c +0 -0
  64. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/faultinject.h +0 -0
  65. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/fileio_win32.c +0 -0
  66. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/jsonb.c +0 -0
  67. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/pyutil.c +0 -0
  68. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/session.c +0 -0
  69. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/sqlite_debug.h +0 -0
  70. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/statementcache.c +0 -0
  71. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/testextension.c +0 -0
  72. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/traceback.c +0 -0
  73. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/util.c +0 -0
  74. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/src/vtable.c +0 -0
  75. {apsw_sqlite3mc-3.53.3.1 → apsw_sqlite3mc-3.53.4.0}/tools/vend.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apsw_sqlite3mc
3
- Version: 3.53.3.1
3
+ Version: 3.53.4.0
4
4
  Summary: SQLite3 Multiple Ciphers combined with Another Python SQLite Wrapper
5
5
  Home-page: https://github.com/utelle/apsw-sqlite3mc
6
6
  Author: Ulrich Telle
@@ -90,7 +90,7 @@ Setting the key on a new database is the only change needed to your code.
90
90
 
91
91
  >>> import apsw
92
92
  >>> print(apsw.mc_version)
93
- SQLite3 Multiple Ciphers 2.3.6
93
+ SQLite3 Multiple Ciphers 2.4.0
94
94
  >>> con = apsw.Connection("database.sqlite3")
95
95
  >>> con.pragma("key", "my secret passphrase")
96
96
  ok
@@ -59,7 +59,7 @@ Setting the key on a new database is the only change needed to your code.
59
59
 
60
60
  >>> import apsw
61
61
  >>> print(apsw.mc_version)
62
- SQLite3 Multiple Ciphers 2.3.6
62
+ SQLite3 Multiple Ciphers 2.4.0
63
63
  >>> con = apsw.Connection("database.sqlite3")
64
64
  >>> con.pragma("key", "my secret passphrase")
65
65
  ok
@@ -856,8 +856,8 @@ class Backup:
856
856
  @final
857
857
  class Blob:
858
858
  """This object is created by :meth:`Connection.blob_open` and provides
859
- access to a blob in the database. It behaves like a Python file.
860
- It wraps a `sqlite3_blob
859
+ access to a blob in the database. It behaves like a Python
860
+ :class:`file <io.RawIOBase>` in binary mode. It wraps a `sqlite3_blob
861
861
  <https://sqlite.org/c3ref/blob.html>`_.
862
862
 
863
863
  .. note::
@@ -893,6 +893,9 @@ class Blob:
893
893
  Calls: `sqlite3_blob_close <https://sqlite.org/c3ref/blob_close.html>`__"""
894
894
  ...
895
895
 
896
+ closed: bool
897
+ """Indicates if the blob has been closed."""
898
+
896
899
  def __enter__(self) -> Blob:
897
900
  """You can use a blob as a `context manager
898
901
  <https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers>`_
@@ -913,6 +916,20 @@ class Blob:
913
916
  *with* block is raised after closing the blob."""
914
917
  ...
915
918
 
919
+ def fileno(self) -> int:
920
+ """Always raises :exc:`OSError` because there is no
921
+ underlying file descriptor."""
922
+ ...
923
+
924
+ def flush(self) -> None:
925
+ """Does nothing. There is no intermediate buffer, and SQLite's overall
926
+ transaction/savepoint handling deals with database commits."""
927
+ ...
928
+
929
+ def isatty(self) -> False:
930
+ """Blobs are never interactive."""
931
+ ...
932
+
916
933
  def length(self) -> int:
917
934
  """Returns the size of the blob in bytes.
918
935
 
@@ -951,6 +968,24 @@ class Blob:
951
968
 
952
969
  readinto = read_into ## OLD-NAME
953
970
 
971
+ def readable(self) -> True:
972
+ """You can always read from a blob"""
973
+ ...
974
+
975
+ def readall(self) -> bytes:
976
+ """Read all data until the end of the blob"""
977
+ ...
978
+
979
+ def readline(self, size: int = -1) -> bytes:
980
+ """Always raises :exc:`io.UnsupportedOperation`.
981
+ You can wrap with :class:`io.BufferedReader`."""
982
+ ...
983
+
984
+ def readlines(self, hint: int = -1) -> list[bytes]:
985
+ """Always raises :exc:`io.UnsupportedOperation`.
986
+ You can wrap with :class:`io.BufferedReader`."""
987
+ ...
988
+
954
989
  def reopen(self, rowid: int) -> None:
955
990
  """Change this blob object to point to a different row. It can be
956
991
  faster than closing an existing blob an opening a new one.
@@ -968,13 +1003,28 @@ class Blob:
968
1003
  :raises ValueError: If the resulting offset is before the beginning (less than zero) or beyond the end of the blob."""
969
1004
  ...
970
1005
 
1006
+ def seekable(self) -> True:
1007
+ """You can always seek in a blob"""
1008
+ ...
1009
+
971
1010
  def tell(self) -> int:
972
1011
  """Returns the current offset."""
973
1012
  ...
974
1013
 
975
- def write(self, data: Buffer) -> None:
1014
+ def truncate(self, size = None) -> None:
1015
+ """Always raises :exc:`io.UnsupportedOperation`.
1016
+ You cannot change the size of a blob."""
1017
+ ...
1018
+
1019
+ def writable(self) -> bool:
1020
+ """Returns True if the blob was open for writing, else False."""
1021
+ ...
1022
+
1023
+ def write(self, data: Buffer) -> int:
976
1024
  """Writes the data to the blob.
977
1025
 
1026
+ Returns the number of bytes written, which will be all of them.
1027
+
978
1028
  :param data: Buffer to write
979
1029
 
980
1030
  :raises TypeError: Wrong data type
@@ -987,6 +1037,11 @@ class Blob:
987
1037
  Calls: `sqlite3_blob_write <https://sqlite.org/c3ref/blob_write.html>`__"""
988
1038
  ...
989
1039
 
1040
+ def writelines(self, lines) -> None:
1041
+ """Always raises :exc:`io.UnsupportedOperation`.
1042
+ You can wrap with :class:`io.BufferedWriter`."""
1043
+ ...
1044
+
990
1045
  @final
991
1046
  class ChangesetBuilder:
992
1047
  """This object wraps a `sqlite3_changegroup <https://sqlite.org/session/changegroup.html>`__
@@ -6057,8 +6112,8 @@ class AsyncBlob:
6057
6112
  """This represents a :class:`Blob` when in async mode.
6058
6113
 
6059
6114
  This object is created by :meth:`Connection.blob_open` and provides
6060
- access to a blob in the database. It behaves like a Python file.
6061
- It wraps a `sqlite3_blob
6115
+ access to a blob in the database. It behaves like a Python
6116
+ :class:`file <io.RawIOBase>` in binary mode. It wraps a `sqlite3_blob
6062
6117
  <https://sqlite.org/c3ref/blob.html>`_.
6063
6118
 
6064
6119
  .. note::
@@ -6103,8 +6158,25 @@ class AsyncBlob:
6103
6158
  Calls: `sqlite3_blob_close <https://sqlite.org/c3ref/blob_close.html>`__"""
6104
6159
  ...
6105
6160
 
6161
+ closed: bool
6162
+ """Indicates if the blob has been closed."""
6106
6163
 
6107
6164
 
6165
+
6166
+ def fileno(self) -> int:
6167
+ """Always raises :exc:`OSError` because there is no
6168
+ underlying file descriptor."""
6169
+ ...
6170
+
6171
+ async def flush(self) -> None:
6172
+ """Does nothing. There is no intermediate buffer, and SQLite's overall
6173
+ transaction/savepoint handling deals with database commits."""
6174
+ ...
6175
+
6176
+ def isatty(self) -> False:
6177
+ """Blobs are never interactive."""
6178
+ ...
6179
+
6108
6180
  def length(self) -> int:
6109
6181
  """Returns the size of the blob in bytes.
6110
6182
 
@@ -6141,6 +6213,24 @@ class AsyncBlob:
6141
6213
  Calls: `sqlite3_blob_read <https://sqlite.org/c3ref/blob_read.html>`__"""
6142
6214
  ...
6143
6215
 
6216
+ def readable(self) -> True:
6217
+ """You can always read from a blob"""
6218
+ ...
6219
+
6220
+ async def readall(self) -> bytes:
6221
+ """Read all data until the end of the blob"""
6222
+ ...
6223
+
6224
+ async def readline(self, size: int = -1) -> bytes:
6225
+ """Always raises :exc:`io.UnsupportedOperation`.
6226
+ You can wrap with :class:`io.BufferedReader`."""
6227
+ ...
6228
+
6229
+ async def readlines(self, hint: int = -1) -> list[bytes]:
6230
+ """Always raises :exc:`io.UnsupportedOperation`.
6231
+ You can wrap with :class:`io.BufferedReader`."""
6232
+ ...
6233
+
6144
6234
  async def reopen(self, rowid: int) -> None:
6145
6235
  """Change this blob object to point to a different row. It can be
6146
6236
  faster than closing an existing blob an opening a new one.
@@ -6158,13 +6248,28 @@ class AsyncBlob:
6158
6248
  :raises ValueError: If the resulting offset is before the beginning (less than zero) or beyond the end of the blob."""
6159
6249
  ...
6160
6250
 
6251
+ def seekable(self) -> True:
6252
+ """You can always seek in a blob"""
6253
+ ...
6254
+
6161
6255
  def tell(self) -> int:
6162
6256
  """Returns the current offset."""
6163
6257
  ...
6164
6258
 
6165
- async def write(self, data: Buffer) -> None:
6259
+ async def truncate(self, size = None) -> None:
6260
+ """Always raises :exc:`io.UnsupportedOperation`.
6261
+ You cannot change the size of a blob."""
6262
+ ...
6263
+
6264
+ def writable(self) -> bool:
6265
+ """Returns True if the blob was open for writing, else False."""
6266
+ ...
6267
+
6268
+ async def write(self, data: Buffer) -> int:
6166
6269
  """Writes the data to the blob.
6167
6270
 
6271
+ Returns the number of bytes written, which will be all of them.
6272
+
6168
6273
  :param data: Buffer to write
6169
6274
 
6170
6275
  :raises TypeError: Wrong data type
@@ -6177,6 +6282,11 @@ class AsyncBlob:
6177
6282
  Calls: `sqlite3_blob_write <https://sqlite.org/c3ref/blob_write.html>`__"""
6178
6283
  ...
6179
6284
 
6285
+ async def writelines(self, lines) -> None:
6286
+ """Always raises :exc:`io.UnsupportedOperation`.
6287
+ You can wrap with :class:`io.BufferedWriter`."""
6288
+ ...
6289
+
6180
6290
  class AsyncConnection:
6181
6291
  """This represents a :class:`Connection` when in async mode.
6182
6292
 
@@ -379,24 +379,20 @@ class APSW(unittest.TestCase):
379
379
  if name in apsw.vfs_names():
380
380
  apsw.unregister_vfs(name)
381
381
 
382
- def check_db_mutex(self):
382
+ def check_db_mutex(self, db):
383
383
  # verify a db mutex is not being held by doing work in another
384
384
  # thread
385
- try:
386
- self.db.readonly("main")
387
- except apsw.ConnectionClosedError:
388
- return
389
385
 
390
- self.db.set_progress_handler(None)
391
- self.db.exec_trace = None
392
- self.db.row_trace = None
386
+ db.set_progress_handler(None)
387
+ db.exec_trace = None
388
+ db.row_trace = None
393
389
 
394
390
  val = Exception("The database mutex is still held and should not be")
395
391
 
396
392
  def thread():
397
393
  nonlocal val
398
394
  try:
399
- val = self.db.execute("select 3").get
395
+ val = db.execute("select 3").get
400
396
  except BaseException as exc:
401
397
  val = exc
402
398
 
@@ -412,14 +408,10 @@ class APSW(unittest.TestCase):
412
408
 
413
409
  def tearDown(self):
414
410
  apsw.config(apsw.SQLITE_CONFIG_LOG, None)
415
- if self.db is not None:
416
- try:
417
- self.check_db_mutex()
418
- finally:
419
- self.db.close(True)
420
- del self.db
421
411
  for c in apsw.connections():
422
- c.close()
412
+ self.check_db_mutex(c)
413
+ c.close(True)
414
+ del self.db
423
415
  gc.collect()
424
416
  deltempfiles()
425
417
  if hasattr(apsw, "leak_check"):
@@ -4875,6 +4867,8 @@ class APSW(unittest.TestCase):
4875
4867
 
4876
4868
  def testClosingChecks(self):
4877
4869
  "Check closed connection/blob/cursor is correctly detected"
4870
+ if sys.platform == "emscripten":
4871
+ self.skipTest("pyodide crashes")
4878
4872
  cur = self.db.cursor()
4879
4873
  rowid = curnext(
4880
4874
  cur.execute("create table foo(x blob); insert into foo values(zeroblob(98765)); select rowid from foo")
@@ -4882,7 +4876,7 @@ class APSW(unittest.TestCase):
4882
4876
  blob = self.db.blob_open("main", "foo", "x", rowid, True)
4883
4877
  blob.close()
4884
4878
  nargs = self.blob_nargs
4885
- for func in [x for x in dir(blob) if not x.startswith("__") and x not in ("close", "aclose")]:
4879
+ for func in [x for x in dir(blob) if not x.startswith("__") and x not in ("close", "aclose", "closed")]:
4886
4880
  args = ("one", "two", "three")[: nargs.get(func, 0)]
4887
4881
  try:
4888
4882
  getattr(blob, func)(*args)
@@ -6150,7 +6144,7 @@ class APSW(unittest.TestCase):
6150
6144
  "order": ("closed",),
6151
6145
  },
6152
6146
  "APSWBlob": {
6153
- "skip": ("dealloc", "dealloc_mutex", "init", "close", "close_internal", "tp_repr", "bool", "aclose"),
6147
+ "skip": ("dealloc", "dealloc_mutex", "init", "close", "close_internal", "tp_repr", "bool", "aclose", "closed"),
6154
6148
  "req": {"closed": "CHECK_BLOB_CLOSED"},
6155
6149
  "order": ("use", "closed"),
6156
6150
  },
@@ -6967,6 +6961,12 @@ class APSW(unittest.TestCase):
6967
6961
  self.assertEqual(blobro.read(), b"\x00" * 98765)
6968
6962
  blobro.seek(-3, 2)
6969
6963
  self.assertEqual(blobro.read(), b"\x00" * 3)
6964
+ blobro.seek(0)
6965
+ x=blobro.read()
6966
+ blobro.seek(0)
6967
+ self.assertEqual(blobro.readall(), x)
6968
+ self.assertEqual(b"", blobro.read())
6969
+ self.assertEqual(b"", blobro.readall())
6970
6970
  # check types
6971
6971
  self.assertRaises(TypeError, blobro.read, "foo")
6972
6972
  self.assertRaises(TypeError, blobro.tell, "foo")
@@ -6989,6 +6989,7 @@ class APSW(unittest.TestCase):
6989
6989
  self.assertRaises(apsw.ReadOnlyError, blobro.close)
6990
6990
  # check can't work on closed blob
6991
6991
  self.assertRaises(ValueError, blobro.read)
6992
+ self.assertRaises(ValueError, blobro.readall)
6992
6993
  self.assertRaises(ValueError, blobro.read_into, b"ab")
6993
6994
  self.assertRaises(ValueError, blobro.seek, 0, 0)
6994
6995
  self.assertRaises(ValueError, blobro.tell)
@@ -7063,7 +7064,7 @@ class APSW(unittest.TestCase):
7063
7064
  blobrw.seek(0, 0)
7064
7065
  self.assertEqual(blobrw.read(7), b"abcdefg")
7065
7066
  blobrw.seek(50, 0)
7066
- blobrw.write(b"hijkl")
7067
+ self.assertEqual(5, blobrw.write(b"hijkl"))
7067
7068
  blobrw.seek(-98765, 2)
7068
7069
  self.assertEqual(blobrw.read(55), b"abcdefg" + b"\x00" * 43 + b"hijkl")
7069
7070
  self.assertRaises(TypeError, blobrw.write, 12)
@@ -7090,21 +7091,93 @@ class APSW(unittest.TestCase):
7090
7091
  self.assertRaises(apsw.SQLError, blobro.reopen, 0x1FFFFFFFF)
7091
7092
  blobro.close()
7092
7093
 
7093
- def testBlobReadError(self):
7094
- "Ensure blob read errors are handled well"
7095
- cur = self.db.cursor()
7096
- cur.execute("create table ioerror (x, blob)")
7097
- cur.execute("insert into ioerror (rowid,x,blob) values (2,3,x'deadbeef')")
7098
- blob = self.db.blob_open("main", "ioerror", "blob", 2, False)
7099
- blob.read(1)
7100
- # Do a write which cause blob to become invalid
7101
- cur.execute("update ioerror set blob='fsdfdsfasd' where x=3")
7102
- try:
7103
- blob.read(1)
7104
- 1 / 0
7105
- except:
7106
- klass, value = sys.exc_info()[:2]
7107
- self.assertTrue(klass is apsw.AbortError)
7094
+ def testBlobExpiredError(self):
7095
+ "Ensure blob errors are handled well"
7096
+ self.db.execute("""
7097
+ create table ioerror (x, blob);
7098
+ insert into ioerror (rowid,x,blob)
7099
+ values (2,3,x'deadbeefbaadf00d'), (3,3,x'deadbeefbaadf00d');""")
7100
+ blobro = self.db.blob_open("main", "ioerror", "blob", 2, False)
7101
+ blobrw = self.db.blob_open("main", "ioerror", "blob", 3, True)
7102
+ self.assertEqual(blobro.length(), 8)
7103
+ self.assertEqual(blobrw.length(), 8)
7104
+ blobro.read(4)
7105
+ blobrw.read(4)
7106
+ blobrw.write(b"X")
7107
+ self.assertEqual(blobro.tell(), 4)
7108
+ self.assertEqual(blobrw.tell(), 5)
7109
+ # Do a SQL write which cause blobs to become invalid
7110
+ self.db.execute("update ioerror set blob='fsdfdsfasd' where x=3")
7111
+ self.assertRaises(apsw.AbortError, blobro.read)
7112
+ self.assertRaises(apsw.AbortError, blobro.readinto, bytearray(10))
7113
+ self.assertRaises(apsw.AbortError, blobrw.write, b"Y")
7114
+ self.assertRaises(apsw.AbortError, blobrw.write, b"Z")
7115
+ self.assertRaises(apsw.AbortError, blobro.readall)
7116
+ self.assertRaises(apsw.AbortError, blobrw.readall)
7117
+ self.assertRaises(apsw.AbortError, blobro.read, 1)
7118
+
7119
+ def testBlobIOBase(self):
7120
+ "blob conformance with io.RawIOBase"
7121
+
7122
+ rowids=self.db.execute("""
7123
+ CREATE TABLE victim(x);
7124
+ INSERT INTO victim VALUES (randomblob(16383)) RETURNING rowid;
7125
+ INSERT INTO victim VALUES (randomblob(16383)) RETURNING rowid;
7126
+ """).get
7127
+
7128
+ blobro = self.db.blob_open("main", "victim", "x", rowids[0], False)
7129
+ blobrw = self.db.blob_open("main", "victim", "x", rowids[1], True)
7130
+
7131
+ for name in dir(io.RawIOBase):
7132
+ match name:
7133
+ case "close" | "read" | "write" | "seek" | "tell" | "readall" | "readinto":
7134
+ # covered in tests above
7135
+ pass
7136
+ case _ if name.startswith("_"):
7137
+ pass
7138
+ case "closed":
7139
+ self.assertFalse(blobro.closed)
7140
+ self.assertFalse(blobrw.closed)
7141
+ case "fileno":
7142
+ self.assertRaises(OSError, blobro.fileno)
7143
+ self.assertRaises(OSError, blobrw.fileno)
7144
+ case "flush":
7145
+ self.assertIsNone(blobro.flush())
7146
+ self.assertIsNone(blobrw.flush())
7147
+ case "isatty":
7148
+ self.assertFalse(blobro.isatty())
7149
+ self.assertFalse(blobrw.isatty())
7150
+ case "readable" | "seekable":
7151
+ self.assertTrue(blobro.readable())
7152
+ self.assertTrue(blobrw.readable())
7153
+ self.assertTrue(blobro.seekable())
7154
+ self.assertTrue(blobrw.seekable())
7155
+ case "readline" | "readlines" | "writelines" | "truncate":
7156
+ # pyodide gets a fatal internal error on these
7157
+ if sys.platform == "emscripten":
7158
+ self.skipTest("pyodide crashes")
7159
+ self.assertRaises(io.UnsupportedOperation, blobro.readline)
7160
+ self.assertRaises(io.UnsupportedOperation, blobrw.readline)
7161
+ self.assertRaises(io.UnsupportedOperation, blobro.readlines)
7162
+ self.assertRaises(io.UnsupportedOperation, blobrw.readlines)
7163
+ self.assertRaises(io.UnsupportedOperation, blobro.writelines)
7164
+ self.assertRaises(io.UnsupportedOperation, blobrw.writelines)
7165
+ self.assertRaises(io.UnsupportedOperation, blobro.truncate)
7166
+ self.assertRaises(io.UnsupportedOperation, blobrw.truncate)
7167
+ case "writable":
7168
+ self.assertFalse(blobro.writable())
7169
+ self.assertTrue(blobrw.writable())
7170
+ case _:
7171
+ raise NotImplementedError
7172
+
7173
+ blobro.close()
7174
+ blobrw.close()
7175
+ self.assertTrue(blobro.closed)
7176
+ self.assertTrue(blobrw.closed)
7177
+
7178
+ self.assertIsInstance(blobro, io.RawIOBase)
7179
+ self.assertIsInstance(blobrw, io.RawIOBase)
7180
+
7108
7181
 
7109
7182
  def testAutovacuumPages(self):
7110
7183
  self.assertRaises(TypeError, self.db.autovacuum_pages)
@@ -10,14 +10,14 @@
10
10
  # stubs and documentation.
11
11
 
12
12
  import functools
13
- import inspect
14
- import threading
15
- import unittest
16
13
  import importlib.resources
14
+ import inspect
15
+ import io
17
16
  import json
18
-
19
17
  import queue
20
- from typing import Literal, Callable, Any
18
+ import threading
19
+ import unittest
20
+ from typing import Any, Callable, Literal
21
21
 
22
22
  import apsw
23
23
  import apsw.aio
@@ -245,6 +245,14 @@ class AsyncMeta(unittest.TestCase):
245
245
  if klass == "Connection" and member in {"vtab_config", "vtab_on_conflict"}:
246
246
  return "value"
247
247
  raise
248
+ except io.UnsupportedOperation:
249
+ if klass == "Blob" and member in {"readline", "readlines", "truncate", "writelines"}:
250
+ return "value" if send else "async"
251
+ raise
252
+ except OSError:
253
+ if (klass, member) == ("Blob", "fileno"):
254
+ return "value"
255
+ raise
248
256
 
249
257
  def testMetaJson(self):
250
258
  apsw.async_controller.set(SimpleController)
@@ -335,7 +343,6 @@ class AsyncMeta(unittest.TestCase):
335
343
  "aclose",
336
344
  "__exit__",
337
345
  "__aexit__",
338
- "finish",
339
346
  }
340
347
 
341
348
  pre = {"__aexit__": "__aenter__", "__exit__": "__enter__", "__next__": "__iter__", "__anext__": "__aiter__"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apsw-sqlite3mc
3
- Version: 3.53.3.1
3
+ Version: 3.53.4.0
4
4
  Summary: SQLite3 Multiple Ciphers combined with Another Python SQLite Wrapper
5
5
  Home-page: https://github.com/utelle/apsw-sqlite3mc
6
6
  Author: Ulrich Telle
@@ -90,7 +90,7 @@ Setting the key on a new database is the only change needed to your code.
90
90
 
91
91
  >>> import apsw
92
92
  >>> print(apsw.mc_version)
93
- SQLite3 Multiple Ciphers 2.3.6
93
+ SQLite3 Multiple Ciphers 2.4.0
94
94
  >>> con = apsw.Connection("database.sqlite3")
95
95
  >>> con.pragma("key", "my secret passphrase")
96
96
  ok
@@ -21,6 +21,8 @@ https://sqlite.org/sqlar/zip/sqlar-src-20180107193712-4824e73896.zip 1980460 63b
21
21
  https://github.com/madler/zlib/releases/download/v1.3.2/zlib132.zip 1616754 e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28 6b35b981f73425dc5f142cbee457f078d42240c022c804982849b962f1d1e2c6
22
22
 
23
23
  # SQLite releases
24
+ https://sqlite.org/2026/sqlite-autoconf-3530400.tar.gz 3283177 0e9483900e92cd5de8fd48d16bf9200145a61f7fd5be542a5ac81d8a9516eb9c 454e45f61c6bd75b7420e7190732dea03ce6639c63ada47bbc592f67fc340338
25
+ https://sqlite.org/2026/sqlite-src-3530400.zip 14557315 d18fa15aec74d8c17e1463f861095adc01b5ad190256acb4f91d22f0368d232b b834d474b9b393d85a9e3ee4cc11f1329e007e9376a424ee740796f5c4bda3a8
24
26
  https://sqlite.org/2026/sqlite-autoconf-3530300.tar.gz 3282443 c917d7db16648ec95f714974ace5e5dcf46b7dc70e26600a0a102a3141125db0 98f2b3f3c11be6a03ea32346937b032c2472ebbd7a716bed36ca2f5693e7ce8b
25
27
  https://sqlite.org/2026/sqlite-src-3530300.zip 14537772 bb80bf8a3bffc19241ce8aba5a4bc74e9c3980013cb0b5f0f0976a99516942af 2daecfa16e3b19e058dc2e2cb717b80ade361e0315aa5376c3619f66aa81e181
26
28
  https://sqlite.org/2026/sqlite-autoconf-3530200.tar.gz 3279926 588ad51949419a56ebe81fe56193d510c559eb94c9a57748387860b5d3069316 025328da165109f48abccc6e7478508060804412bed2bd81d47e98ba1b72983b