Permalink
Browse files

At version 1.0.0

- keygens.py and keystores.py have been renamed to key.py and store.py. Keystores are now Stores.
- Revokation tokens are now a required part of every Store.
- All multi* functions have been replaced with a context.
- shorten.shortener is now called shorten.make_store to better reflect its usage.
- A Formatter class is used to format tokens and keys internally.
- lock.py provides an easy-to-use gevent mutex.
- Updated all tests to be more readable.
  • Loading branch information...
1 parent 3ebbe83 commit 6bca92c782539310afd050f57e6ba12367dfef41 Charlie Liban committed Feb 5, 2013
View
@@ -1,3 +1,4 @@
+*.sw[p-z]
*.py[oc]
venv/
env/
View
Binary file not shown.
View
Binary file not shown.
View
@@ -1,40 +1,65 @@
-from . import keygens
-from . import keystores
+import key
+import token
+import store
-__version__ = '0.2.3'
-__all__ = ['keygens', 'shortener', 'keystores', '__version__', 'shorteners']
+from formatter import Formatter
-shorteners = ('memcache', 'memory', 'mongo', 'redis', 'redis-bucket', 'sqlalchemy')
+__version__ = '1.0.0'
+__all__ = ['__version__', 'key', 'token', 'store', 'make_store', 'stores', 'Formatter']
-def shortener(name, min_length=4, start=None, alphabet=None, **kwargs):
- """
- Simplifies creation of keystores.
+stores = ('memcache', 'memory', 'mongo', 'redis', 'redis-bucket', 'sqlalchemy')
+
+def make_store(name, **kwargs):
+ """\
+ Simplifies creation of stores by linking them with the appropriate keygens.
- 'redis' creates a redis-backed keystore
- 'memory' creates a memory-backed keystore
+ Valid stores are:
+
+ 'redis' A redis-backed store.
+ 'memory' A memory-backed store.
+
+ `min_length` The minimum key length, in characters.
+
+ `start` The index to start counting from.
+
+ `alphabet` The alphabet to use: any indexable object
+ can be used as an alphabet.
+
"""
+
+ keygen_args = {
+ 'min_length': kwargs.pop('min_length', 4),
+ 'start': kwargs.pop('start', None),
+ 'alphabet': kwargs.pop('alphabet', None),
+ }
+
+ store_args = kwargs
if name == 'memcache':
raise NotImplemented()
+
elif name == 'memory':
- keygen = keygens.MemoryKeygen(min_length=min_length,
- start=start,
- alphabet=alphabet)
- return keystores.MemoryKeystore(keygen, **kwargs)
+ keygen = key.MemoryKeygen(**keygen_args)
+ return store.MemoryStore(keygen, **store_args)
+
elif name == 'mongo':
raise NotImplemented()
+
elif name == 'redis':
- counter_key = kwargs.pop('counter_key', None)
- redis = kwargs['redis']
- keygen = keygens.RedisKeygen(min_length=min_length,
- start=start,
- alphabet=alphabet,
- counter_key=counter_key,
- redis=redis)
- return keystores.RedisKeystore(keygen, **kwargs)
+ keygen_args.update({
+ 'redis_counter_key': kwargs.pop('redis_counter_key', None),
+ 'redis': kwargs.get('redis', None),
+ })
+
+ keygen = key.RedisKeygen(**keygen_args)
+ return store.RedisStore(keygen, **store_args)
+
elif name == 'redis-bucket':
raise NotImplemented()
+
elif name == 'sqlalchemy':
raise NotImplemented()
+
else:
- raise Exception("valid shorteners are %s" % ', '.join(shorteners))
+ raise Exception("valid stores are %s" % ', '.join(stores))
+
View
@@ -0,0 +1,14 @@
+__all__ = ['Formatter']
+
+class Formatter(object):
+ """\
+ A formatter modifies the key (or revokation token) before it is stored
+ in a Keystore. Externally visible keys (and revokation tokens) are not
+ altered by the formatter.
+ """
+
+ def format_token(self, token):
+ return token
+
+ def format_key(self, key):
+ return key
@@ -1,15 +1,8 @@
import operator
-
-try:
- import gevent.coros
- __gevent__ = True
-except ImportError:
- __gevent__ = False
+from lock import Lock
__all__ = ['Keygen', 'MemoryKeygen', 'RedisKeygen']
-DEFAULT_ALPHABET = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
-
def bx_encode(n, alphabet):
"""\
Encodes an integer ``n`` as a string by mapping it to the 0-indexed iterable ``alphabet``.
@@ -59,13 +52,15 @@ def largerange(start, stop, step=1):
class Keygen(object):
"""\
- Generates keys with symbols from an ``alphabet``.
+ Generates keys with symbols from an indexable ``alphabet``.
The keys may or may not be in lexographic order.``min_length`` or ``start`` can
be specified to generate keys of a certain string length or larger.
"""
+
+ DEFAULT_ALPHABET = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
- def __init__(self, alphabet=None, min_length=4, start=None):
- self._alphabet = alphabet or DEFAULT_ALPHABET
+ def __init__(self, alphabet=None, min_length=4, start=None):
+ self._alphabet = alphabet or self.DEFAULT_ALPHABET
if min_length is not None and start is not None:
raise Exception("only one of 'min_length' or 'start' can be set")
@@ -77,10 +72,7 @@ def __init__(self, alphabet=None, min_length=4, start=None):
else:
self._start = start
- def next(self):
- raise NotImplemented()
-
- def map(self, n):
+ def encode(self, n):
return bx_encode(n, self._alphabet)
alphabet = property(lambda self: self._alphabet)
@@ -90,69 +82,52 @@ class MemoryKeygen(Keygen):
"""\
Generates keys in-memory.
"""
-
- def __init__(self, *args, **kwargs):
- super(MemoryKeygen, self).__init__(*args, **kwargs)
- self._current = self._start
- if __gevent__:
- self._lock = gevent.coros.Semaphore()
- else:
- self._lock = None
-
- def _acquire(self):
- if self._lock is not None:
- self._lock.acquire()
-
- def _release(self):
- if self._lock is not None:
- self._lock.release()
-
- def _next_generator(self, start, total):
- for i in largerange(start, total):
- yield self.map(i)
-
- def next(self, num=1):
+
+ def __iter__(self):
"""\
Increments the in-memory counter immediately and returns a new key.
"""
- self._acquire()
- current = self._current
- self._current += num
-
- if num == 1:
- self._release()
- return [self.map(current)]
- else:
- self._release()
- return self._next_generator(current, current+num)
+
+ lock = Lock()
+ current = self._start
+
+ while True:
+ try:
+ lock.acquire()
+ yield self.encode(current)
+ current += 1
+
+ finally:
+ lock.release()
class RedisKeygen(Keygen):
"""\
Generates keys in-memory but increments them in Redis.
"""
- DEFAULT_COUNTER_KEY = 'shorten:counter'
+ COUNTER_KEY = 'counter'
def __init__(self, *args, **kwargs):
- self._counter_key = kwargs.pop('counter_key', self.DEFAULT_COUNTER_KEY)
- self._redis = kwargs.pop('redis')
+ self._counter_key = kwargs.pop('redis_counter_key', self.COUNTER_KEY)
+ self._redis = kwargs.pop('redis', None)
+
+ if self._redis is None:
+ raise Exception('A Redis object is required for the RedisKeygen.')
+
super(RedisKeygen, self).__init__(*args, **kwargs)
- def next(self, num=1):
+ def __iter__(self):
"""\
Increments the global Redis counter immediately and returns a new key.
"""
- ckey = self._counter_key
+ ckey = self.counter_key
start = self._start
- redis = self._redis
-
- if num == 1:
- return [self.map(redis.incr(ckey) + start - 1)]
- else:
- with redis.pipeline() as pipe:
- for i in largerange(0, num):
- pipe.incr(ckey)
- return [self.map(token + start - 1) for token in pipe.execute()]
+
+ while True:
+ i = self._redis.incr(ckey) + start - 1
+ yield self.encode(i)
- counter_key = property(lambda self: self._counter_key)
+ @property
+ def counter_key(self):
+ return self._counter_key
Oops, something went wrong.

0 comments on commit 6bca92c

Please sign in to comment.