Skip to content

Commit

Permalink
add documentation about custom storage implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
alisaifee committed Oct 5, 2015
1 parent 97eacad commit 5666554
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
86 changes: 86 additions & 0 deletions doc/source/custom-storage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.. currentmodule:: limits

Custom storage backends
-----------------------

The **limits** package ships with a few storage implementations which allow you
to get started with some common data stores (redis & memcached) used for rate limiting.

To accommodate customizations to either the default storage backends or
different storage backends altogether, **limits** uses a registry pattern that
makes it relatively painless to add a new storage (without having to submit patches
to the package itself).

Creating a custom backend requires:

#. Subclassing :class:`limits.storage.Storage`
#. Providing implementations for the abstractmethods of :class:`limits.storage.Storage`
#. If the storage can support the :ref:`moving-window` strategy - additionally implementing
the `acquire_entry` instance method.
#. Providing a naming *scheme* that can be used to lookup the custom storage in the storage registry.
(Refer to :ref:`storage-scheme` for more details)

Example
=======
The following example shows two backend stores: one which doesn't implement the
:ref:`moving-window` strategy and one that does. Do note the :code:`STORAGE_SCHEME` class
variables which result in the classes getting registered with the **limits** storage registry::


import urlparse
from limits.storage import Storage
import time

class AwesomeStorage(Storage):
STORAGE_SCHEME="awesomedb"
def __init__(self, uri, **options):
self.awesomesness = options.get("awesomeness", None)
self.host = urlparse.urlparse(uri).netloc
self.port = urlparse.urlparse(uri).port

def check(self):
return True

def get_expiry(self, key):
return int(time.time())

def incr(self, key, expiry, elastic_expiry=False):
return

def get(self, key):
return 0


class AwesomerStorage(Storage):
STORAGE_SCHEME="awesomerdb"
def __init__(self, uri, **options):
self.awesomesness = options.get("awesomeness", None)
self.host = urlparse.urlparse(uri).netloc
self.port = urlparse.urlparse(uri).port

def check(self):
return True

def get_expiry(self, key):
return int(time.time())

def incr(self, key, expiry, elastic_expiry=False):
return

def get(self, key):
return 0

def acquire_entry(
self, key, limit, expiry, no_add=False
):
return True


Once the above implementations are declared you can look them up
using the factory method described in :ref:`storage-scheme` in the following manner::

from limits.storage import storage_from_string

awesome = storage_from_string("awesomedb://localhoax:42", awesomeness=0)
awesomer = storage_from_string("awesomerdb://localhoax:42", awesomeness=1)

10 changes: 6 additions & 4 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and storage backends such as redis & memcached.
:hidden:

string-notation
custom-storage
storage-url
strategies
api

Expand All @@ -16,23 +18,23 @@ and storage backends such as redis & memcached.
Quickstart
----------

Build the storage backend::
Initialize the storage backend::

from limits import storage
memory_storage = storage.MemoryStorage()

Build a rate limiter with the :ref:`moving-window`::
Initialize a rate limiter with the :ref:`moving-window` strategy::

from limits import strategies
moving_window = strategies.MovingWindowRateLimiter(memory_storage)


Build a rate limit using the :ref:`ratelimit-string`::
Initialize a rate limit using the :ref:`ratelimit-string`::

from limits import parse
one_per_minute = parse("1/minute")

Build a rate limit explicitly using a subclass of :class:`RateLimitItem`::
Initialize a rate limit explicitly using a subclass of :class:`RateLimitItem`::

from limits import RateLimitItemPerSecond
one_per_second = RateLimitItemPerSecond(1, 1)
Expand Down
42 changes: 42 additions & 0 deletions doc/source/storage-url.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.. currentmodule:: limits
.. _storage-scheme:

Storage url scheme
------------------

**limits** uses a url style storage scheme notation (similar to the JDBC driver
connection string notation) for configuring and initializing
storage backends. This notation additionally provides a simple
mechanism to both identify and configure the backend implementation based on
a single string argument.

The storage scheme follows the format :code:`{scheme}://{parameters}`

:func:`limits.storage.storage_from_string` is provided to
lookup and construct an instance of a storage based on the storage scheme. For example::

import limits.storage
uri = "redis://localhost:9999"
options = {}
redis_storage = limits.storage.storage_from_string(uri, **options)


Examples
========

In-Memory
The in-memory storage takes no parameters so the only relevant value is :code:`memory://`

Memcached
Requires the location of the memcached server(s). As such
the parameters is a comma separated list of :code:`{host}:{port}` locations such as
:code:`localhost:11211` or :code:`localhost:11211,localhost:11212,192.168.1.1:11211` etc...

Redis
Requires the location of the redis server and optionally the database number.
:code:`localhost:6379` or :code:`localhost:6379/1` (for database `1`)

Redis with Sentinel
Requires the location(s) of the redis sentinal instances and the `service-name`
that is monitored by the sentinels.
:code:`localhost:26379/my-redis-service` or :code:`localhost:26379,localhost:26380/my-redis-service`

0 comments on commit 5666554

Please sign in to comment.