From d25cdd37a3b7d1b277b399106473fa368b72635a Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 1 Mar 2022 16:24:27 -0800 Subject: [PATCH] db.sqlite_version property and fix for deterministic=True on SQLite 3.8.3 Closes #408 --- docs/python-api.rst | 10 ++++++++++ sqlite_utils/db.py | 12 +++++++++++- tests/test_constructor.py | 9 +++++++++ tests/test_register_function.py | 19 +++++++++++++++++-- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/docs/python-api.rst b/docs/python-api.rst index e8c4bda..6c35ef3 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -1717,6 +1717,16 @@ A more useful example: if you are working with `SpatiaLite >> db.sqlite_version + (3, 36, 0) + .. _python_api_introspection: Introspecting tables and views diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 9e00366..3bc528f 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -387,7 +387,11 @@ class Database: if not replace and (name, arity) in self._registered_functions: return fn kwargs = {} - if deterministic and sys.version_info >= (3, 8): + if ( + deterministic + and sys.version_info >= (3, 8) + and self.sqlite_version >= (3, 8, 3) + ): kwargs["deterministic"] = True self.conn.create_function(name, arity, fn, **kwargs) self._registered_functions.add((name, arity)) @@ -547,6 +551,12 @@ class Database: except Exception: return False + @property + def sqlite_version(self) -> Tuple[int, ...]: + "Version of SQLite, as a tuple of integers e.g. (3, 36, 0)" + row = self.execute("select sqlite_version()").fetchall()[0] + return tuple(map(int, row[0].split("."))) + @property def journal_mode(self) -> str: "Current ``journal_mode`` of this database." diff --git a/tests/test_constructor.py b/tests/test_constructor.py index 36ab1bc..d8c8d72 100644 --- a/tests/test_constructor.py +++ b/tests/test_constructor.py @@ -16,3 +16,12 @@ def test_memory_name(): db2 = Database(memory_name="shared") db1["dogs"].insert({"name": "Cleo"}) assert list(db2["dogs"].rows) == [{"name": "Cleo"}] + + +def test_sqlite_version(): + db = Database(memory=True) + version = db.sqlite_version + assert isinstance(version, tuple) + as_string = ".".join(map(str, version)) + actual = next(db.query("select sqlite_version() as v"))["v"] + assert actual == as_string diff --git a/tests/test_register_function.py b/tests/test_register_function.py index 19af0b6..d86c7b6 100644 --- a/tests/test_register_function.py +++ b/tests/test_register_function.py @@ -34,16 +34,31 @@ def test_register_function_deterministic(fresh_db): @pytest.mark.skipif( sys.version_info < (3, 8), reason="deterministic=True was added in Python 3.8" ) -def test_register_function_deterministic_registered(fresh_db): +@pytest.mark.parametrize( + "fake_sqlite_version,should_use_deterministic", + ( + ("3.36.0", True), + ("3.8.3", True), + ("3.8.2", False), + ), +) +def test_register_function_deterministic_registered( + fresh_db, fake_sqlite_version, should_use_deterministic +): fresh_db.conn = MagicMock() fresh_db.conn.create_function = MagicMock() + fresh_db.conn.execute().fetchall.return_value = [(fake_sqlite_version,)] @fresh_db.register_function(deterministic=True) def to_lower_2(s): return s.lower() + expected_kwargs = {} + if should_use_deterministic: + expected_kwargs = dict(deterministic=True) + fresh_db.conn.create_function.assert_called_with( - "to_lower_2", 1, to_lower_2, deterministic=True + "to_lower_2", 1, to_lower_2, **expected_kwargs )