-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Feature request: Namespace support #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This seems like a reasonable suggestion. I'll add it in the next version. Remember that the namespace string will add extra bytes to each key. It might be better in some cases to namespace with the db=X option, since it has no additional memory overhead. |
What is the use case in real life (development or production)? |
This allows the user to run several "applications" on top of a single Redis install in a manner that is more flexible than using different databases and different Redis processes. This feature becomes increasingly useful as the number of utilities built on top of Redis increases, because namespace support allows multiple apps to be run against a single Redis install without fear of keys overlapping. For example, Resque ( http://github.com/defunkt/resque ), uses namespacing to enable users to run multiple Resque instances in a straightforward manner. (More details are in the Namespaces section of the README.) The feature also lets this same Redis instance be used for a different purpose, in addition to Resque. It's most useful in a development context though. Right now I'm using Redis for over a dozen different purposes and I need to do this constant juggling act on my main dev machine (and, in general, be very careful that keys don't collide). |
Okay so I came up with an ugly but obvious solution. I use a decorator and decorate nearly all commands. Pros: obvious |
@kmerenkov: Check out the consistent_hashing branch in my redis-py repo. I needed a way to detect which arguments in a command were keys just like you do, but did it a bit differently, similar to the way response callbacks work. I was envisioning using that for namespacing as well, barring a more elegant solution. |
I feel I probably too late to suggest it, but such namespacing is easily done as a separate wrapper. Crude attempt is available at http://gist.github.com/616851 - it needs more special-casing for functions which don't follow (key, *args) signature, but it works pretty well for me. |
Hey Andy, any updates on this feature? It's really useful for development when you have many developers working on the same instance. Especially if you have many redis instances in your topology Thanks |
I started down the road of doing a wrapper that adds namespacing on top of redis-py. Here are the beginnings of what I was thinking: https://gist.github.com/2190617. It's not complete, but if there is still interest in adding ns to redis-py, i'd love to get feedback and possibly switch this over to something like a pull request. |
I started playing around with an implementation here: https://github.com/zmsmith/predis/blob/master/predis/client.py It's based on StrictRedis using consistently named parameters, but it could definitely be changed to be more configurable. Also, it doesn't alter any calls that return keys right now, but it's a start. |
Any progress on this? It would be nice to have namespaces. |
I have a Python project, subredis, that implements this nicely. A Subredis is a nearly full StrictRedis implementation/wrapper that uses a namespace. Its definitely a useful pattern. Some thoughts on limitations:
|
2010-2015 |
@andymccurdy You've mentioned you could "add it in the next version". Is it still an option? I believe it would be a welcomed feature. Especially now that multiple databases are discouraged. Edit: multiple databases might not be deprecated for non-cluster mode after all. |
+1 |
1 similar comment
+1 |
+1? |
@andymccurdy Here is a working version with tests. I can expand the testing to all commands if you like the direction. exit99@8994f26 |
@kazanz your solution only works for commands that deal with exactly one key. Commands like KEYS, SUNION/SINTER, SORT, and others that operate on 2 or more keys would not be covered. Also, any command that doesn't operate on a key, e.g. INFO, CONFIG etc. would also break with this change. It's one of the reasons why this issue is tricky and hasn't already been addressed. |
@andymccurdy I see. If no one else is addressing the issue, I would be happy to. Let me know. |
@andymccurdy Here is a working version that allows namespacing on every command along with tests for every command. Works in py2 & py3. Followed proposed syntax of @mjrusso. Pull request here. |
6 years later, still no namespace? |
@etcsayeghr Pull requests are accepted. This is a pretty complicated issue that I personally don't have a need for. You can see some of the complexities here: #710 (comment) |
@andymccurdy Finally have some more time to refactor based on your comments. Will post periodically for feedback. |
Finished a working PR with namespace support for all cmd calls on:
Refactored based on @andymccurdy comments on my last PR. New PR uses simple decorators to handle namespacing. Followed proposed syntax by @mjrusso. Includes full test suit for python2 + python3. Here is my response to the complexities mentioned. Thanks |
@andymccurdy Any update on this pull request? We use this in production at my company and would like to see it integrated so we don't have to maintain a separate installation. |
@Kazanz Hey, sorry I've been away visiting family and friends for the past 8 weeks. When I last looked at this, there were some commands that weren't implemented ( After discovering a few of these types of issues I went off into the weeds and made a separate implementation. I made
Although still incomplete (I haven't dealt with commands that return values like |
@andymccurdy Having 8 weeks for family and friends must be nice :). I like the Perhaps we could also have a When you get your branch up, I will go through and write defaults for each command and anything else you think needs to be addressed. Happy to be moving forward on this. |
@andymccurdy May I know the latest status of this please? |
@Jerry-Ma please remember this is an open-source project and people are giving their time freely to work on it... in a sense this makes the contributors the customer, you need to sell them an idea if you want them to implement it. That being said, I will probably PR a change that'll let you pass non-str/bytes objects as keys, and just call class NamespacedKey(object):
def __init__(self, namespace, key=None):
self._namespace = namespace
self.key = None
def __str__(self):
return f'{self._namespace}:{self.key}'
# ...
k1 = NamespacedKey('namespace')
k1.key = 'keyname'
r = redis.client.Redis(...)
r.set(k1, 'test value') # currently raises an exception:
# DataError: Invalid input of type: 'NamespacedKey'. Convert to a byte, string or number first. |
@AngusP That seems like a pretty reasonable approach to me. I like that it has no performance penalty to folks who don't need namespaced keys. If you're going the route of global key instances that are defined in some shared location and then imported/used in Redis commands, we'll also need a way to template the key such that runtime variables can be interpolated. For example, I might have a hash object per user identified by their user_id, e.g. With that requirement, perhaps we just do something like: # defined in some shared location...
namespace = Namespace('my-namespace:')
user_key = namespace.key('user-%(user_id)s')
# now we can use the key elsewhere...
r = redis.Redis()
user_id = ... # get the user_id from somewhere
user_data = r.hgetall(user_key.resolve(user_id=user_id)) This also has the added benefit of not needing special treatment in the redis-py |
I totally agree with your comments and I did end up implement the namespace on my own after I posted the comment. I made use the class _KeyDecorator(object):
def __init__(self, prefix=None, suffix=None):
self._prefix = prefix or ''
self._suffix = suffix or ''
def _decorate(self, key):
return f"{self._prefix}{key}{self._suffix}"
def decorate(self, *keys):
return tuple(map(self._decorate, keys))
def _resolve(self, key):
return key.lstrip(self._prefix).rstrip(self._suffix)
def resolve(self, *keys):
return tuple(map(self._resolve, keys))
def __call__(self, *args):
return self.decorate(*args)
def r(self, *args):
return self.resolve(*args)
class RedisKeyDecorator(_KeyDecorator):
pass
class RedisIPC(object):
connection = StrictRedis.from_url(url, decode_responses=True)
_dispatch_key_positons = {
'get': ((0, ), None),
'set': ((0, ), None),
}
def __init__(self, namespace=None):
self._key_decor = RedisKeyDecorator(prefix=namespace)
def __call__(self, func_name, *args, **kwargs):
_key_pos, _key_return_pos = self._dispatch_key_positons[
func_name]
if isinstance(_key_pos, slice):
_key_pos = range(*_key_pos.indices(len(args)))
for i, a in enumerate(args):
if i in _key_pos:
args[i] = self._key_decor.decorate(a)
result = getattr(
self.connection, func_name)(*args, **kwargs)
return result |
I like the idea of using class AbstractKeyDecorator(abc.Meta):
@classmethod
@abstractmethod
def decorate(cls, key):
"""Return the decorated key."""
return NotImplemented
@classmethod
@abstractmethod
def resolve(cls, key):
"""The inverse of `decorate`."""
return NotImplemented
class MyKeyDecor(AbstractKeyDecorator):
# implement the interfaces
pass
conn = StrictRedis(...)
# stateful
conn.set_key_decorator(MyKeyDecor)
value = conn.get("a") # "a" get decorated
keys = conn.scan("*") # all returned keys get resolved.
# or state less
value = conn.get("a", key_decorator=MyKeyDecor)
keys = conn.scan("*", key_decorator=MyKeyDecor) |
I suppose I'm maybe misinterpreting the use case -- there seem to be two slightly different ones
We could perhaps address both simply by adding optional hooks that can be used to transform keys on the way in (and sometimes on the way out of the client). e.g. def prefix(key):
return f'my_namespace:{key}'
# Simple encapsulation example
r = Redis(..., key_hook=prefix)
r.get('test') # calls GET my_namespace:test
# More complex:
def prefix(key):
return f'{{my_namespace}}:node_{os.uname().nodename}:{key}' If you want your keys to all be instances of some class that does fancy things to key names, just pass In the convenience case, if a stateless hook is insufficient then it might just be easier to be explicit as in @andymccurdy's example |
I favor convenience for several reasons.
If we go the "convenience" option, I'd be fine including those helpers within redis-py and documenting how to use them. |
This issue is marked stale. It will be closed in 30 days if it is not updated. |
I’d like to see this and I’d be happy to work on it. Sounds like @andymccurdy is ok with the convenience approach, which I could work on. Managing complex keys is an important part of most non-trivial projects, so we should give users some tooling to help with that and guidance on how to do it well. |
A while back I implemented a small class to do this. I called it a "keyspace" and basically it took care of the prefix (i.e. namespace) and also had a hash which kept track of all the keys created in the namespace. It also had some capability to manage TTL on keys and a few other things. The api was/is more or less: foo = keyspace("foo") It's not really complicated to implement, and whatever you do, it probably will not hit my use-case exactly. So is it really something that belongs as a part of redis-py? It could be better as a separate project. |
Year 2047, this feature still not implemented |
+10000000000000000000000000000000000000000000000000000000000000000000 |
+1. This feature would be very useful for enforcing data isolation in a multi-tenancy use case where different client instances may use the same database but shouldn't touch each other's keys. |
+1 |
1 similar comment
+1 |
👀 |
+1 |
+1 |
+1 - Would help with multi tenancy solutions with shared db |
+1 |
any good news for this one!? |
maybe something like this being implemented in the main package!? |
2010 - 2024! |
Close redis#12 Signed-off-by: Salvatore Mesoraca <[email protected]>
Proposal:
Behind-the-scenes, this would prefix every key with
foobar
.For example, if I issued the following command:
It would be equivalent to the following:
In Ruby, the redis-namespace class does the same thing.
However, I think that it would be preferable if this were a core feature of the Python client.
The text was updated successfully, but these errors were encountered: