Python app-level document/record locking (eg. for MongoDB) MongoDB does not provide means to lock a document (like SELECT FOR UPDATE in RDBMS).

There is a recommended approach to Isolate Sequence of Operations, which is sometimes called optimistic locking, but there are scenarios when this approach seems to be overly complicated or even inefficient (that is why pessimistic locks are still in use).

Task

I am trying to implement generic (can be used with various databases) document (or record) locking at application level. Assuming I have only one application working with collection (or table). And my application is multi-threaded.

Usage scenario:

with DocumentLock(doc_id):
  # make sure no other thread interferes
  doc=db.doc_collection.find_one(doc_id)
  ... # analyse and update the document
  db.doc_collection.save(doc)


Solution

Here is my DocumentLock class:

class DocumentLock(object):

  _lock=RLock() # protects access to _locks dictionary
  _locks=WeakValueDictionary()

  def __init__(self, doc_id):
    self.doc_id=doc_id

  def __enter__(self):
    with self._lock:
      self.lock=self._locks.get(self.doc_id)
      if self.lock is None:
        self._locks[self.doc_id]=self.lock=RLock()
    self.lock.acquire()

  def __exit__(self, exc_type, exc_value, traceback):
    self.lock.release()
    self.lock=None # make available for garbage collection


So basically I have a dictionary of RLocks, one RLock per accessed document. I use weak references to get rid of unused locks.

DocumentLock can be subclassed for every collection (or table) that needs record locking.

Question

This all seems to work (although not tested under concurrency) but I am publishing it here for review and advise. Do you see any weaknesses or mistakes in this code? Is there a better approach to do this kind of things in Python?
The above code will fail with multiple worker nodes (or processes).
To achieve atomic find-and-update, mongodb provides native solution (findOneAndUpdate, findAndModify, updateOne, updateMany).