diff --git a/README.md b/README.md index 5504fa4..ef7bd08 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ helpers for the common paths, with a raw escape hatch for everything else. `requirements.txt`: ``` -mongo @ git+ssh://git@git.rethinkstudios.io/rethink-public/mongo.git@v0.1.1 +mongo @ git+ssh://git@git.rethinkstudios.io/rethink-public/mongo.git@v0.1.2 ``` Direct: ```bash -pip install "mongo @ git+ssh://git@git.rethinkstudios.io/rethink-public/mongo.git@v0.1.1" +pip install "mongo @ git+ssh://git@git.rethinkstudios.io/rethink-public/mongo.git@v0.1.2" ``` Requires `motor` and `pymongo` (pulled transitively). diff --git a/pyproject.toml b/pyproject.toml index 369f81d..b9c648c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mongo" -version = "0.1.1" +version = "0.1.2" description = "async mongodb wrapper over motor with a raw escape hatch" requires-python = ">=3.10" dependencies = [ diff --git a/src/mongo/__init__.py b/src/mongo/__init__.py index fa500e0..6000814 100644 --- a/src/mongo/__init__.py +++ b/src/mongo/__init__.py @@ -7,7 +7,11 @@ def __getattr__(name: str): """proxy bare package attribute access to the default instance (PEP 562) lets `import mongo; await mongo.get_documents(...)` work after init(). - `from mongo import func` still won't see this (resolved before init) + `from mongo import func` still won't see this (resolved before init). + note: the bare-proxy raw escape hatch is `mongo.collection(name)`, not + `mongo[name]` — module-level subscripting isn't a thing in python, so the proxy + can only forward named attributes. on a Mongo instance both `db[name]` and + `db.collection(name)` work. """ if not name.startswith("_") and hasattr(Mongo, name): return getattr(instance(), name) diff --git a/src/mongo/mongo.py b/src/mongo/mongo.py index 4a77873..bb01505 100644 --- a/src/mongo/mongo.py +++ b/src/mongo/mongo.py @@ -181,7 +181,11 @@ class Mongo: return [] async def get_document_hashmap(self, collection: str, target: dict, key: str) -> dict: - """return matching documents keyed into a dict by the given field""" + """return matching documents keyed into a dict by the given field + + documents missing `key` are skipped (not in the result); a later document + with a duplicate key value overwrites an earlier one. + """ try: cursor = self._db[collection].find(target) return {doc[key]: doc async for doc in cursor if key in doc} @@ -192,7 +196,11 @@ class Mongo: async def get_document_fields( self, collection: str, target: dict, key: str, fields: Optional[dict] = None ) -> List: - """return a flat list of one field's value across matching documents""" + """return a flat list of one field's value across matching documents + + documents missing `key` are skipped, so the list length may be smaller than + the match count. + """ try: cursor = self._db[collection].find(target, fields) return [doc[key] async for doc in cursor if key in doc] @@ -299,10 +307,15 @@ class Mongo: return 0 async def update_document_field(self, collection: str, target: dict, updates: dict) -> bool: - """$set one or more fields on a single document""" + """$set one or more fields on a single document + + returns True when a document matched, even if the write changed nothing (a + `$set` to the identical value leaves `modified_count=0`); use `matched_count` + so an idempotent no-op on an existing doc isn't misread as a failure. + """ try: response = await self._db[collection].update_one(target, {"$set": updates}) - return response.modified_count > 0 + return response.matched_count > 0 except Exception: log.exception(f"db.update_document_field() on {collection}") return False @@ -310,10 +323,15 @@ class Mongo: async def update_document_operator( self, collection: str, target: dict, update_query: dict ) -> bool: - """apply raw update operators ($set/$inc/$unset/...) to a single document""" + """apply raw update operators ($set/$inc/$unset/...) to a single document + + returns True when a document matched, even if the operators changed nothing + (e.g. `$set` to an identical value) — use `matched_count` so an idempotent + write on an existing doc isn't misread as a failure. + """ try: response = await self._db[collection].update_one(target, update_query) - return response.modified_count > 0 + return response.matched_count > 0 except Exception: log.exception(f"db.update_document_operator() on {collection}") return False @@ -369,10 +387,15 @@ class Mongo: async def document_pop_array( self, collection: str, target: dict, array: str, value: Any ) -> bool: - """$pull a value from an array field""" + """$pull a value from an array field + + returns True when a document matched, even if nothing was pulled (the value + was absent) — use `matched_count` so a no-op pull on an existing doc isn't + misread as a failure. + """ try: response = await self._db[collection].update_one(target, {"$pull": {array: value}}) - return response.modified_count > 0 + return response.matched_count > 0 except Exception: log.exception(f"db.document_pop_array() on {collection}") return False