Skip to content

Commit fafa966

Browse files
simonwclaude
andcommitted
Table.create() now stores configuration in _defaults
When configuration parameters like pk, foreign_keys, not_null, etc. are passed to Table.create(), they are now stored in self._defaults so that subsequent operations on the same Table instance (like insert, upsert) will use those settings automatically. Previously, calling db["table"].insert({...}, pk="id") would create the table correctly but subsequent calls to insert() or upsert() on the same table object would not know about the pk setting, causing issues like upsert() failing with "requires a pk" error. Closes #655 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent d5f113d commit fafa966

File tree

2 files changed

+139
-9
lines changed

2 files changed

+139
-9
lines changed

sqlite_utils/db.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,19 +1703,19 @@ def strict(self) -> bool:
17031703
def create(
17041704
self,
17051705
columns: Dict[str, Any],
1706-
pk: Optional[Any] = None,
1707-
foreign_keys: Optional[ForeignKeysType] = None,
1708-
column_order: Optional[List[str]] = None,
1709-
not_null: Optional[Iterable[str]] = None,
1710-
defaults: Optional[Dict[str, Any]] = None,
1711-
hash_id: Optional[str] = None,
1712-
hash_id_columns: Optional[Iterable[str]] = None,
1713-
extracts: Optional[Union[Dict[str, str], List[str]]] = None,
1706+
pk: Optional[Any] = DEFAULT,
1707+
foreign_keys: Optional[ForeignKeysType] = DEFAULT,
1708+
column_order: Optional[List[str]] = DEFAULT,
1709+
not_null: Optional[Iterable[str]] = DEFAULT,
1710+
defaults: Optional[Dict[str, Any]] = DEFAULT,
1711+
hash_id: Optional[str] = DEFAULT,
1712+
hash_id_columns: Optional[Iterable[str]] = DEFAULT,
1713+
extracts: Optional[Union[Dict[str, str], List[str]]] = DEFAULT,
17141714
if_not_exists: bool = False,
17151715
replace: bool = False,
17161716
ignore: bool = False,
17171717
transform: bool = False,
1718-
strict: bool = False,
1718+
strict: bool = DEFAULT,
17191719
) -> "Table":
17201720
"""
17211721
Create a table with the specified columns.
@@ -1737,6 +1737,38 @@ def create(
17371737
:param transform: If table already exists transform it to fit the specified schema
17381738
:param strict: Apply STRICT mode to table
17391739
"""
1740+
# Resolve defaults from _defaults (issue #655)
1741+
pk = self.value_or_default("pk", pk)
1742+
foreign_keys = self.value_or_default("foreign_keys", foreign_keys)
1743+
column_order = self.value_or_default("column_order", column_order)
1744+
not_null = self.value_or_default("not_null", not_null)
1745+
defaults = self.value_or_default("defaults", defaults)
1746+
hash_id = self.value_or_default("hash_id", hash_id)
1747+
hash_id_columns = self.value_or_default("hash_id_columns", hash_id_columns)
1748+
extracts = self.value_or_default("extracts", extracts)
1749+
strict = self.value_or_default("strict", strict)
1750+
1751+
# Store configuration in _defaults for subsequent operations (issue #655)
1752+
# Don't store pk if hash_id is set, since pk is derived from hash_id in that case
1753+
if pk is not None and hash_id is None:
1754+
self._defaults["pk"] = pk
1755+
if foreign_keys is not None:
1756+
self._defaults["foreign_keys"] = foreign_keys
1757+
if column_order is not None:
1758+
self._defaults["column_order"] = column_order
1759+
if not_null is not None:
1760+
self._defaults["not_null"] = not_null
1761+
if defaults is not None:
1762+
self._defaults["defaults"] = defaults
1763+
if hash_id is not None:
1764+
self._defaults["hash_id"] = hash_id
1765+
if hash_id_columns is not None:
1766+
self._defaults["hash_id_columns"] = hash_id_columns
1767+
if extracts is not None:
1768+
self._defaults["extracts"] = extracts
1769+
if strict:
1770+
self._defaults["strict"] = strict
1771+
17401772
columns = {name: value for (name, value) in columns.items()}
17411773
with self.db.conn:
17421774
self.db.create_table(

tests/test_create.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,3 +1380,101 @@ def test_bad_table_and_view_exceptions(fresh_db):
13801380
with pytest.raises(NoView) as ex2:
13811381
fresh_db.view("t")
13821382
assert ex2.value.args[0] == "View t does not exist"
1383+
1384+
1385+
# Tests for issue #655: Table configuration should be stored in _defaults
1386+
# after table creation, so subsequent operations use the same settings.
1387+
1388+
1389+
def test_pk_persists_after_insert_655(fresh_db):
1390+
"""When pk is passed to insert(), subsequent inserts should use it."""
1391+
table = fresh_db["users"]
1392+
table.insert({"id": 1, "name": "Alice"}, pk="id")
1393+
# Second insert should use pk="id" from _defaults
1394+
table.insert({"id": 2, "name": "Bob"})
1395+
assert table.pks == ["id"]
1396+
# Verify both rows exist (not overwritten due to missing pk)
1397+
assert table.count == 2
1398+
1399+
1400+
def test_pk_persists_after_insert_all_655(fresh_db):
1401+
"""When pk is passed to insert_all(), subsequent inserts should use it."""
1402+
table = fresh_db["users"]
1403+
table.insert_all([{"id": 1, "name": "Alice"}], pk="id")
1404+
# Second insert_all should use pk="id" from _defaults
1405+
table.insert_all([{"id": 2, "name": "Bob"}])
1406+
assert table.pks == ["id"]
1407+
assert table.count == 2
1408+
1409+
1410+
def test_pk_persists_after_create_655(fresh_db):
1411+
"""When pk is passed to create(), it should be stored in _defaults."""
1412+
table = fresh_db["users"]
1413+
table.create({"id": int, "name": str}, pk="id")
1414+
assert table._defaults["pk"] == "id"
1415+
# Subsequent insert should use the pk
1416+
table.insert({"id": 1, "name": "Alice"})
1417+
table.insert({"id": 2, "name": "Bob"})
1418+
assert table.count == 2
1419+
1420+
1421+
def test_foreign_keys_persist_after_create_655(fresh_db):
1422+
"""When foreign_keys is passed to create(), it should be stored in _defaults."""
1423+
fresh_db["authors"].insert({"id": 1, "name": "Alice"}, pk="id")
1424+
table = fresh_db["books"]
1425+
table.create(
1426+
{"id": int, "title": str, "author_id": int},
1427+
pk="id",
1428+
foreign_keys=[("author_id", "authors", "id")],
1429+
)
1430+
assert table._defaults["pk"] == "id"
1431+
assert table._defaults["foreign_keys"] == [("author_id", "authors", "id")]
1432+
1433+
1434+
def test_not_null_persists_after_create_655(fresh_db):
1435+
"""When not_null is passed to create(), it should be stored in _defaults."""
1436+
table = fresh_db["users"]
1437+
table.create({"id": int, "name": str}, pk="id", not_null=["name"])
1438+
assert table._defaults["not_null"] == ["name"]
1439+
1440+
1441+
def test_defaults_persist_after_create_655(fresh_db):
1442+
"""When defaults is passed to create(), it should be stored in _defaults."""
1443+
table = fresh_db["users"]
1444+
table.create({"id": int, "score": int}, pk="id", defaults={"score": 0})
1445+
assert table._defaults["defaults"] == {"score": 0}
1446+
1447+
1448+
def test_strict_persists_after_create_655(fresh_db):
1449+
"""When strict is passed to create(), it should be stored in _defaults."""
1450+
table = fresh_db["users"]
1451+
table.create({"id": int, "name": str}, pk="id", strict=True)
1452+
assert table._defaults["strict"] == True
1453+
1454+
1455+
def test_upsert_uses_pk_from_prior_insert_655(fresh_db):
1456+
"""After insert with pk, upsert should use the same pk."""
1457+
table = fresh_db["users"]
1458+
table.insert({"id": 1, "name": "Alice"}, pk="id")
1459+
# Upsert should work without specifying pk again
1460+
table.upsert({"id": 1, "name": "Alice Updated"})
1461+
assert table.count == 1
1462+
assert list(table.rows)[0]["name"] == "Alice Updated"
1463+
1464+
1465+
def test_upsert_all_uses_pk_from_prior_insert_655(fresh_db):
1466+
"""After insert with pk, upsert_all should use the same pk."""
1467+
table = fresh_db["users"]
1468+
table.insert({"id": 1, "name": "Alice"}, pk="id")
1469+
# Upsert_all should work without specifying pk again
1470+
table.upsert_all([{"id": 1, "name": "Alice Updated"}, {"id": 2, "name": "Bob"}])
1471+
assert table.count == 2
1472+
rows = {row["id"]: row["name"] for row in table.rows}
1473+
assert rows == {1: "Alice Updated", 2: "Bob"}
1474+
1475+
1476+
def test_chained_create_sets_pks(fresh_db):
1477+
table = fresh_db.table("dogs3", pk="id").create(
1478+
{"id": int, "name": str, "color": str}
1479+
)
1480+
assert table.pks == ["id"]

0 commit comments

Comments
 (0)