Extending

sherlock can be easily extended to work with any backend. You just have to register your lock’s implementation with sherlock and you will be able to use your lock with the backend of your choice in your project.

Registration

Custom locks can be registered using the following API:

backends.register(name, lock_class, library, client_class, default_args=(), default_kwargs={})

Register a custom backend.

Parameters:
  • name (str) – Name of the backend by which you would want to refer this backend in your code.
  • lock_class (class) – the sub-class of sherlock.lock.BaseLock that you have implemented. The reference to your implemented lock class will be used by sherlock.Lock proxy to use your implemented class when you globally set that the choice of backend is the one that has been implemented by you.
  • library (str) – dependent client library that this implementation makes use of.
  • client_class – the client class or valid type which you use to connect the datastore. This is used by the configure() function to validate that the object provided for the client parameter is actually an instance of this class.
  • default_args (tuple) – default arguments that need to passed to create an instance of the callable passed to client_class parameter.
  • default_kwargs (dict) – default keyword arguments that need to passed to create an instance of the callable passed to client_class parameter.

Usage:

>>> import some_db_client
>>> class MyLock(sherlock.lock.BaseLock):
...     # your implementation comes here
...     pass
>>>
>>> sherlock.configure(name='Mylock',
...                    lock_class=MyLock,
...                    library='some_db_client',
...                    client_class=some_db_client.Client,
...                    default_args=('localhost:1234'),
...                    default_kwargs=dict(connection_pool=6))

Example

Here is an example of implementing a custom lock that uses Elasticsearch as backend.

Note

You may distributed your custom lock implementation as package if you please. Just make sure that you add sherlock as a dependency.

The following code goes in a module called sherlock_es.py.

import elasticsearch
import sherlock
import uuid

from elasticsearch import Elasticsearch
from sherlock import LockException


class ESLock(sherlock.lock.BaseLock):
    def __init__(self, lock_name, **kwargs):
        super(ESLock, self).__init__(lock_name, **kwargs)

        if self.client is None:
            self.client = Elasticsearch(hosts=['localhost:9200'])

        self._owner = None

    def _acquire(self):
        owner = uuid.uuid4().hex

        try:
            self.client.get(index='sherlock', doc_type='locks',
                            id=self.lock_name)
        except elasticsearch.NotFoundError, err:
            self.client.index(index='sherlock', doc_type='locks',
                              id=self.lock_name, body=dict(owner=owner))
            self._owner = owner
            return True
        else:
            return False

    def _release(self):
        if self._owner is None:
            raise LockException('Lock was not set by this process.')

        try:
            resp = self.client.get(index='sherlock', doc_type='locks',
                                   id=self.lock_name)
            if resp['_source']['owner'] == self._owner:
                self.client.delete(index='sherlock', doc_type='locks',
                                   id=self.lock_name)

            else:
                raise LockException('Lock could not be released because it '
                                    'was not acquired by this process.')
        except elasticsearch.NotFoundError, err:
            raise LockException('Lock could not be released as it has not '
                                'been acquired.')

    @property
    def _locked(self):
        try:
            self.client.get(index='sherlock', doc_type='locks',
                            id=self.lock_name)
            return True
        except elasticsearch.NotFoundError, err:
            return False


# Register the custom lock with sherlock
sherlock.backends.register(name='ES',
                           lock_class=ESLock,
                           library='elasticsearch',
                           client_class=Elasticsearch,
                           default_args=(),
                           default_kwargs={
                               'hosts': ['localhost:9200'],
                           })

Our module can be used like so:

import sherlock
import sherlock_es

# Notice that ES is available as backend now
sherlock.configure(backend=sherlock.backends.ES)

lock1 = sherlock.Lock('test1')
lock1.acquire() # True

lock2 = sherlock_es.ESLock('test2')
lock2.acquire() # True

Indices and tables