Futures

When redis clients communicate with the server, they send a command, then wait for the response. The redis-py client reflects this design choice. But when you pipeline, you don’t wait. You queue up a bunch of commands. Then you execute them. Then you gather these results and feed them back to where they need to go.

This forces the code invoking the execute method to know what to do with all the responses.

That’s pretty inconvenient.

The RedPipe Future makes it possible to get a reference to the data before pipeline execute happens. A Future is a contract that says the result of this redis command will be populated into this object once the pipeline executes. And you can use a Future just like you would the actual result. But only after the pipeline executes. If you try to use it prior to that, it raises a redpipe.ResultNotReady exception. Kaboom!

Well, what use is that?

For one, it makes it easier to assign it a variable name in the context of calling the command. No confusion about how to get the result you need from the array of results returned by pipeline.execute. Unlike redis-py RedPipe does not return an array of results on the execute call. You already have the result as a variable returned from the command you called initially.

More importantly, the Future can be passed into a python closure that can do additional work. Tell the pipeline to execute your closure callback function after it runs. Now you have a powerful mechanism to format those results and build modular reusable components.

Still don’t quite see how? Read more about callbacks.

Gotchas

There are a few gotchas to watch out for:

  • isinstance() checks
  • identity checks like: future is None
  • trying to mutate the object like this: future += 1

You can always type cast the object into the type you expect if you need this behavior.

f = Future()
f.set(1)

# `f is 1` fails
assert(int(f) is 1) # works

This doesn’t work so well for is None checks. You can’t type-cast to None. You can use equality checks though.

f = Future()
f.set(None)
assert(f == None)

This is frowned upon by most lint-checks who think the is comparison is more appropriate. But if you do an is comparison, that compares the object ids. And there’s no way for RedPipe to wrap that.

UGH!

In response, I came up a handy IS method.

f = Future()
f.set(None)
assert(f.IS(None))

Or you can access the underlying result:

f = Future()
f.set(None)

assert(f.result is None)

Hope that helps.

Examples

Other than those few caveats, you should be able to access a future object just like the underlying result.

Here are some examples if your result is numeric.

future = Future()
future.set(1)
assert(future == 1)
assert(future != 2)
assert(future > 0)
assert(future < 2)
assert(future >= 1)
assert(future <= 1)
assert(bool(future))
assert(float(future) == 1.0)
assert(future + 1 == 2)
assert(future * 2 == 2)
assert(future ^ 1 == 0)
assert(repr(future) == '1')

And here is an example if your future is a list:

future = Future()
future.set([1])
assert(future == [1])
assert([v for v in future] == [1])
assert(future + [2] == [1, 2])

And here is a dictionary:

future = Future()
future.set({'a': 1})
assert(future == {'a': 1})
assert(dict(future) == {'a': 1})
assert({k: v for k, v in future.items()} == {'a': 1})

There are many more operations supported but these are the most common. Let me know if you need more examples or explanation.

Json Serialization

The default json serializer doesn’t know anything about RedPipe Futures. When it encounters a Future, the json encoder would normally blow up.

I monkey-patched it so it will serialize correctly.

future = Future()
future.set({'a': 1})
json.dumps(future)

The monkey-patching is pretty simple. Take a look at the source code if you are interested. If you have serious objections to this hack, let me know.

If you used a different json serializer, I can’t help you. You may be able to patch those libraries on your own. Or you could also explicitly extract the result or type-cast before encoding as json.