Metadata-Version: 2.0
Name: python-redis-lock
Version: 2.0.0
Summary: Lock context manager implemented via redis SETNX/BLPOP.
Home-page: https://github.com/ionelmc/python-redis-lock
Author: Ionel Cristian Mărieș
Author-email: contact@ionelmc.ro
License: BSD
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: Unix
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Utilities
Requires-Dist: redis (>=2.10.0)
Provides-Extra: django
Requires-Dist: django-redis (>=3.8.0); extra == 'django'

===============================
redis-lock
===============================

| |docs| |travis| |appveyor| |coveralls| |landscape| |scrutinizer|
| |version| |downloads| |wheel| |supported-versions| |supported-implementations|

.. |docs| image:: https://readthedocs.org/projects/python-redis-lock/badge/?style=flat
    :target: https://readthedocs.org/projects/python-redis-lock
    :alt: Documentation Status

.. |travis| image:: http://img.shields.io/travis/ionelmc/python-redis-lock/master.png?style=flat
    :alt: Travis-CI Build Status
    :target: https://travis-ci.org/ionelmc/python-redis-lock

.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/ionelmc/python-redis-lock?branch=master
    :alt: AppVeyor Build Status
    :target: https://ci.appveyor.com/project/ionelmc/python-redis-lock

.. |coveralls| image:: http://img.shields.io/coveralls/ionelmc/python-redis-lock/master.png?style=flat
    :alt: Coverage Status
    :target: https://coveralls.io/r/ionelmc/python-redis-lock

.. |landscape| image:: https://landscape.io/github/ionelmc/python-redis-lock/master/landscape.svg?style=flat
    :target: https://landscape.io/github/ionelmc/python-redis-lock/master
    :alt: Code Quality Status

.. |version| image:: http://img.shields.io/pypi/v/python-redis-lock.png?style=flat
    :alt: PyPI Package latest release
    :target: https://pypi.python.org/pypi/python-redis-lock

.. |downloads| image:: http://img.shields.io/pypi/dm/python-redis-lock.png?style=flat
    :alt: PyPI Package monthly downloads
    :target: https://pypi.python.org/pypi/python-redis-lock

.. |wheel| image:: https://pypip.in/wheel/python-redis-lock/badge.png?style=flat
    :alt: PyPI Wheel
    :target: https://pypi.python.org/pypi/python-redis-lock

.. |supported-versions| image:: https://pypip.in/py_versions/python-redis-lock/badge.png?style=flat
    :alt: Supported versions
    :target: https://pypi.python.org/pypi/python-redis-lock

.. |supported-implementations| image:: https://pypip.in/implementation/python-redis-lock/badge.png?style=flat
    :alt: Supported imlementations
    :target: https://pypi.python.org/pypi/python-redis-lock

.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/ionelmc/python-redis-lock/master.png?style=flat
    :alt: Scrtinizer Status
    :target: https://scrutinizer-ci.com/g/ionelmc/python-redis-lock/

Lock context manager implemented via redis SETNX/BLPOP.

* Free software: BSD license

Interface targeted to be exactly like `threading.Lock <http://docs.python.org/2/library/threading.html#threading.Lock>`_.

Usage
=====

Because we don't want to require users to share the lock instance across processes you will have to give them names.
Eg::

    conn = StrictRedis()
    with redis_lock.Lock(conn, "name-of-the-lock"):
        print("Got the lock. Doing some work ...")
        time.sleep(5)

Eg::

    lock = redis_lock.Lock(conn, "name-of-the-lock")
    if lock.acquire(blocking=False):
        print("Got the lock.")
    else:
        print("Someone else has the lock.")


You can also associate an identifier along with the lock so that it can be retrieved later by the same process, or by a
different one. This is useful in cases where the application needs to identify the lock owner (find out who currently
owns the lock). Eg::

    import socket
    host_id = "owned-by-%s" % socket.gethostname()
    lock = redis_lock.Lock(conn, "name-of-the-lock", id=host_id)
    if lock.acquire(blocking=False):
        print("Got the lock.")
    else:
        if lock.get_owner_id() == host_id:
            print("I already acquired this in another process.")
        else:
            print("The lock is held on another machine.")


Avoid dogpile effect in django
------------------------------

The dogpile is also known as the thundering herd effect or cache stampede. Here's a pattern to avoid the problem
without serving stale data. The work will be performed a single time and every client will wait for the fresh data.

To use this you will need `django-redis <https://github.com/niwibe/django-redis>`_, however, ``python-redis-lock``
provides you a cache backend that has a cache method for your convenience. Just install ``python-redis-lock`` like
this::

    pip install "python-redis-lock[django]"

Now put something like this in your settings::

    CACHES = {
        'default': {
            'BACKEND': 'redis_lock.django_cache.RedisCache',
            'LOCATION': '127.0.0.1:6379',
            'OPTIONS': {
                'DB': 1
            }
        }
    }

This backend just adds a convenient ``.lock(name, expire=None)`` function to django-redis's cache backend.

You would write your functions like this::

    from django.core.cache import cache

    def function():
        val = cache.get(key)
        if val:
            return val
        else:
            with cache.lock(key):
                val = cache.get(key)
                if val:
                    return val
                else:
                    # DO EXPENSIVE WORK
                    val = ...

                    cache.set(key, value)
                    return val


Troubleshooting
------------------------------

In some cases, the lock remains in redis forever (like a server blackout / redis or application crash / an unhandled
exception). In such cases, the lock is not removed by restarting the application. One solution is to use the
``reset_all()`` function when the application starts::

    # On application start/restart
    import redis_lock
    redis_lock.reset_all()

Alternativelly, you can reset individual locks via the ``reset`` method.

Use these carefully, if you understand what you do.


Features
========

* based on the standard SETNX recipe
* optional expiry
* no spinloops at acquire

Implementation
==============

``redis_lock`` will use 2 keys for each lock named ``<name>``:

* ``lock:<name>`` - a string value for the actual lock
* ``lock-signal:<name>`` - a list value for signaling the waiters when the lock is released

This is how it works:

.. image:: https://raw.github.com/ionelmc/python-redis-lock/master/docs/redis-lock%20diagram.png
    :alt: python-redis-lock flow diagram

Documentation
=============

https://python-redis-lock.readthedocs.org/

Development
===========

To run the all tests run::

    tox

Requirements
============

:OS: Any
:Runtime: Python 2.6, 2.7, 3.2, 3.3 or PyPy
:Services: Redis 2.6.12 or later.

Similar projects
================

* `bbangert/retools <https://github.com/bbangert/retools/blob/master/retools/lock.py>`_ - acquire does spinloop
* `distributing-locking-python-and-redis <https://chris-lamb.co.uk/posts/distributing-locking-python-and-redis>`_ - acquire does polling
* `cezarsa/redis_lock <https://github.com/cezarsa/redis_lock/blob/master/redis_lock/__init__.py>`_ - acquire does not block
* `andymccurdy/redis-py <https://github.com/andymccurdy/redis-py/blob/master/redis/client.py#L2167>`_ - acquire does spinloop
* `mpessas/python-redis-lock <https://github.com/mpessas/python-redis-lock/blob/master/redislock/lock.py>`_ - blocks fine but no expiration


Changelog
=========

2.0.0 (2014-12-29)
------------------

* Rename ``Lock.token`` to ``Lock.id``. Now only allowed to be set via constructor. (contributed by Jardel Weyrich)

1.0.0 (2014-12-23)
------------------

* Fix Django integration. (reported by Jardel Weyrich)
* Reorganize tests to use py.test.
* Add test for Django integration.
* Add ``reset_all`` functionality. (contributed by Yokotoka)
* Add ``Lock.reset`` functionality.
* Expose the ``Lock.token`` attribute.

0.1.2 (2013-11-05)
------------------

* ?

0.1.1 (2013-10-26)
------------------

* ?

0.1.0 (2013-10-26)
------------------

* ?

0.0.1 (2013-10-25)
------------------

* First release on PyPI.


