From ae30c8f6ebae8f2526a2ce71a6ac24d52d831931 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micka=C3=ABl=20Desfr=C3=AAnes?=
 <mickael.desfrenes@unicaen.fr>
Date: Thu, 27 Mar 2025 15:22:46 +0100
Subject: [PATCH] lower required python version. Add callables in migrations

---
 README.md            |  3 ++-
 pyproject.toml       |  4 ++--
 src/oora/__init__.py | 13 +++++++------
 tests.py             |  6 +++++-
 4 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index f829484..63360f7 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,8 @@ Oora is "sql first" and these are out of scope:
 
     db = oora.DB(
         db_path=":memory:",  # or /path/to/your/db.sqlite3
-        # migrations are just pairs key=>val where key is an arbitrary (but unique) label and val is a SQL script.
+        # migrations are just pairs of key=>val where key is an arbitrary (but unique) label and val is a SQL script or a callable.
+        # If val is a callable, it must take a sqlite3.Cursor as first parameter.
         # migrations are executed in order
         migrations={
             # here's an initial migration:
diff --git a/pyproject.toml b/pyproject.toml
index 538b3ef..05f4413 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,13 +1,13 @@
 [project]
 name = "oora"
-version = "0.2.2"
+version = "0.2.3"
 description = "Thin layer above sqlite3"
 readme = "README.md"
 license = {text = "CECILL-C"}
 authors = [
     { name = "Mickaël Desfrênes", email = "mickael.desfrenes@unicaen.fr" }
 ]
-requires-python = ">=3.12"
+requires-python = ">=3.9"
 dependencies = []
 [dependency-groups]
 dev = [
diff --git a/src/oora/__init__.py b/src/oora/__init__.py
index aed7e88..c2fa681 100644
--- a/src/oora/__init__.py
+++ b/src/oora/__init__.py
@@ -61,11 +61,14 @@ class DB:
                 "SELECT COUNT(*) from _migrations where identifier = ?", (identifier,)
             )
             result = cursor.fetchone()
-            if result["COUNT(*)"] == 0:
-                cursor.executescript(script)
+            if result["COUNT(*)"] == 0:  # needs migration
+                if callable(script):
+                    script(cursor)
+                else:
+                    cursor.executescript(script)
                 cursor.execute(
                     "INSERT INTO _migrations(identifier, script) VALUES(?,?)",
-                    (identifier, script),
+                    (identifier, str(script)),
                 )
                 needs_commit = True
         if needs_commit:
@@ -140,9 +143,7 @@ class DB:
         dataclass_instance._db = self
         return dataclass_instance
 
-    def delete(
-        self, table: str, where: str = None, params: List = None
-    ) -> sqlite3.Cursor:
+    def delete(self, table: str, where: str, params: List = None) -> sqlite3.Cursor:
         """
         No escaping of any kind is done on the 'table' and 'where' params.
         Do not trust user input with these.
diff --git a/tests.py b/tests.py
index c60a0c5..5c05829 100644
--- a/tests.py
+++ b/tests.py
@@ -16,6 +16,10 @@ CREATE TABLE IF NOT EXISTS actormovie(
 """
 
 
+def migration_as_callable(cursor: sqlite3.Cursor):
+    assert isinstance(cursor, sqlite3.Cursor)
+
+
 @dataclass
 class Actor:
     id: int
@@ -101,7 +105,7 @@ class TestOora(unittest.TestCase):
     def test_link_integrity(self):
         db = MovieDB(
             db_path=":memory:",
-            migrations={"0000": INITIAL_SCHEMA},
+            migrations={"0000": INITIAL_SCHEMA, "00001": migration_as_callable},
             migrate_on_connect=True,
         )
         actor = db.save(Actor(None, "Daniel Day Lewis"))
-- 
GitLab