From 13bf77f0f4f93ac0d7c222fabffa1769f091e8bf Mon Sep 17 00:00:00 2001 From: disqualifier Date: Sun, 28 Jun 2026 18:45:25 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20mongo=20backend=20=E2=80=94=20sync=20clo?= =?UTF-8?q?se()=20+=20fail-loud=20via=20raw=20collection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit two regressions in the [mongo] storage backend: (1) the four finally blocks did 'await db.close()' but the mongo lib's close() became synchronous this session, so await None raised TypeError on every op — dropped the await. (2) the backend consumed mongo's swallow-and-return-default wrapped methods raw, conflating a driver error with 'no document / not initialized' in the lib that gates authority; it now goes through the raw db.collection(name) escape hatch (the motor collection, which raises) and raises on a no-op upsert, matching the CLI's fail-loud stance. Signed-off-by: disqualifier --- .../storage/mongo_store.py | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/envelope_authorizer/storage/mongo_store.py b/src/envelope_authorizer/storage/mongo_store.py index 8bdaf1f..238fd8d 100644 --- a/src/envelope_authorizer/storage/mongo_store.py +++ b/src/envelope_authorizer/storage/mongo_store.py @@ -6,6 +6,14 @@ self-contained per call. no module-level client and no shared event loop, so it never collides with a running loop. a per-call connect is an accepted tradeoff for a one-shot admin CLI. requires envelope_authorizer[mongo]; without it every method raises a clear RuntimeError. + +fail-loud: operations go through the mongo lib's RAW collection escape hatch +(`db.collection(name)`, the motor collection — which raises) rather than the +swallow-and-return-default wrapped methods. this is an auth backend; conflating +a backend error with "no document / not initialized" in the thing that gates +authority is the worst place for the swallow anti-pattern, so a driver error +propagates and a no-op upsert raises instead of silently reporting success. +`close()` is synchronous on the mongo lib (motor's close is sync) — not awaited. """ import asyncio @@ -36,33 +44,38 @@ class MongoStore(StorageBackend): async def _get(self, fingerprint: str) -> Optional[dict]: db = Mongo(self.uri, self.database) try: - return await db.get_document(self.collection, {"_id": fingerprint}) + return await db.collection(self.collection).find_one({"_id": fingerprint}) finally: - await db.close() + db.close() async def _get_all(self) -> List[dict]: db = Mongo(self.uri, self.database) try: - return await db.get_documents(self.collection, {}) + cursor = db.collection(self.collection).find({}) + return await cursor.to_list(length=None) finally: - await db.close() + db.close() async def _save(self, doc: dict) -> None: db = Mongo(self.uri, self.database) try: - await db.update_document( - self.collection, {"_id": doc["_id"]}, doc, do_upsert=True + result = await db.collection(self.collection).replace_one( + {"_id": doc["_id"]}, doc, upsert=True ) + if not (result.matched_count or result.upserted_id): + raise RuntimeError( + f"mongo upsert affected no document for _id={doc['_id']!r}" + ) finally: - await db.close() + db.close() async def _delete(self, fingerprint: str) -> bool: db = Mongo(self.uri, self.database) try: - removed = await db.delete_document(self.collection, {"_id": fingerprint}) - return bool(removed) + result = await db.collection(self.collection).delete_one({"_id": fingerprint}) + return result.deleted_count > 0 finally: - await db.close() + db.close() def get(self, fingerprint: str) -> Optional[dict]: """return the key doc with this `_id`, or None"""