Permalink
Browse files

Small fix to allow dicts, and then documented

  • Loading branch information...
1 parent ebe527e commit ad168bc768f483197f5ede0615eb81cf35b513ae @atl atl committed Sep 14, 2010
Showing with 84 additions and 16 deletions.
  1. +60 −1 docs/proxy.rst
  2. +24 −15 redish/proxy.py
View
@@ -24,7 +24,7 @@ objects when accessing by key::
Deletion and invalid keys work as expected::
- >>> del(x["foo"])
+ >>> del x["foo"]
>>> x["foo"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
@@ -183,3 +183,62 @@ A Proxy object retains all the normal methods from Redis object::
['z', 'dictionary', 'Liszt', 'set', 'game']
>>> x.bgsave()
True
+
+Keyspaces in proxy objects
+--------------------------
+
+The fact that Redis offers a flat keyspace in each of its databases is
+a great benefit: it does not presuppose any structure for the keys,
+and access is fast and unencumbered. However, users are likely to want
+some structure in using keys, and the Keyspaces feature is a first
+attempt at making key name patterns accessible to users.
+
+At the heart, a "keyspace" is a formatstring with an associated label.
+Access is achieved by accessing elements in the proxy with a tuple
+argument, with the label as the first element of the tuple and the
+following elements used as inputs to the formatstring::
+
+ >>> x.register_keyspace('myspace', "person:%04d:name")
+ 'myspace'
+ >>> x['myspace', 1] = "Bob"
+ >>> x['person:0001:name']
+ u'Bob'
+
+The label string is returned to facilitate structured and symbolic use
+of the keyspaces, so the following is equivalent to the above::
+
+ >>> UNAME = x.register_keyspace('myspace', "person:%04d:name")
+ >>> x[UNAME, 1] = "Bob"
+ >>> x['myspace', 1]
+ u'Bob'
+
+One can debug the keyspaces by feeding a tuple to `actual_key`::
+
+ >>> x.actual_key((UNAME, 202))
+ 'person:0202:name'
+
+One can also obtain a keyspace as a subset of all the keys in the
+database, allowing you to treat the keyspace as a dict::
+
+ >>> names = x.keyspace(UNAME)
+ >>> names[1]
+ u'Bob'
+
+These features can be combined::
+
+ >>> ZZ = x.register_keyspace('friends', '%(type)s:%(id)04d:friends')
+ >>> friendstore = x.keyspace(ZZ)
+ >>> frank = {'type': 'person', 'id': 203,
+ ... 'friends': set([204, 1]), 'name': 'Frank'}
+ >>> fido = {'type': 'pet', 'id': 204,
+ ... 'name': 'Fido', 'friends': set([1, 202])}
+ >>> for o in [frank, fido]:
+ ... friendstore[o] = o['friends']
+ >>> x['person:0203:friends']
+ <Set: ['1', '204']>
+ >>> x['pet:0204:friends'].intersection(friendstore[frank])
+ set(['1'])
+
+I have no idea at this point if these experimental features are
+useful, but they are small, and seem to make sense to me. Feedback is
+appreciated.
View
@@ -14,6 +14,7 @@
(originally by Adam T. Lindsay)
"""
from codecs import encode, decode
+import re
from redis import Redis
from redish import types
@@ -31,6 +32,9 @@
types.ZSet: types.SortedSet,
}
+# OMG, what have I done?
+FORMAT_SPEC = re.compile(r'%(\(\w+\))?[#0\- \+]?[0-9\*]*\.?[0-9\*]*[hlL]?[diouxXeXfXgGcrs]')
+
def int_or_str(thing, key, client):
try:
int(thing)
@@ -52,16 +56,19 @@ def __init__(self, *args, **kwargs):
that container is kept in the (local thread's) proxy object so that
subsequent accesses keep the right type without throwing KeyErrors.
"""
- self.empties = {}
- self.keyspaces = {}
+ self._empties = {}
+ self._keyspaces = {}
super(Proxy, self).__init__(*args, **kwargs)
def keyspaced(f):
def preprocessed(self, key, *argv):
if isinstance(key, tuple):
keyspace = key[0]
- keyargs = tuple(key[1:])
- key = self.keyspaces[keyspace] % keyargs
+ if len(key) == 2:
+ keyargs = key[1:][0]
+ else:
+ keyargs = tuple(key[1:])
+ key = self._keyspaces[keyspace] % keyargs
return f(self, key, *argv)
return preprocessed
@@ -75,11 +82,11 @@ def __getitem__(self, key):
if typ == 'string':
# because strings can be empty, check before "empties"
return int_or_str(self.get(key), key, self)
- if key in self.empties:
+ if key in self._empties:
if typ == 'none':
- return self.empties[key]
+ return self._empties[key]
else:
- self.empties.pop(key)
+ self._empties.pop(key)
if typ == 'none':
raise KeyError(key)
else:
@@ -88,8 +95,8 @@ def __getitem__(self, key):
@keyspaced
def __setitem__(self, key, value):
"""Copy the contents of the value into the redis store."""
- if key in self.empties:
- del self.empties[key]
+ if key in self._empties:
+ del self._empties[key]
if isinstance(value, (int, types.Int)):
self.set(key, int(value))
return
@@ -100,7 +107,7 @@ def __setitem__(self, key, value):
if self.exists(key):
self.delete(key)
if value != None:
- self.empties[key] = REV_TYPE_MAP[type(value)](key, self)
+ self._empties[key] = REV_TYPE_MAP[type(value)](key, self)
return
pline = self.pipeline()
if self.exists(key):
@@ -124,7 +131,7 @@ def __contains__(self, key):
We check for existence within the *proxy object*, and so we
must look in both the backing store and the object's "empties."
"""
- return self.exists(key) or key in self.empties
+ return self.exists(key) or key in self._empties
@keyspaced
def __delitem__(self, k):
@@ -133,11 +140,13 @@ def __delitem__(self, k):
else:
keys = [k]
for key in keys:
- if key in self.empties:
- del self.empties[key]
+ if key in self._empties:
+ del self._empties[key]
self.delete(*keys)
def multikey(self, pattern):
+ if pattern in self._keyspaces:
+ pattern = FORMAT_SPEC.sub('*', self._keyspaces[pattern])
for p in self.keys(pattern):
yield self[p]
@@ -152,14 +161,14 @@ def register_keyspace(self, shortcut, formatstring):
proxyobject[VALUE, 1001] = "Fred"
proxyobject['val', 1001]
"""
- self.keyspaces[shortcut] = formatstring
+ self._keyspaces[shortcut] = formatstring
return shortcut
def keyspace(self, keyspace):
"""
Convenient, consistent access to a sub-set of all keys.
"""
- return KeyspacedProxy(self, self.keyspaces[keyspace])
+ return KeyspacedProxy(self, self._keyspaces[keyspace])
@keyspaced
def actual_key(self, key):

0 comments on commit ad168bc

Please sign in to comment.