From b977a9cec0ec32f7ab7990a4016744dc8b9c70cf Mon Sep 17 00:00:00 2001 From: uadhran Date: Sat, 19 Sep 2026 14:56:14 +0530 Subject: [PATCH 1/2] fix: coerce doc_ids in update/remove like get String IDs such as '1' already worked with get() via str lookup, but update/remove compared them to int table keys and silently no-oped. Coerce through document_id_class so both paths agree. Fixes #639 --- tests/test_tinydb.py | 10 ++++++++++ tinydb/table.py | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/test_tinydb.py b/tests/test_tinydb.py index 9ff866fe..e9723bd6 100644 --- a/tests/test_tinydb.py +++ b/tests/test_tinydb.py @@ -364,6 +364,16 @@ def test_doc_id_missing_consistency(db: TinyDB): assert db.remove(doc_ids=[99]) == [] +def test_doc_ids_string_coercion(db: TinyDB): + # ``get(doc_id='1')`` already works via str coercion; ``update`` / + # ``remove`` must accept the same string IDs (see #639). + assert db.get(doc_id='1') is not None + assert db.update({'int': 9}, doc_ids=['1']) == [1] + assert db.get(doc_id=1)['int'] == 9 + assert db.remove(doc_ids=['1']) == [1] + assert db.get(doc_id=1) is None + + def test_update_multiple(db: TinyDB): assert len(db) == 3 diff --git a/tinydb/table.py b/tinydb/table.py index f4027bfa..1fc28eb5 100644 --- a/tinydb/table.py +++ b/tinydb/table.py @@ -457,7 +457,9 @@ def perform_update(table, doc_id): # (see issue #591). The list of *actually* updated IDs is # determined inside the updater so it reflects the table state at # write time. - requested_ids = list(doc_ids) + # Coerce IDs with document_id_class so string IDs like '1' match + # table keys the same way get(doc_id='1') does (see issue #639). + requested_ids = [self.document_id_class(doc_id) for doc_id in doc_ids] updated_ids: list[int] = [] def updater(table: dict): @@ -640,7 +642,9 @@ def remove( # ``get(doc_ids=...)`` (see issue #591). The list of *actually* # removed IDs is determined inside the updater so it reflects the # table state at write time. - requested_ids = list(doc_ids) + # Coerce IDs with document_id_class so string IDs like '1' match + # table keys the same way get(doc_id='1') does (see issue #639). + requested_ids = [self.document_id_class(doc_id) for doc_id in doc_ids] removed_ids: list[int] = [] def updater(table: dict): From 0838f66f572a02173be9e08e509f72c84b3d8e68 Mon Sep 17 00:00:00 2001 From: uadhran Date: Sat, 19 Sep 2026 14:59:46 +0530 Subject: [PATCH 2/2] fix: make string doc_id types mypy-clean CI failed on Python 3.14 mypy: get(doc_id='1') and update/remove with doc_ids=['1'] did not match annotations that only allowed int. - Annotate get/update/remove to accept int | str IDs - Coerce get(doc_id=...) through document_id_class like other read paths - Narrow Document | None in the regression test before indexing --- tests/test_tinydb.py | 4 +++- tinydb/table.py | 17 ++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/test_tinydb.py b/tests/test_tinydb.py index e9723bd6..ca2356f8 100644 --- a/tests/test_tinydb.py +++ b/tests/test_tinydb.py @@ -369,7 +369,9 @@ def test_doc_ids_string_coercion(db: TinyDB): # ``remove`` must accept the same string IDs (see #639). assert db.get(doc_id='1') is not None assert db.update({'int': 9}, doc_ids=['1']) == [1] - assert db.get(doc_id=1)['int'] == 9 + doc = db.get(doc_id=1) + assert doc is not None + assert doc['int'] == 9 assert db.remove(doc_ids=['1']) == [1] assert db.get(doc_id=1) is None diff --git a/tinydb/table.py b/tinydb/table.py index 1fc28eb5..cf9edd8d 100644 --- a/tinydb/table.py +++ b/tinydb/table.py @@ -309,12 +309,14 @@ def get( @overload def get( - self, cond: Optional[QueryLike], doc_id: int, doc_ids: Optional[list] = ... + self, cond: Optional[QueryLike], doc_id: Union[int, str], + doc_ids: Optional[list] = ... ) -> Optional[Document]: ... @overload def get( - self, *, cond: Optional[QueryLike] = ..., doc_id: int, doc_ids: Optional[list] = ..., + self, *, cond: Optional[QueryLike] = ..., doc_id: Union[int, str], + doc_ids: Optional[list] = ..., ) -> Optional[Document]: ... @overload @@ -335,7 +337,7 @@ def get( def get( self, cond: Optional[QueryLike] = None, - doc_id: Optional[int] = None, + doc_id: Optional[Union[int, str]] = None, doc_ids: Optional[list] = None ): """ @@ -360,8 +362,9 @@ def get( if raw_doc is None: return None - # Convert the raw data to the document class - return self.document_class(raw_doc, doc_id) + # Convert the raw data to the document class. Coerce the ID the same + # way other read paths do so str IDs like '1' become document_id_class. + return self.document_class(raw_doc, self.document_id_class(doc_id)) elif doc_ids is not None: # Filter the table by extracting out all those documents which @@ -423,7 +426,7 @@ def update( self, fields: Union[Mapping, Callable[[MutableMapping], None]], cond: Optional[QueryLike] = None, - doc_ids: Optional[Iterable[int]] = None, + doc_ids: Optional[Iterable[Union[int, str]]] = None, ) -> list[int]: """ Update all matching documents to have a given set of fields. @@ -622,7 +625,7 @@ def upsert(self, document: Mapping, cond: Optional[QueryLike] = None) -> list[in def remove( self, cond: Optional[QueryLike] = None, - doc_ids: Optional[Iterable[int]] = None, + doc_ids: Optional[Iterable[Union[int, str]]] = None, ) -> list[int]: """ Remove all matching documents.