Skip to content

fix(model): honor document.id in upsert()'s no-match insert - #149

Merged
cdwilson merged 1 commit into
mainfrom
fix/upsert-honors-document-id
Aug 14, 2026
Merged

cdwilson merged 1 commit into
mainfrom
fix/upsert-honors-document-id

Conversation

@cdwilson

Copy link
Copy Markdown
Collaborator

Closes #120

What

upsert() with a condition silently discarded a set document.id: the no-match insert landed at a fresh id and then overwrote document.id with it, destroying even the evidence that the caller asked for a specific id. The same instance produced two different outcomes depending on whether a condition was passed.

The no-match insert now honors document.id exactly as insert() does — a set id says "put it here" — and a taken id raises DocumentAlreadyExistsError instead of silently duplicating the document elsewhere. Only id=None still gets a fresh id, the condition's own id value is still never adopted, and a matching condition still wins over a preset id (now pinned by a test).

How

Delegating to Table.upsert() cannot express this: handed a Document and a cond, TinyDB extracts the doc_id and calls Table.update(document, cond, doc_ids=[doc_id]), which checks doc_ids before cond — the condition is never evaluated. The non-id-condition branch therefore inlines upstream's update-then-insert through the public Table.update()/Table.insert(); the id-condition branch keeps its atomic write cycle and passes the id-bearing Document through to the fallback insert. Both branches translate the taken-id ValueError via the new _upsert_insert() helper.

Per the upstream-friction policy, the Table.upsert() behavior is recorded on the Upstream Limitations page and tracked as #148 (public-API workaround; no private-API registry entry needed).

Docs

  • upsert() docstring: documents the id-honoring insert and the new Raises: entry.
  • CRUD tour: new prose + doctested DocumentAlreadyExistsError example.
  • CHANGELOG entry under Unreleased → Fixed.

Verification

  • TDD: every new test watched to fail first ([1] != [7], DID NOT RAISE).
  • poe test: 3990 passed.
  • poe test-matrix: green on Python 3.10–3.14, exit 0.
  • poe check (ruff, sbom, cspell, mypy, pyright) and strict poe docs-build: clean.

🤖 Generated with Claude Code

upsert() with a condition serialized the model with force_dict=True,
so a set document.id was dropped on the no-match insert: the document
landed at a fresh id and document.id was then overwritten with it,
destroying even the evidence that the caller asked for a specific id
(#120). The same instance produced two different outcomes depending
on whether a condition was passed.

The no-match insert now honors document.id exactly as insert() does:
a set id says "put it here", so the document is inserted at that id,
and a taken id raises DocumentAlreadyExistsError instead of silently
duplicating the document elsewhere. Only id=None still gets a fresh
id, and the condition's own id value is still never adopted. A
matching condition still wins over a preset id (pinned by a test).

Delegating to Table.upsert() cannot express this: handed a Document
and a cond, TinyDB extracts the doc_id and passes it to
Table.update(document, cond, doc_ids=[doc_id]), which checks doc_ids
before cond — the condition is never evaluated. The non-id-condition
branch therefore inlines upstream's update-then-insert through the
public Table.update()/Table.insert(); the id-condition branch keeps
its atomic write cycle and now passes the id-bearing Document
through to the fallback insert. Both branches translate the taken-id
ValueError via the new _upsert_insert() helper. Friction recorded on
the Upstream Limitations page and tracked as #148.

Assisted-by: Claude:claude-fable-5
@netlify

netlify Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for tinydantic-preview ready!

Name Link
🔨 Latest commit 4f23d9a
🔍 Latest deploy log https://app.netlify.com/projects/tinydantic-preview/deploys/6a7e741bd6b1820008149a44
😎 Deploy Preview https://deploy-preview-149--tinydantic-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 90
Accessibility: 98
Best Practices: 83
SEO: 97
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@cdwilson
cdwilson merged commit 389c6d9 into main Aug 14, 2026
27 checks passed
@cdwilson
cdwilson deleted the fix/upsert-honors-document-id branch August 14, 2026 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

upsert() silently discards document.id when a condition is passed

1 participant