Files
mongo/README.md
T
dsql 30f0e44097 fix: correct wrong success predicates in upsert_document_field and create_documents
upsert_document_field returned False for an idempotent no-op upsert on an
existing document (modified_count=0, upserted_id=None) even though the doc
matched the requested state; switch to matched_count like its update_document_field
and update_document_operator siblings.

create_documents returned 0 on a partial insert_many failure, discarding the
BulkWriteError's nInserted count and inviting callers to retry the whole batch
on a wrongly-reported failure, duplicating documents that had already landed.
Catch BulkWriteError specifically and return e.details['nInserted'].

Bump to v0.1.7.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 17:22:31 -04:00

3.9 KiB

mongo

Async MongoDB wrapper over motor. Thin, opinionated helpers for the common paths, with a raw escape hatch for everything else.

Install

requirements.txt:

mongo @ git+ssh://git@git.rethinkstudios.io/rethink-public/mongo.git@v0.1.7

Direct:

pip install "mongo @ git+ssh://git@git.rethinkstudios.io/rethink-public/mongo.git@v0.1.7"

Requires motor and pymongo (pulled transitively).

Drop the @v0.1.7 suffix from the line above to install the latest unpinned.

Usage

Object (preferred) — one client per process:

from mongo import MongoDB

db = MongoDB(conn_string, database)         # attach as bot.db / app.db
await db.connect()                          # optional: ping to fail-early on a bad URI
users = await db.get_documents("users", {"active": True})
db.close()                                  # on shutdown (sync)

# or with guaranteed cleanup:
async with MongoDB(conn_string, database) as db:
    await db.get_documents("users", {"active": True})

The class is MongoDB; Mongo remains a back-compat alias (Mongo = MongoDB), so existing Mongo(...) call sites keep working unchanged.

Module proxy (back-compat) — arm once, then call bare:

import mongo                                # NOT `from mongo import ...`
mongo.init(conn_string, database)
users = await mongo.get_documents("users", {"active": True})

Both styles share one client. The proxy exists so legacy call sites keep working after a one-line init(); new code should use the object.

Naming consistency with the datastore trio

mongo predates the redis/psql/mysql trio; this version makes it surface-consistent without breaking anything (all additive, every old name preserved):

  • class is MongoDB (with Mongo kept as an alias)
  • connect() + async with like the trio (motor connects lazily, so connect() just pings to validate early)
  • exists() aliases check_document_exists(); delete() aliases delete_document() — old names still work, the trio-consistent names are now available

The one deliberate difference that remains: mongo swallows (see below) where the trio is fail-loud. That's intentional — flipping it would break existing consumers' branch- on-result control flow.

Error contract

  • Wrapped methods log-and-swallow exceptions and return a safe default (False / [] / {} / 0 / None). Branch on the result. (connect() is the exception — it raises on a bad connection so you can fail-early.)
  • db.collection(name) (or db[name]) returns the raw motor collection: full driver surface, no swallowing, raises. Use it for anything not wrapped (find_one_and_* beyond what's exposed, change streams, complex bulk ops).

API

See the module docstring and method docstrings in mongo.py — that's the source of truth. Grouped as: collection/index management, create, read, update, delete, bulk, checks. Atomic ops (find_one_and_update/replace/delete) and bulk_write are included.

Gotchas

  • from mongo import func won't see the proxy (resolved at import, before init). Use import mongo then mongo.func(...).
  • create_collection(collection, index=...) seeds one index (the param is index, renamed from indexes in v0.1.6). A key-spec like [("a",1),("b",1)] builds one compound index, not two — call create_index per index for multiples.
  • find_one_and_update returns the after image by default (return_after=True).
  • bulk_write takes pymongo ops the caller builds:
    from pymongo import UpdateOne
    ops = [UpdateOne({"_id": i}, {"$set": {...}}, upsert=True) for i in ids]
    await db.bulk_write("col", ops)
    

Versioning

Releases are tagged vX.Y.Z. The install line above pins a release; drop the @vX.Y.Z suffix to install the latest unpinned. Pin deliberately for reproducible installs.